> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thedecard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 扫码付（QR Pay）

> 扫码付（QR Pay）是 DeCard 托管模式独有的消费能力：decode → create → confirm 三步主流程 + order-list / order-detail 查询，扣款落在用户独立账户余额。

> **定位**：扫码付是**DeCard 托管独有**的消费能力。本页归入「交易管理」组。

## 扫码即付，扣的还是同一个余额

扫码付让持卡人在支持二维码收款的商户处，用**扫码**（而非刷实体卡 / Tap）完成消费。与刷卡授权一样，**扣款最终落在用户的独立账户余额**上——可用余额减少、总额减少（下文以 DCS 内部惯用简称 `free`（可用）/ `total`（总额）指代；API 层面 `free` 对应 `availableBalance`、冻结余额为 `frozenBalance`，参见 [账户与资产模型](../../basic-concepts/ledgering-system)），与刷卡走的是同一套。区别只在于「发起方式」：扫码付不经过卡网络的刷卡授权报文，而是 DCS 收单侧解码二维码、下单、再由您引导持卡人确认付款。

扫码付由 5 个接口组成，前缀统一为 `/qrpay/v1/`：支付主流程 **decode → create → confirm**，查询用 **order-list / order-detail**。

## 前置条件

* 用户已在 DeCard 托管下完成注册并通过 KYC，拥有 `externalUserId`（见 [注册用户](../signing-up-a-customer/overview)）。
* 用户已开卡，且 **独立账户有足额可用余额 `free`**（充值见 [用户余额](./user-balance)）。
* 调用方需带全站统一鉴权头（见 [鉴权指南](../../integration-resources/overview)）；下文示例省略鉴权头，仅示业务体。

<Note>
  全站响应结构统一为 `CommonRet`：`{ code, message, messageDetail{message,title,type,icon,action,linkTitle,linkUrl}, data }`，**无 `success` 布尔字段**；成功时 `code = SYS_SUCCESS`。下文每个接口的响应样例均套用此结构。
</Note>

***

## 支付主流程

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-qrpay-flow-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=5aff4f3e8dd7e4136e21e9e4c09a1fe8" alt="QR Pay 扫码支付流程" width="560" height="674" data-path="imgs/diagrams/va-qrpay-flow-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/diagrams/va-qrpay-flow-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=888df9d3656f9c9eeef585b083934c01" alt="QR Pay 扫码支付流程" width="560" height="674" data-path="imgs/diagrams/va-qrpay-flow-dark.svg" />
</Frame>

<Warning>
  `decode` 仅做解码与预览，**不扣款**；真正占用 / 扣减用户独立账户余额发生在 `confirm` 阶段。`create` 与 `confirm` 之间订单有 `expiryTime` 有效期（毫秒时间戳），超时需重新发起。
</Warning>

***

## 1. 解析二维码

**`POST /qrpay/v1/decode`** —— 解析二维码并返回订单预览（金额、商户、汇率、费用和限额），供应用展示给持卡人确认。

### 请求

| 字段               | 类型     | 必填 | 说明                | 约束        |
| ---------------- | ------ | -- | ----------------- | --------- |
| `externalUserId` | string | 是  | 外部用户 ID           | 最大 64 字符  |
| `qrCodeValue`    | string | 是  | 扫码值（二维码原文 / 付款码串） | 最大 512 字符 |
| `clientIP`       | string | 是  | 客户端请求 IP          | 最大 50 字符  |

```json theme={null}
{
  "externalUserId": "<external-user-id>",
  "qrCodeValue": "<qr-code-value>",
  "clientIP": "<client-ip>"
}
```

### 响应（`data`）

| 字段                          | 类型        | 说明                                                | 约束         |
| --------------------------- | --------- | ------------------------------------------------- | ---------- |
| `needCashier`               | boolean   | 是否需跳收银台（`true`=需要 / `false`=不需要）                  |            |
| `cashierExtensionInfo`      | string    | 收银台扩展信息                                           | 最大 1000 字符 |
| `orderId`                   | string    | 系统订单编号                                            | 最大 20 字符   |
| `acqCurrency`               | string    | 收单币种（如 SGD）                                       | 最大 3 字符    |
| `acqAmount`                 | number    | 收单金额                                              |            |
| `payCurrency`               | string    | 实际付款币种                                            | 最大 3 字符    |
| `payAmount`                 | number    | 实际付款金额                                            |            |
| `merchantName`              | string    | 收单商户名称                                            | 最大 128 字符  |
| `expiryTime`                | integer   | 订单有效期时间戳（毫秒）                                      |            |
| `qrCodeType`                | string    | 二维码类型                                             | 最大 20 字符   |
| `promoInfo`                 | object\[] | 优惠信息列表                                            | 见下         |
| `promoInfo[].promoName`     | string    | 优惠名称                                              | 最大 128 字符  |
| `promoInfo[].promoCurrency` | string    | 优惠币种                                              | 最大 3 字符    |
| `promoInfo[].promoAmount`   | number    | 优惠金额                                              |            |
| `sdkActionType`             | string    | SDK 动作类型（A+ 才有）：`HANDLE_BY_PSP` / `HANDLE_BY_SDK` | 最大 20 字符   |
| `sdkActionPayload`          | string    | SDK 动作负载（A+ `HANDLE_BY_SDK` 才有）                   | 最大 512 字符  |
| `rateInfo`                  | object    | 汇率信息（`baseCurrency` / `quoteCurrency` / `rate`）   |            |
| `usdAmount`                 | number    | 美元金额                                              |            |
| `feeAmount`                 | number    | 费用金额                                              |            |
| `feeCurrency`               | string    | 费用币种                                              | 最大 3 字符    |
| `minAmount`                 | number    | 最小金额（与商户收款币种一致）                                   |            |
| `maxAmount`                 | number    | 最大金额（与商户收款币种一致）                                   |            |
| `maxSingleAmount`           | number    | 单笔最大金额（与商户收款币种一致）                                 |            |
| `remainingAmount`           | number    | 用户当日剩余额度（与商户收单币种一致）                               |            |
| `cashierSession`            | string    | 收银台会话（`needCashier=true` 时返回）                     | 最大 32 字符   |
| `acqOrderNo`                | string    | 商户收单订单号                                           | 最大 32 字符   |
| `orderType`                 | string    | 订单类型（正扫 / 被扫）                                     | 最大 15 字符   |

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "",
  "messageDetail": null,
  "data": {
    "needCashier": false,
    "orderId": "<order-id>",
    "acqCurrency": "SGD",
    "acqAmount": 12.50,
    "payCurrency": "USD",
    "payAmount": 9.30,
    "merchantName": "<merchant-name>",
    "expiryTime": 1717855575000,
    "qrCodeType": "<qr-code-type>",
    "promoInfo": [],
    "rateInfo": { "baseCurrency": "SGD", "quoteCurrency": "USD", "rate": "0.744" },
    "usdAmount": 9.30,
    "feeAmount": 0.10,
    "feeCurrency": "USD",
    "minAmount": 0.01,
    "maxAmount": 5000.00,
    "maxSingleAmount": 2000.00,
    "remainingAmount": 1990.70,
    "acqOrderNo": "<acq-order-no>",
    "orderType": "POSITIVE_SCAN"
  }
}
```

***

## 2. 创建扫码订单

**`POST /qrpay/v1/create`** —— 在解码确认金额后下单，锁定订单。相比 `decode` 多传 `currency` 与 `amount`（用户输入金额的场景，如商户码不带金额）。

### 请求

| 字段               | 类型     | 必填 | 说明                   | 约束        |
| ---------------- | ------ | -- | -------------------- | --------- |
| `externalUserId` | string | 是  | 外部用户 ID              | 最大 64 字符  |
| `qrCodeValue`    | string | 是  | 扫码值                  | 最大 512 字符 |
| `clientIP`       | string | 是  | 客户端请求 IP             | 最大 50 字符  |
| `currency`       | string | 否  | 币种（如 SGD），商户码不带金额时需传 | 最大 10 字符  |
| `amount`         | string | 否  | 金额，商户码不带金额时需传        | 最大 20 字符  |

<Warning>
  示例中的 `currency` 写作「SGD」仅为示例值，实际按收单 / 付款场景传对应币种，不要硬编码 SGD。
</Warning>

```json theme={null}
{
  "externalUserId": "<external-user-id>",
  "qrCodeValue": "<qr-code-value>",
  "clientIP": "<client-ip>",
  "currency": "SGD",
  "amount": "12.50"
}
```

### 响应（`data`）

字段与 `decode` 的 `data` 基本一致（`orderId` / `acqCurrency` / `acqAmount` / `payCurrency` / `payAmount` / `merchantName` / `expiryTime` / `qrCodeType` / `promoInfo` / `sdkActionType` / `sdkActionPayload` / `rateInfo` / `usdAmount` / `feeAmount` / `feeCurrency` / `acqOrderNo` / `orderType`），不含 `needCashier` / `cashierSession` / 限额类字段。请以返回的 `orderId` 进入下一步确认。

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "",
  "messageDetail": null,
  "data": {
    "orderId": "<order-id>",
    "acqCurrency": "SGD",
    "acqAmount": 12.50,
    "payCurrency": "USD",
    "payAmount": 9.30,
    "merchantName": "<merchant-name>",
    "expiryTime": 1717855575000,
    "qrCodeType": "<qr-code-type>",
    "promoInfo": [],
    "sdkActionType": "",
    "sdkActionPayload": "",
    "rateInfo": { "baseCurrency": "SGD", "quoteCurrency": "USD", "rate": "0.744" },
    "usdAmount": 9.30,
    "feeAmount": 0.10,
    "feeCurrency": "USD",
    "acqOrderNo": "<acq-order-no>",
    "orderType": "POSITIVE_SCAN"
  }
}
```

***

## 3. 确认支付

**`POST /qrpay/v1/confirm`** —— 对已下单的 `orderId` 确认支付。此步**真正从用户独立账户扣款**（可用余额 `free` 不足将失败）。

### 请求

| 字段               | 类型     | 必填 | 说明                    | 约束       |
| ---------------- | ------ | -- | --------------------- | -------- |
| `externalUserId` | string | 是  | 外部用户 ID               | 最大 64 字符 |
| `orderId`        | string | 是  | 订单 ID（来自 `create` 返回） | 最大 50 字符 |

```json theme={null}
{
  "externalUserId": "<external-user-id>",
  "orderId": "<order-id>"
}
```

### 响应（`data`）

| 字段            | 类型     | 说明                 | 约束        |
| ------------- | ------ | ------------------ | --------- |
| `payStatus`   | string | 支付状态               | 最大 10 字符  |
| `transId`     | string | 交易流水 ID（可选）        | 最大 20 字符  |
| `bizTransId`  | string | 业务交易流水 ID（可选）      | 最大 32 字符  |
| `redirectUrl` | string | 付款完成跳转地址（可选，收银台场景） | 最大 256 字符 |

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "",
  "messageDetail": null,
  "data": {
    "payStatus": "SUCCESS",
    "transId": "<trans-id>",
    "bizTransId": "<biz-trans-id>",
    "redirectUrl": ""
  }
}
```

<Note>
  `confirm` 返回成功仅代表受理；若返回 `redirectUrl`（收银台 / 二次验证场景），需引导持卡人完成跳转。最终状态以 `order-detail` 的 `transList` 或 Webhook 为准。扣款引起的余额变动会经 `BALANCE_CHANGE` Webhook 通知——见 [Webhook 与 WebSocket 实时通知](../../integration-resources/webhook-websocket)。
</Note>

***

## 查询订单

### 4. 查询订单列表

**`GET /qrpay/v1/order-list`** —— 游标分页查询用户的扫码付订单列表。

| Query 参数         | 类型     | 必填 | 说明                               | 约束       |
| ---------------- | ------ | -- | -------------------------------- | -------- |
| `externalUserId` | string | 是  | 外部用户 ID                          | 最大 64 字符 |
| `cursorOrderId`  | string | 否  | 游标订单 ID（翻页用，传上一页最后一条的 `orderId`） |          |
| `limit`          | string | 否  | 单页数量，默认 `"30"`（query 参数传字符串形式）   |          |

```
GET /qrpay/v1/order-list?externalUserId=<external-user-id>&cursorOrderId=<cursor>&limit=30
```

响应 `data` 为**数组**，每项字段：

| 字段             | 类型      | 说明     | 约束        |
| -------------- | ------- | ------ | --------- |
| `orderId`      | integer | 系统订单编号 |           |
| `orderStatus`  | string  | 订单状态   | 最大 10 字符  |
| `acqCurrency`  | string  | 收单币种   | 最大 3 字符   |
| `acqAmount`    | number  | 收单金额   |           |
| `payCurrency`  | string  | 支付币种   | 最大 3 字符   |
| `payAmount`    | number  | 支付金额   |           |
| `createTime`   | string  | 创建时间   |           |
| `merchantName` | string  | 收单商户名称 | 最大 128 字符 |

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "",
  "messageDetail": null,
  "data": [
    {
      "orderId": 100000000000000001,
      "orderStatus": "SUCCESS",
      "acqCurrency": "SGD",
      "acqAmount": 12.50,
      "payCurrency": "USD",
      "payAmount": 9.30,
      "createTime": "2026-06-08 14:16:15",
      "merchantName": "<merchant-name>"
    }
  ]
}
```

> 游标分页：取本页最后一条的 `orderId` 作为下一次请求的 `cursorOrderId`；返回空数组表示已到末页。

### 5. 查询订单详情

**`GET /qrpay/v1/order-detail`** —— 查询单笔订单详情，含订单状态与底层交易流水列表 `transList`。

| Query 参数         | 类型     | 必填 | 说明      | 约束       |
| ---------------- | ------ | -- | ------- | -------- |
| `externalUserId` | string | 是  | 外部用户 ID | 最大 64 字符 |
| `orderId`        | string | 是  | 订单 ID   | 最大 50 字符 |

```
GET /qrpay/v1/order-detail?externalUserId=<external-user-id>&orderId=<order-id>
```

响应 `data` 字段（除 `decode`/`create` 共有的金额 / 商户 / 汇率 / 费用字段外，额外有）：

| 字段                        | 类型        | 说明                                                 | 约束        |
| ------------------------- | --------- | -------------------------------------------------- | --------- |
| `orderStatus`             | string    | 订单状态                                               | 最大 10 字符  |
| `createTime`              | string    | 交易时间                                               |           |
| `transList`               | object\[] | 交易列表（见下）                                           |           |
| `transList[].transId`     | integer   | 交易流水号                                              | 最多 20 位数字 |
| `transList[].transType`   | string    | 交易类型：`PAY` / `REFUND` / `REFUND_PART` / `REVERSAL` | 最大 20 字符  |
| `transList[].transStatus` | string    | 交易状态：`INIT` / `SUCCESS` / `FAILED`                 | 最大 10 字符  |
| `transList[].transTime`   | string    | 交易时间                                               | 最大 19 字符  |

一笔订单的底层流水可能不止一条（如先 `PAY` 后 `REFUND_PART`）。`transType` 与 `transStatus` 枚举：

| `transType`   | 含义   |
| ------------- | ---- |
| `PAY`         | 付款   |
| `REFUND`      | 全额退款 |
| `REFUND_PART` | 部分退款 |
| `REVERSAL`    | 冲正   |

| `transStatus` | 含义      |
| ------------- | ------- |
| `INIT`        | 处理中（初始） |
| `SUCCESS`     | 成功      |
| `FAILED`      | 失败      |

> `transList[].transId` 按接口定义为 integer；「20」表示最大数字位数，不是字符串长度。

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "",
  "messageDetail": null,
  "data": {
    "orderId": "<order-id>",
    "acqCurrency": "SGD",
    "acqAmount": 12.50,
    "payCurrency": "USD",
    "payAmount": 9.30,
    "merchantName": "<merchant-name>",
    "expiryTime": 1717855575000,
    "qrCodeType": "<qr-code-type>",
    "promoInfo": [],
    "sdkActionType": "",
    "sdkActionPayload": "",
    "rateInfo": { "baseCurrency": "SGD", "quoteCurrency": "USD", "rate": "0.744" },
    "usdAmount": 9.30,
    "orderStatus": "SUCCESS",
    "createTime": "2026-06-08 14:16:15",
    "transList": [
      {
        "transId": 200000000000000001,
        "transType": "PAY",
        "transStatus": "SUCCESS",
        "transTime": "2026-06-08 14:16:15"
      }
    ],
    "feeAmount": 0.10,
    "feeCurrency": "USD",
    "acqOrderNo": "<acq-order-no>",
    "orderType": "POSITIVE_SCAN"
  }
}
```

***

## 错误处理

* 非成功时 `code ≠ SYS_SUCCESS`，`message` / `messageDetail` 给出可读信息——以 `data` 业务结果与 `code` 判定，勿仅看 HTTP 200。
* 常见失败原因：用户独立账户 `free` 余额不足（`confirm` 阶段）、订单过期（超 `expiryTime`）、二维码非法 / 不支持（`decode` 阶段）、金额超出 `minAmount`/`maxAmount`/`maxSingleAmount`/当日剩余额度 `remainingAmount`。
* `confirm` 返回受理后仍可能异步失败，最终以 `order-detail` 的 `transList[].transStatus` 或 Webhook 为准。
* 完整错误码字典请向 DCS 团队索取。

## 与独立账户余额模型的关系

扫码付与刷卡消费共享同一套用户独立账户：扣款发生在 `confirm`，最终落在用户 **独立账户**（`free` 可用余额）。（`free` / `total` 并非本接口直接返回，这些余额字段来自账户模型接口——如 `/user-asset/v1/balance` 等，其中 `free` 对应 API 字段 `availableBalance`、冻结余额对应 `frozenBalance`，详见 [用户余额](./user-balance) 与 [账户与资产模型](../../basic-concepts/ledgering-system)。）因此发起扫码付前，请确保用户账户已通过充值备足余额。

## 下一步

* 账户与资产模型（`free` / `freeze` / `total`）：[账户与资产模型](../../basic-concepts/ledgering-system)
* 查询 / 调整用户余额：[用户余额](./user-balance)
* 接收支付结果 / 余额变动通知：[Webhook 与 WebSocket 实时通知](../../integration-resources/webhook-websocket)
* 鉴权头与白名单：[鉴权指南](../../integration-resources/overview)
