> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scalev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout and Payments

> Prepare shipping, compute checkout totals, create guest orders, and render payment instructions from a browser storefront.

Storefront API checkout uses the same buyer-facing checkout concepts as the Scalev-hosted storefront. A browser storefront can prepare delivery options, compute the checkout summary, create a guest order, read the public order, and create or reuse payment instructions without a merchant backend.

The Storefront API is designed so your storefront can render its own payment page. Use the public order and payment responses to show buyer-facing instructions directly in your UI. `payment_url` is still returned as a hosted fallback for storefronts that have not implemented a method-specific renderer or for provider flows that must open a hosted payment page.

Use only payment methods returned by `GET /v3/stores/{store_id}/public/payment-methods`. Storefront checkout does not return `no_payment`, and checkout endpoints reject it if submitted directly.

## Guest checkout flow

1. Read or create the guest cart and store `X-Scalev-Guest-Token`.
2. Ask the buyer for delivery location and postal code when the cart contains physical items.
3. Call `POST /public/checkout/shipping-options`.
4. Let the buyer select a shipping option.
5. Call `POST /public/checkout/summary`.
6. Call `POST /public/checkout` with buyer details and the selected checkout fields.
7. Read the order with `GET /public/orders/{secret_slug}`.
8. For manual `bank_transfer`, request a transfer-proof upload URL, upload the image, and patch the order with the returned file URL.
9. Call `POST /public/orders/{secret_slug}/payment` and render the returned payment data on your own payment page.

## Shipping options

```http theme={null}
POST /v3/stores/{store_id}/public/checkout/shipping-options
```

Headers:

```text theme={null}
X-Scalev-Storefront-Api-Key: sfpk_...
X-Scalev-Guest-Token: <guest-token>
```

Send direct typed items, or omit `items` and let `X-Scalev-Guest-Token` select the guest cart:

```json theme={null}
{
  "items": [
    {
      "type": "variant",
      "variant_id": 494535,
      "quantity": 1
    }
  ],
  "destination": {
    "location_id": 9089,
    "postal_code": "10510"
  },
  "payment_method": "bank_transfer"
}
```

Direct `items[]` supports variants and bundle price options. Bundle price option items use `{ "type": "bundle_price_option", "bundle_price_option_id": 123, "quantity": 1 }`.

Response:

```json theme={null}
{
  "data": [
    {
      "courier_service_id": 123,
      "courier_code": "jne",
      "service_code": "REG",
      "name": "Regular",
      "cost": 12000,
      "etd": "2-3",
      "is_cod": false,
      "warehouse_unique_id": "warehouse_...",
      "courier_aggregator_code": null
    }
  ],
  "is_paginated": false
}
```

Digital-only carts return:

```json theme={null}
{
  "data": [],
  "is_paginated": false
}
```

## Checkout summary

```http theme={null}
POST /v3/stores/{store_id}/public/checkout/summary
```

Send the selected shipping option. Scalev recomputes the shipping cost server-side and ignores any client-supplied `shipping_cost`.

```json theme={null}
{
  "destination": {
    "location_id": 9089,
    "postal_code": "10510"
  },
  "courier_service_id": 123,
  "warehouse_unique_id": "warehouse_...",
  "courier_aggregator_code": null,
  "payment_method": "bank_transfer"
}
```

Response fields match the existing checkout summary:

```json theme={null}
{
  "product_price": "125000.00",
  "shipping_cost": "12000",
  "other_income": "0",
  "other_income_name": "Biaya Lainnya",
  "gross_revenue": "137000.00"
}
```

Digital-only carts return `shipping_cost: "0"`.

## Discount code check

```http theme={null}
POST /v3/stores/{store_id}/public/discount-codes/check
```

Use a JSON body to validate a discount code before checkout:

```json theme={null}
{
  "code": "SAVE10",
  "items": [
    {
      "type": "variant",
      "variant_id": 494535,
      "quantity": 1
    }
  ],
  "destination": {
    "location_id": 9089,
    "postal_code": "10510"
  },
  "courier_service_id": 123,
  "warehouse_unique_id": "warehouse_abc",
  "courier_aggregator_code": null,
  "payment_method": "bank_transfer"
}
```

If direct `items` are supplied, the API uses them as the item source. Otherwise, provide `X-Scalev-Guest-Token` for the guest cart. Unknown or ineligible codes return a normal validation response with `is_eligible: false` and `discount_code: null`.

## Public checkout

Create the order with:

```http theme={null}
POST /v3/stores/{store_id}/public/checkout
```

The endpoint accepts direct typed `items`, the guest cart referenced by `X-Scalev-Guest-Token`, or both. If both are supplied, direct `items` create the order and the referenced guest cart is cleared after success.

Use the same selected checkout fields:

```json theme={null}
{
  "items": [
    {
      "type": "variant",
      "variant_id": 494535,
      "quantity": 1
    }
  ],
  "customer_name": "Customer Name",
  "customer_email": "customer@example.com",
  "customer_phone": "08123456789",
  "shipping_address": "Jl. Example 1",
  "shipping_location_id": 9089,
  "shipping_subdistrict": "Cempaka Putih",
  "shipping_postal_code": "10510",
  "courier_service_id": 123,
  "warehouse_unique_id": "warehouse_...",
  "courier_aggregator_code": null,
  "payment_method": "bank_transfer"
}
```

The checkout endpoint uses the selected courier service, warehouse, destination, payment method, and checkout items to recompute the shipping cost internally. Do not treat a client-supplied `shipping_cost` as the source of truth.

On success, the response includes the created slim public order data, including `secret_slug`, `public_order_url`, `payment_url`, status, totals, the existing `variants` and `bundle_price_options` object maps, line items, shipping details, and payment fields. Internal order IDs, dashboard-only revenue fields, platform fees, payment-status history, and affiliate attribution are not returned. Use `secret_slug` to read or update the order. Use `payment_url` only as a hosted fallback if your storefront does not render the payment instructions itself.

## Payment instructions

After checkout, call:

```http theme={null}
POST /v3/stores/{store_id}/public/orders/{secret_slug}/payment
```

The endpoint is idempotent. If payment instructions already exist, the response returns the order again with the existing buyer-facing payment data and URLs.

Use this order of preference in a browser storefront:

1. Render the order's method-specific instructions from `payment_method`, `sub_payment_method`, `epayment_provider`, and `pg_payment_info`.
2. For provider-hosted methods, open the provider URL from `pg_payment_info` when the provider requires a redirect.
3. Use `payment_url` only as a fallback to the Scalev-hosted payment page when your storefront does not yet support that method or provider response.
4. Poll the public order when the method needs gateway data that may not exist immediately.

## Method rendering

The Scalev-hosted success page is the reference implementation for method-specific rendering. A Storefront API storefront can replace that page by applying the same `pg_payment_info` mappings in its own UI.

The hosted success page first derives a small set of display fields from the current Xendit/default `pg_payment_info`. This is not a separate API response. It is a useful reference if your storefront wants one payment renderer instead of branching directly on raw provider payload fields:

```json theme={null}
{
  "qrString": "000201010212...",
  "qrImageUrl": "https://api.qrserver.com/v1/create-qr-code/?...",
  "vaNumber": "88081234567890",
  "vaAccountHolder": "Customer Name",
  "invoiceUrl": "https://provider.example/pay/...",
  "renderUrlAs": "button"
}
```

The actual source fields come from the order and payment response. For the current Xendit/default payload, map them this way:

| Display value     | Source field                                                                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qrString`        | `pg_payment_info.qr_string` or `pg_payment_info.payment_method.qr_code.channel_properties.qr_string`                                                                            |
| `vaNumber`        | `pg_payment_info.payment_method.virtual_account.channel_properties.virtual_account_number` or `pg_payment_info.payment_method.over_the_counter.channel_properties.payment_code` |
| `vaAccountHolder` | `pg_payment_info.payment_method.virtual_account.channel_properties.customer_name` or `pg_payment_info.payment_method.over_the_counter.channel_properties.customer_name`         |
| `invoiceUrl`      | `pg_payment_info.invoice_url` or `pg_payment_info.redirect_url`                                                                                                                 |
| `qrImageUrl`      | Generate a QR image from `qrString` in your frontend when you need a downloadable QR image.                                                                                     |

For `gopay`, `dana`, `linkaja`, `shopeepay`, and `ovo`, choose an action URL from `pg_payment_info.actions`: mobile prefers `url_type` `WEB` or `DEEPLINK`; desktop prefers `MOBILE` or `DEEPLINK`.

Set `renderUrlAs` to `button` for `invoice`, `card`, and all mobile views. For other desktop views, render the URL as a QR code.

`bank_transfer`:

Display the order total, manual bank transfer accounts, expiry countdown when enabled, and a payment-confirmation link to `/o/{secret_slug}?showPaymentConfirmation=true`. The hosted page gets bank accounts from the store `payment_accounts` where `method` is `bank_transfer` and the method is still active. `pg_payment_info` can be `{}`.

For manual transfer proof, follow the same flow as the Scalev-hosted public order page:

1. Let the buyer choose a bank account from `store.payment_accounts` where `method` is `bank_transfer`.
2. Let the buyer upload a `jpg`, `png`, or `webp` proof image.
3. Request upload metadata:

```http theme={null}
POST /v3/stores/{store_id}/public/orders/{secret_slug}/transfer-proof-upload
```

```json theme={null}
{
  "filename": "transfer-proof.png",
  "content_type": "image/png",
  "content_length": 12345
}
```

4. Upload the file bytes directly to the returned `upload_url` with `PUT` and the same `Content-Type`.
5. Patch the public order with the returned `file_url`:

```http theme={null}
PATCH /v3/stores/{store_id}/public/orders/{secret_slug}
```

```json theme={null}
{
  "transferproof_url": "https://files.scalev.com/uploads/.../transfer-proof.png",
  "transfer_time": "2026-05-15T04:00:00Z",
  "financial_entity_id": 12,
  "payment_account_holder": "PT Example",
  "payment_account_number": "1234567890"
}
```

6. Refetch the order and show the submitted proof from `transferproof_url`.

`cod`:

Display the COD success body and do not show a payment countdown. Payment creation may return an empty `pg_payment_info`.

Virtual account (`va`):

Render the virtual account bank/channel from `sub_payment_method`, the `vaNumber`, and the optional `vaAccountHolder`. Show a copy button and a payment tutorial link. Known bank codes include `BRI`, `MANDIRI`, `PERMATA`, `BNI`, `BCA`, `MAYBANK`, `CIMB`, `BNC`, `DANAMON`, `BSI`, `AG`, `BJB`, `SAHABAT_SAMPOERNA`, `ARTAJASA`, `BMI`, and `BTN`.

QRIS (`qris`):

Render a QR code from `qrString`. Also offer a QR download action from `qrImageUrl` and show mobile instructions for saving or screenshotting the QR image before paying in a QRIS app. If QR generation fails or `qrString` is missing, show a retry action that calls the public payment endpoint again.

E-wallets except OVO (`gopay`, `dana`, `linkaja`, `shopeepay`):

Use `invoiceUrl` from provider actions. On mobile, open it as a button/deep link. On desktop, render the `invoiceUrl` as a QR code when `renderUrlAs` is `qr_code`; otherwise open it as a button. If no URL can be mapped, send the buyer back to the public order page.

OVO (`ovo`):

Show an instruction to check the OVO app tied to the buyer phone number. The hosted success component does not render OVO through the same e-wallet QR component.

Card (`card`) and invoice/hosted payment (`invoice`):

Open the mapped `invoiceUrl` as a button/redirect. The hosted success page redirects automatically when `invoiceUrl` exists and `renderUrlAs` is `button`.

Alfamart and Indomaret:

Render `vaNumber` as `No Pembayaran`, show `vaAccountHolder` when present, and provide a copy action.

## Polling and status checks

For e-payment methods (`gopay`, `card`, `invoice`, `va`, `qris`, `ovo`, `dana`, `shopeepay`, `linkaja`, `alfamart`, and `indomaret`), the hosted success page waits for `pg_payment_info` to appear by refetching the public order up to 15 times with a 1.5 second delay.

For pending unpaid orders with `card`, `invoice`, `shopeepay`, `dana`, `qris`, `linkaja`, `gopay`, `bank_transfer`, `indomaret`, and `alfamart`, the hosted success page then refreshes the public order every 5 seconds while waiting for payment confirmation.

For `va`, `bank_transfer`, `qris`, `alfamart`, and `indomaret`, it also shows a manual "check payment status" action.

## Authenticated customer checkout

Customer checkout endpoints stay under:

```text theme={null}
/v3/stores/{store_id}/customers/me/checkout/*
```

They use `Authorization: Bearer <customer_access_token>`, not the Storefront API key. Addresses, payment methods, shipping options, and summary use the same checkout preparation fields as public checkout. Create the order with `POST /v3/stores/{store_id}/customers/me/checkout`; the request accepts direct typed `items`, `cart_id`, or both. The response uses the same public order shape returned by public checkout and public order reads, so every checkout completion response has the same order fields.

Customer checkout summary uses the selected courier service, warehouse, destination, payment method, and either direct `items` or the referenced customer cart to recompute shipping cost server-side. A client-supplied `shipping_cost` is only a compatibility value and is not the authoritative amount.
