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

# Overview

> Generate one-time payment codes that customers pay in cash at Alfamart and Indomaret cashiers — each code recorded as a single fixed-amount Retail transaction.

Retail Outlet lets customers who prefer cash pay at thousands of **Alfamart** and **Indomaret** stores across Indonesia. You create a payment code through the API, give it to the customer, and the customer shows it to the cashier and pays the exact amount. Similar to QRIS, **each payment code belongs to one transaction and can only be paid once**.

<Info>
  Retail Outlet is currently offered to **selected merchants only**. To enable it for your merchant, contact your Singapay account manager. Create requests from merchants without access are rejected with `422`.
</Info>

<CardGroup cols={3}>
  <Card title="Cash payments" icon="money-bill-wave">
    Accept cash from customers who don't have a bank account, e-wallet, or card.
  </Card>

  <Card title="Nationwide coverage" icon="store">
    Customers pay at any Alfamart or Indomaret cashier — no app or registration needed.
  </Card>

  <Card title="Webhook on payment" icon="bell">
    Singapay notifies your `transaction_notif_url` as soon as the cashier confirms the payment.
  </Card>
</CardGroup>

***

## Payment flow

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant M as Merchant System
  participant S as SingaPay API
  participant C as Customer
  participant R as Retail Outlet

  M->>S: POST /retail-transactions/{account_id}
  Note over M,S: retail_code, amount, customer_name, expired_at
  S-->>M: payment_code, transaction_id (status: unpaid)

  M->>C: Show payment_code & payment instructions
  Note over M,C: Checkout page, email, or app

  C->>R: Show payment_code to the cashier
  C->>R: Pay the exact amount in cash
  R->>S: Payment confirmation

  S->>S: Update status (unpaid → paid)
  S-->>M: Webhook (event: retail-transaction)

  M->>S: GET /retail-transactions/{account_id}/{transaction_id}
  Note over M,S: Verify the final status (optional)
```

***

## Key difference: one code = one transaction

<Warning>
  Unlike Virtual Account or Payment Link, **each payment code is valid for one payment only**. Once it is paid or expires, it cannot be used again. Create a new payment code for every new transaction.
</Warning>

<CardGroup cols={2}>
  <Card title="Cannot do" icon="ban">
    * Reuse a payment code for another payment
    * Accept partial payments or overpayments
    * Change the amount or outlet after the code is created
    * Create a code without an expiry time
  </Card>

  <Card title="Must do" icon="circle-check">
    * Create a new payment code for each transaction
    * Set the exact amount and the outlet (`retail_code`) at creation
    * Set `expired_at` to a time in the future
    * Tell the customer which store to go to: Alfamart or Indomaret
  </Card>
</CardGroup>

***

## Supported outlets

<CardGroup cols={2}>
  <Card title="Alfamart" icon="store">
    `ALFAMART`

    <br />

    Payment code can only be paid at Alfamart cashiers.
  </Card>

  <Card title="Indomaret" icon="store">
    `INDOMARET`

    <br />

    Payment code can only be paid at Indomaret cashiers.
  </Card>
</CardGroup>

<Note>
  A payment code only works at the outlet you selected in `retail_code`. An Alfamart code cannot be paid at Indomaret, and an Indomaret code cannot be paid at Alfamart.
</Note>

***

## Transaction status

| Status    | Description                                                                   |
| --------- | ----------------------------------------------------------------------------- |
| `unpaid`  | Payment code created and waiting for the customer to pay at the cashier.      |
| `paid`    | The cashier confirmed the payment and funds were credited to your account.    |
| `expired` | `expired_at` passed before the customer paid. The code can no longer be paid. |
| `failed`  | The payment could not be completed.                                           |

<Note>
  Settlement is not a separate status. Check the `has_settle` and `settle_at` fields to see whether a `paid` transaction has been settled.
</Note>

***

## Recommended flow

<Steps>
  <Step title="Create a payment code">
    Call **Create Payment Code** with `retail_code`, `amount`, `customer_name`, and `expired_at`. Send your own order ID in `merchant_reff_no` so you can match the transaction later.
  </Step>

  <Step title="Save the transaction">
    Store the `transaction_id` and `payment_code` from the response. You need `transaction_id` to look up the transaction later.
  </Step>

  <Step title="Give the code to the customer">
    Show the `payment_code`, the amount, the outlet (Alfamart or Indomaret), and the expiry time to the customer.
  </Step>

  <Step title="Customer pays at the cashier">
    The customer tells the cashier they want to make a payment, shows the payment code, and pays the exact amount in cash.
  </Step>

  <Step title="Receive the webhook">
    Singapay sends a `retail-transaction` webhook to your `transaction_notif_url` when the transaction becomes `paid`. Update the order on your side.
  </Step>

  <Step title="Verify the status (optional)">
    Call **Show Transaction** to confirm the latest status, for example if you did not receive the webhook.
  </Step>
</Steps>

***

## Important notes

<AccordionGroup>
  <Accordion title="Amount limits" icon="scale-balanced">
    By default, the amount must be between **IDR 15,000** and **IDR 2,500,000** per transaction. Your merchant may have different limits based on its configuration. If the amount is outside the limit, the request is rejected with `422` and a message showing the allowed min or max.
  </Accordion>

  <Accordion title="Expiry time" icon="hourglass">
    `expired_at` is **required** and must be a Unix timestamp in **milliseconds** (13 digits) set to a future time. After it passes, an `unpaid` transaction changes to `expired` and the code can no longer be paid. If you subscribe to the [Transaction Money-In Expiration Webhook](/api-reference/webhooks/transaction-moneyin-expiration), expired Retail transactions are included in it.
  </Accordion>

  <Accordion title="Webhook notification" icon="bell">
    When a transaction becomes `paid`, Singapay sends a POST request to your `transaction_notif_url` with `event = retail-transaction`. This URL is also used by other money-in products, so route requests by the `event` field. See [Shared Webhook Endpoints](/api-reference/webhooks/shared-endpoints) and verify every request as described in [Security and Signature Validation](/api-reference/webhooks/security-and-signature).
  </Accordion>

  <Accordion title="No list or inquiry endpoint" icon="magnifying-glass">
    The API has two endpoints only: create and show. There is no separate inquiry-status endpoint — **Show Transaction** always returns the latest status. To browse or export Retail transactions, use the Singapay merchant dashboard.
  </Accordion>

  <Accordion title="Fees and settlement" icon="coins">
    A fee is charged per transaction and returned in `fees.amount`. The balance from a `paid` transaction follows your merchant's configured settlement method, the same as Virtual Account. Track settlement with the `has_settle` and `settle_at` fields.
  </Accordion>

  <Accordion title="Timestamp format" icon="clock">
    In API responses, `expired_at`, `processed_timestamp`, `post_timestamp`, and `settle_at` are Unix timestamps in **milliseconds**. `processed_timestamp` and `settle_at` stay `null` until the transaction is paid or settled.
  </Accordion>

  <Accordion title="Rate limit" icon="gauge">
    The **Create Payment Code** endpoint is rate-limited per merchant. If you send too many requests, you will receive `429 Too Many Requests`. Wait before you try again.
  </Accordion>
</AccordionGroup>

***

## Available endpoints

<CardGroup cols={2}>
  <Card title="Create Payment Code" icon="plus" href="./create-payment-code">
    Create a one-time Alfamart or Indomaret payment code for a fixed amount.
  </Card>

  <Card title="Show Transaction" icon="eye" href="./show-transaction">
    Get the details and latest status of a Retail transaction by its `transaction_id`.
  </Card>
</CardGroup>

<Note>
  All endpoints require authentication. See [Authentication](/api-reference/authentication).
</Note>
