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.