Skip to main content
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 shorthand free 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

QR Pay payment flowQR Pay payment flow
decode only decodes and previews, and debits nothing; the user’s balance is only held or debited at the confirm step. Between create and confirm the order is valid until its expiryTime (a millisecond timestamp), after which you have to start again.

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

The currency value “SGD” in the example is illustrative only. Send the currency that matches your acquiring or payment scenario, and do not hard-code SGD.

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

externalUserId is the external user ID, the same as on every other endpoint on this page.

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 the orderId of the last item on this page as the cursorOrderId of 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.
Beyond the amount, merchant, exchange rate and fee fields shared with 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_SUCCESS and message and messageDetail carry readable information. Judge the outcome from code and the business result in data, never from an HTTP 200 alone.
  • Common failure causes: insufficient free balance on the user’s DeCard-Managed account (at confirm), an expired order (past expiryTime), an invalid or unsupported QR code (at decode), and an amount outside minAmount, maxAmount, maxSingleAmount or the remaining daily allowance remainingAmount.
  • A confirm that returns as accepted can still fail asynchronously; the final state comes from transList[].transStatus in order-detail or 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 at confirm 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