Scope: QR Pay is a spending capability available only in the DeCard-Managed model. This page belongs to the Managing Transactions group.
Scan, pay, and the same balance is debited
QR Pay lets a cardholder pay by scanning a code instead of swiping or tapping a physical card, at any merchant that accepts QR payments. Just as with a card authorization, the debit ultimately lands on the user’s DeCard-Managed account balance: the available balance falls and the total falls with it (referred to below by the internal DCS shorthandfree and total; at API level free maps to availableBalance, while the held portion is frozenBalance, see Account and Asset Model). It is the same balance a card payment draws on. The only difference is how the payment starts: QR Pay does not travel through a card authorization message on the card network. Instead the DCS acquiring side decodes the code and creates the order, and you then guide the cardholder to confirm the payment.
QR Pay consists of 5 endpoints, all under the /qrpay/v1/ prefix: the main payment flow of decode, create and confirm, plus order-list and order-detail for queries.
Prerequisites
- The user is registered in the DeCard-Managed model, has passed KYC and holds an
externalUserId(see Signing Up a Customer). - The user holds a card and their DeCard-Managed account holds enough available balance (
free) (for top-ups, see User Balance). - Callers must send the site-wide authentication headers (see the Authentication Guide); the examples below omit those headers and show only the business payload.
The site-wide response envelope is
CommonRet: { code, message, messageDetail{message,title,type,icon,action,linkTitle,linkUrl}, data }, with no success boolean; on success, code = SYS_SUCCESS. Every sample response below uses this envelope.The payment flow
1. Decode the QR code
POST /qrpay/v1/decode decodes the QR code and returns an order preview (amount, merchant, exchange rate, fee, limits) for your app to show the cardholder for confirmation.
Request
Response (data)
2. Create the order
POST /qrpay/v1/create creates and locks the order once the amount has been decoded and confirmed. Compared with decode it also accepts currency and amount, for the case where the user enters the amount because the merchant code carries none.
Request
Response (data)
The fields are largely the same as the data returned by decode (orderId / acqCurrency / acqAmount / payCurrency / payAmount / merchantName / expiryTime / qrCodeType / promoInfo / sdkActionType / sdkActionPayload / rateInfo / usdAmount / feeAmount / feeCurrency / acqOrderNo / orderType), minus needCashier, cashierSession and the limit fields. Carry the returned orderId into the confirmation step.
3. Confirm the payment
POST /qrpay/v1/confirm confirms payment for an orderId that has already been created. This step actually debits the user’s DeCard-Managed account and fails if the available balance (free) is insufficient.
Request
Response (data)
A successful
confirm means the payment has been accepted, not completed. If a redirectUrl comes back (the cashier or step-up verification scenario), send the cardholder through it. The final state comes from transList in order-detail or from the webhook. The balance movement caused by the debit is notified through the BALANCE_CHANGE webhook, see Webhook & WebSocket.Querying orders
4. Order list
GET /qrpay/v1/order-list returns a user’s QR Pay orders with cursor pagination.
data comes back as an array, where each item carries:
Cursor pagination: take theorderIdof the last item on this page as thecursorOrderIdof the next request; an empty array means you have reached the last page.
5. Order detail
GET /qrpay/v1/order-detail returns one order in detail, including its status and the underlying transaction list transList.
decode and create, data also carries:
One order can have more than one underlying transaction, for instance a
PAY followed by a REFUND_PART. The transType and transStatus enumerations:
transList[].transId is defined as an integer in the API; the “20” is the maximum number of digits, not a string length.
Error handling
- When a call does not succeed,
code ≠ SYS_SUCCESSandmessageandmessageDetailcarry readable information. Judge the outcome fromcodeand the business result indata, never from an HTTP 200 alone. - Common failure causes: insufficient
freebalance on the user’s DeCard-Managed account (atconfirm), an expired order (pastexpiryTime), an invalid or unsupported QR code (atdecode), and an amount outsideminAmount,maxAmount,maxSingleAmountor the remaining daily allowanceremainingAmount. - A
confirmthat returns as accepted can still fail asynchronously; the final state comes fromtransList[].transStatusinorder-detailor from the webhook. - For the complete error code dictionary, ask the DCS team.
How this relates to the DeCard-Managed balance model
QR Pay and card spending share the same per-user account: the debit happens atconfirm and lands on the user’s DeCard-Managed account (the free available balance). (free and total are not returned by these endpoints; those balance fields come from the account model APIs such as /user-asset/v1/balance, where free maps to availableBalance and the held portion to frozenBalance. See User Balance and Account and Asset Model (Ledgering).) Make sure the user account has been topped up before starting a QR Pay.
Next steps
- Account and asset model (
free/freeze/total): Account and Asset Model (Ledgering) - Reading and adjusting user balances: User Balance
- Receiving payment result and balance change notifications: Webhook & WebSocket
- Authentication headers and IP allowlisting: Authentication Guide

