Accepting a payment

A collection moves money from your customer into your balance: create it, show the next action, learn the result.

sequenceDiagram
  autonumber
  participant C as Customer
  participant S as Your server
  participant API as Partner API
  participant H as Your webhook endpoint
  C->>S: Checkout
  S->>API: POST /payments (direction: collection)
  API-->>S: 201 — state: created
  S->>API: GET /payments/{id}/next_action
  API-->>S: display_qr or redirect
  S->>C: Show QR / redirect
  C->>C: Pays in banking app
  API->>H: payment.succeeded
  H-->>API: 2xx
  S->>API: GET /payments/{id}
  API-->>S: state: succeeded
  S->>C: Order confirmed

1. Create

POST /v1/payments HTTP/1.1
Authorization: Bearer <your API key>
Idempotency-Key: order-4821-attempt-1
Content-Type: application/json

{
  "direction": "collection",
  "amount": "150.00",
  "currency": "MYR",
  "method": "duitnow",
  "country": "MY",
  "merchant_reference": "order-4821"
}
  • 201 → payment in state created. Store its id.
  • merchant_reference is yours, echoed on every read and event.
  • Available method values depend on your account and country.

2. Show the next action

  • Usually not in the create response. It appears once the payment is processing.
  • Fetch it: GET /payments/{id}/next_action.
  • 404 no_next_action while still created → retry in a second.
type Do Expires
display_qr Render fields.qr_payload as a QR code. Never as text. At expires_at (~1 min)
redirect Send the browser to url No
Anything else Show "action pending". New types get added. —

Keep the QR fresh

sequenceDiagram
  participant S as Checkout page
  participant API as Partner API
  S->>API: GET /payments/{id}/next_action
  API-->>S: QR A, expires 09:32:22
  S->>API: 09:32:15 — GET /payments/{id}/next_action
  API-->>S: QR B, expires 09:33:21
  • Re-fetch a few seconds before expires_at. Don't cache.

3. Learn the result

Event State Do
payment.succeeded succeeded Fulfil
payment.partial partial Handle shortfall (settled_total)
payment.failed failed Offer a retry (new payment)
  • Events can repeat and arrive out of order. On any event, GET /payments/{id} and act on state.
  • No event on expiry. No answer by your checkout timeout → read the payment.

4. Reconcile

  • settled_total = credited to you, after fee.
  • fee_amount = our fee.
  • settled_total + fee_amount = what the customer paid.

Troubleshooting

You see Do
Timeout on POST /payments Retry, same key
409 idempotency_conflict New payment → new key
404 no_next_action on created Retry in a second
502 processing_error on next_action Retry shortly
Still processing after your timeout Check later

Next: Sending a payout.