The complete decode → quote → pay sequence, including how to handle
processing.
Decode, Quote, and Pay
Read Configuration When the Screen Opens
GET /v1/qrpay/config
Whether this member can use QR payments, which currencies they can pay with, each balance, and the limits. When available: false, reason explains why, and assets is empty.
⚠ assets is filtered by the server and ordered by the member's payment preferences— the first entry is the default debit asset. You do not specify the debit currency: preferred_asset means only “try this first”; valid values are the assets[].asset entries here.
⚠ require_step_up is always true: every payment requires end-user confirmation on our hosted page. Include that step in your checkout design rather than waiting for POST /v1/qrpay/payments to return 400.
Decode
POST /v1/qrpay/decode { "code_value": "<扫到的字符串>" }
Returns the QR scheme, recipient, and whether the amount is fixed or must be entered by the user.
Unrecognized means unrecognized: an error is returned. Do not implement fuzzy matching— a wrong guess could pay another merchant.
Quote
POST /v1/qrpay/quotes
Returns one quote for each payable asset held by the member.
⚠ Call this first to build the confirmation page. Calling payment directly means the user sees the amount
for the first time only after funds have been locked.
Pay
POST /v1/qrpay/payments
{
"code_value": "<同一张码>",
"currency": "THB", ← 只在自定义金额码上传
"amount": "350.00", ← 同上
"preferred_asset": "USDT" ← 可选
}
⚠ There is no
quote_idor payment authorization ticket. This endpoint obtains a fresh quote itselfbefore locking funds. The actual debit may differ from the preceding quote by a few smallest units;
the payment response is authoritative.
Three Parameter Pitfalls
| Parameter | Key point |
|---|---|
currency / amount | Send only for user-entered-amount codes, in the acquirer-side fiat currency. Supplying these for a fixed-amount code changes the merchant's price; we forward them upstream, which rejects a mismatch |
preferred_asset | Only means “try this first.” An asset the upstream cannot quote this time is rejected, without silently switching |
⚠ You do not specify the debit currency. We select it using the member's payment preference order.
Silently debiting another currency makes the decision for the user.
processing Means an Unknown Result, Not Failure
This is the most important part of this flow.
If the network fails while notifying the upstream, the request may already have arrived and payment may already have been approved— so we never unfreeze funds at that point. We record processing for verification and reconciliation.
⚠⚠ You must wait for a webhook or poll by order ID.
Do not declare failure or automatically create another order. Doing so can charge twice for one purchase.
Three Failure Types and How to Handle Them
| Response | Meaning | Your action |
|---|---|---|
| 4xx business error | Definitive failure; funds untouched | Show a human-readable message based on code |
upstream_error | Upstream 5xx / network failure | Funds have not moved in this case; retry unchanged |
| 504 / network error | Unknown result | Do not treat as failure—retry with the same key or query the order |
⚠ The last case is critical. A network failure on
paymentsdoes not mean payment failed—we may already have locked funds and notified the upstream. Telling the user “Payment failed, try again” can make them pay twice.
The Two Amounts Differ in Both Value and Currency
The amount we send upstream is the original text of the upstream quote, not the member's total payment (actual payment = upstream quote + our cost protection + markup + fee, and is naturally higher).
Therefore, customer_total (the digital asset paid by the member) and acquirer_amount (fiat received by the recipient) are not directly comparable.