Skip to content
Appearance

Payment status polling and reconciliation

Give checkout fast feedback without turning a browser clock into a payment verdict, then retrieve the complete record your order system can reconcile against.

Observe
POST /payment/status
Reconcile
POST /payment/detail
Interval
Every 2–3 seconds
Verdict
Server terminal status

Use the lightweight status route

The status endpoint is the observation channel for an active checkout; the detail endpoint is the durable record used afterward.

Send a store identifier plus either Bongluy's payment UUID inid or your owntranId. When both payment identifiers are present, id wins.

Read the current payment state
curl -X POST https://api.bongluy.com/payment/status \
  -H "Authorization: Bearer $BONGLUY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantStoreId": "branch-2",
    "tranId": "INV-1042"
  }'

The status response uses epoch milliseconds forexpireAt and at. The payment object returned by create and detail uses an ISO 8601 expiry, so do not parse the two response shapes as if they were identical.

Branch on four payment states

State names are stable machine values. Only a terminal value from the server decides whether payment succeeded or expired.

PENDING
Created and awaiting payment.
SUCCESS
Paid and settled.
EXPIRED
Expired without payment.
FAILED
Reserved for a future non-success terminal state.

Use a bounded polling loop

Poll every two or three seconds, stop when the state leaves PENDING, or pause when your checkout has waited long enough.

Stop polling at settlement or expiry
async function waitForPayment(payment, giveUpAfterMs = 10 * 60_000) {
  const deadline = Date.now() + giveUpAfterMs;

  while (Date.now() < deadline) {
    const result = await readStatus(payment.tranId);
    if (result.status !== "PENDING") return result.status;
    await new Promise((resolve) => setTimeout(resolve, 2500));
  }

  // Undecided, not unpaid. Reconcile later.
  return "PENDING";
}

Poll once per payment from your server or one active checkout, not from every browser tab. Authenticated routes are limited per API key, so duplicate loops waste capacity without making settlement arrive sooner.

Read detail for the durable record

After success, use POST /payment/detail to retrieve the full record required for receipts and order reconciliation.

The detail response contains the amount, currency, your transaction id, provider transaction reference, settlement time, QR, optional receipt and deeplinks, and timestamps. Match it to your order using the sametranId supplied during creation.

A store webhook can deliver the same terminal result without a polling loop. Keep this detail read as the durable reconciliation path for any expected webhook that never arrives.

Retry without creating a duplicate

A network timeout does not prove the original create failed, so retry the create request with the same store and transaction id.

Bongluy uses tranId as the idempotency key within a store. Reusing it resolves to the original payment; changing it requests a new payment. On authentication or rate-limit errors, fix the key or back off before polling again rather than replacing the transaction id.

Continue the integration

Read KHQR API guide next, or use the complete API reference for request fields, response shapes, limits, and error behavior.