Integration Guide
QR Flow
How the gateway requests a QR, what the customer sees, and how settlement is detected.
The QR flow lets the customer pay from their own banking app without typing anything into the gateway. PayNow renders a QR; the customer scans, confirms, and the gateway's status poller flips to the receipt.
Sequence
Browser Merchant SSR Gateway SSR OnePay
│ │ │ │
│ POST /api/initiate-payment │ │
│──────────────────────▶│ │ │
│ │ POST /web-payment/initiate │
│ │────────────────────────────────▶ │
│ │ ◀─── { paymentId, token } ───────│
│ ◀── { redirectUrl } ─│ │ │
│ │
│ navigate(redirectUrl) │
│─────────────────────────────────────▶│ │
│ │ POST /merchant-qr │
│ │─────────────────▶│
│ │ ◀── { qrCode } ─ │
│ ◀── HTML with QR + status poller ───│ │
│ │
│ (customer scans + confirms in their banking app) │
│ │
│ POST /check-status (every 3s, then 10s) │
│─────────────────────────────────────▶│ │
│ │ POST /check-status │
│ │─────────────────▶│
│ │ ◀── { paymentStatus: SUCCESS, ... } │
│ ◀── transition to receipt ──────────│ │
What the gateway calls
| Step | Endpoint | Method | Purpose |
|---|---|---|---|
| 1 | /web-payment/initiate |
POST | (Merchant server) Create the payment session. Returns paymentId + bearer JWT. |
| 2 | /web-payment/{paynow,one-pay}/merchant-qr |
POST | (Gateway) Generate the QR payload bound to the session. |
| 3 | /web-payment/check-status |
POST | (Gateway, polling) Read settlement status. |
State transitions
The session moves through pending → qrGenerated → waitingPayment → success (terminal). Failed scans go to failed; idle sessions go to expired. The PaymentStateMachine enforces these transitions; arbitrary jumps throw PaymentStateTransitionException.
There are two non-obvious "fast-path" transitions the state machine accepts:
qrGenerated → success— backend settles before the gateway ever seeswaitingPayment(e.g. the webhook fires fast).qrGenerated → otpRequired— customer abandons QR, switches to the account tab.
Polling cadence
The status poller activates as soon as the page reaches qrGenerated and runs against /web-payment/check-status with this schedule:
- 3 seconds for the first 30 seconds.
- 10 seconds after that.
- Hard cap of 5 minutes.
It self-stops on success / failed / expired. See Status Polling for the request body shape and how to override the schedule.
What the customer sees
- The amount, merchant name, and a single "Scan to pay" panel with the QR.
- A subtle "Awaiting confirmation" message under the code.
- Receipt screen with transaction ID and timestamp once
/check-statusreportsSUCCESS.
The "Refresh status" button stays available throughout — it triggers a one-shot /check-status call, identical to what the poller does on its own. Most users never need it.