QR Scan-to-Pay¶
Tip
Scan-to-Pay lets users scan an offline recipient QR code in the merchant app. The merchant server parses the QR code and submits a payout using the returned resolveToken, providing an in-app QR payment experience similar to a wallet app.
The product flow is:
Scan -> Parse -> Confirm -> Pay
- Scan: The user scans the recipient's QR code in the merchant app.
- Parse: The merchant server calls the QR Code Parsing API to retrieve the recipient details and a
resolveToken. - Confirm: The app displays the recipient name, amount, and other payment details for the user to confirm.
- Pay: The merchant server calls the QR Payout API with the
resolveTokento submit the payout.
1. QR Code Parsing¶
Call this API to parse the QR code before submitting a Scan-to-Pay transaction.
Request URL¶
/v1/vexora/qr/parse
Request Method¶
- Method: POST
- Content-Type: application/json
Header¶
| Header | Required | Type | Description |
|---|---|---|---|
| merchantNo | Yes | String | Merchant number |
Request Body Parameters¶
| Parameter Name | Required | Type | Description |
|---|---|---|---|
| sign | Yes | String | Sort all body fields except sign alphabetically by field name and concatenate them as key1=value1key2=value2. Use the app secret as the salt for MD5 hashing. The resulting signature must be lowercase. |
| timestamp | Yes | String | Request timestamp in milliseconds, for example 1715941383720. |
| qrType | Yes | String | QR code type. Use KHQR. |
| qrContent | Yes | String | QR code content. |
| scanSource | No | String | Scan source: CAMERA or ALBUM. This field is for source identification only. |
Request Parameter Example¶
{
"sign": "363b4674a1c1772e8a50295dc19d6727",
"timestamp": "1732988442913",
"qrType": "KHQR",
"qrContent": "000201010212...",
"scanSource": "CAMERA"
}
Response Result¶
| Parameter Name | Required | Type | Description |
|---|---|---|---|
| code | Yes | String | Response code. 0000 indicates that the QR code parsing request succeeded. |
| msg | Yes | String | Request result message. |
| success | Yes | Boolean | Indicates whether the API request was processed successfully. |
| timestamp | Yes | Number | Response timestamp in milliseconds. |
| data | Yes | Object | Parsed QR code details. |
| data.payable | Yes | Boolean | Indicates whether the QR code can be paid. |
| data.qrType | Yes | String | QR code type, for example KHQR. |
| data.qrMode | Yes | String | QR code mode: STATIC or DYNAMIC. |
| data.merchantType | Yes | String | Recipient type: INDIVIDUAL for a personal QR code or MERCHANT for a merchant QR code. |
| data.resolveToken | Yes | String | Identifier for the parsed QR payment details. Use this token when submitting the QR Payout request. |
| data.accountName | Yes | String | Recipient name displayed to the user. |
| data.accountBank | Yes | String | Recipient bank or payment provider name. |
| data.amount | Conditional | String | Returned when the QR code contains an amount. If present, the payout amount must match it. |
| data.currency | Yes | String | Account currency. |
| data.qrExpireAt | Yes | String | QR code expiration time, in Cambodia local time or the format returned by the API. |
| data.expireAt | Yes | String | Expiration time of the resolveToken, in Cambodia local time. |
| data.message | Yes | String | Result message or explanation. |
Note
For QR code security, every resolveToken expires after 600 seconds. Parsing the same QR code again generates a new token; an earlier token remains usable until it expires, unless it has already been used to create a payout order. If the QR code expires sooner than 600 seconds, the token expires with the QR code.
Successful Response Example: Dynamic QR¶
{
"code": "0000",
"data": {
"accountBank": "LantonPay",
"accountName": "LAN TON",
"amount": "0.1",
"currency": "USD",
"expireAt": "2026-11-03 12:31:14",
"merchantType": "INDIVIDUAL",
"message": "Success.",
"payable": true,
"qrExpireAt": "1793680274716",
"qrMode": "DYNAMIC",
"qrType": "KHQR",
"resolveToken": "SCAN5EDBA8542BB34A538E8D8C4D6FAE5B7B"
},
"msg": "success",
"success": true,
"timestamp": 1793679696292
}
Successful Response Example: Static QR¶
{
"code": "0000",
"data": {
"sign": "363b4674a1c1772e8a50295dc19d6727",
"timestamp": "1732988442913",
"resolveToken": "SCAN202609250001",
"accountBank": "Lanton Pay",
"qrType": "KHQR",
"qrMode": "STATIC",
"merchantType": "MERCHANT",
"accountName": "ABC Merchant",
"currency": "KHR",
"payable": true,
"expireAt": "2026-09-26 20:10:00",
"qrExpireAt": "2026-09-26 20:10:00"
},
"msg": "success",
"success": true,
"timestamp": 1793679696292
}
Failed Response Example¶
{
"msg": "success",
"code": "0000",
"timestamp": 1793680461168,
"success": true,
"data": {
"payable": false,
"qrType": "KHQR",
"resolveToken": "SCAN55D6D7B3CD92434A8211D968EC230984",
"expireAt": "2026-11-03 12:44:19",
"message": "QR code is not valid"
}
}
In this example, the parsing request succeeded, but the QR code is not payable. Check data.payable before allowing the user to continue.
2. QR Payout¶
After retrieving and confirming the QR code details, submit a payout using the resolveToken returned by the QR Code Parsing API.
Note
A resolveToken can be used to successfully create only one payout order.
Request URL¶
/v1/vexora/qr/payOut
Request Method¶
- Method: POST
- Content-Type: application/json
Header¶
| Header | Required | Type | Description |
|---|---|---|---|
| merchantNo | Yes | String | Merchant number |
Request Body Parameters¶
| Parameter Name | Required | Type | Description |
|---|---|---|---|
| sign | Yes | String | Sort all body fields except sign alphabetically by field name and concatenate them as key1=value1key2=value2. Use the app secret as the salt for MD5 hashing. The resulting signature must be lowercase. |
| timestamp | Yes | String | Request timestamp in milliseconds, for example 1715941383720. |
| tradeNo | Yes | String | Unique merchant order identifier. A date/time string followed by random digits is recommended. |
| userId | Yes | String | Unique identifier for the user in the merchant system. |
| resolveToken | Yes | String | Token returned by the QR Code Parsing API. It links the payout to the parsed QR payment details. |
| amount | Yes | Float | Payout amount. Required for STATIC QR codes. For DYNAMIC QR codes with a parsed amount, this value must match the parsed amount. |
| notifyUrl | Yes | String | Asynchronous notification URL. |
| remark | Yes | String | Remark returned unchanged in the response. |
Request Parameter Example¶
{
"sign": "363b4674a1c1772e8a50295dc19d6727",
"timestamp": "1732988442913",
"resolveToken": "SCAN202609250001",
"tradeNo": "39fa354542824ee789faf526f27c024f",
"userId": "6bdsjk435n67iuw7633",
"amount": 500,
"notifyUrl": "https://merchant.example/callback",
"remark": "test"
}
Response Result¶
| Parameter Name | Required | Type | Description |
|---|---|---|---|
| msg | Yes | String | Request result. A value of success only indicates that the API request succeeded; it is not the final payout result. |
| code | Yes | String | API response code. 0000 only indicates that the request succeeded. Refer to the business error code documentation for other codes. |
| timestamp | Yes | Number | Response timestamp in milliseconds. |
| success | Yes | Boolean | Indicates whether the API request was processed successfully. |
| data | Yes | Object | Payout response object. |
| data.tradeNo | Yes | String | Merchant order identifier. |
| data.platFormTradeNo | Yes | String | Unique platform transaction number. |
| data.status | Yes | String | Payout status: 0000 successful, 0015 processing, or 00029 failed. |
| data.message | Yes | String | Status description or error message. |
| data.remark | Yes | String | Remark returned from the request. |