你是一名资深支付系统对接工程师。请帮我对接下面这套「商户 OpenAPI」。

对接要求：
1. 用我指定的编程语言写出可直接运行的对接代码；若我未指定语言，请先问我（常用 Node.js / PHP / Python / Go / Java）。
2. 严格实现签名 sign（资金安全关键，务必逐条遵守）：
   - 算法 HMAC-SHA256，结果转小写 16 进制；
   - 参与签名的字段 = 请求体顶层除 `sign` 外、且值不为 null 的所有字段；
   - 按字段名 ASCII 升序排序，拼成 `key=value`，用 `&` 连接，末尾再追加 `&secret=<对应业务密钥>`；
   - 值为 object/array 的字段（如 `extra`）先做「key 升序的紧凑 JSON」稳定序列化，再作为该字段的 value 参与；
   - 代收用 api_secret_pay 签名，代付用 api_secret_payout 签名。
3. 覆盖代收下单 `pay/create` 与查询 `pay/query`；若我需要代付，再加 `payout/create` 与 `payout/query`。
4. 金额为放大 10000 倍的整数（精度 0.0001，例如 1.2345 传 12345）；统一返回结构为 `{ code, message, data }`，请处理文档中列出的常见错误码。
5. 接入地址与凭据一律用占位符表示，我会自行替换（Base URL 请从我的商户后台「安全中心 · OpenAPI」获取）：`<BASE_URL>`、`<YOUR_MERCHANT_NO>`、`<YOUR_API_KEY>`、`<YOUR_API_SECRET_PAY>`、`<YOUR_API_SECRET_PAYOUT>`。

产出要求：先用要点列出对接步骤，再给出完整可运行代码，并附一个「签名计算 + 下单」的最小可运行示例。

以下是完整接口文档（端点、字段、签名规则与官方 SDK 入口均在其中，请以此为准）：

=== 接口文档开始 ===
# 商户 OpenAPI 接入文档

- Base URL：请登录商户后台「安全中心 · OpenAPI」获取你的实际接入地址（本文示例中以 `<BASE_URL>` 占位）。
- 接口版本：`v1`（服务端版本 1.2.0；每次响应都带 `X-Api-Version` 头）
- 导出日期：20260710

> 注意：本文档不包含商户真实凭据与白名单信息，请使用占位符替换后对接。

## 版本与兼容策略

- 大版本体现在路径前缀：当前为 `/api/open/v1`。
- v1 仅做**向后兼容**的演进（新增可选字段、修复缺陷），不会改变已有字段含义或收紧已有校验。
- 若有破坏性变更，会新增 `/api/open/v2` 并保留 v1 一段时间，商户可平滑迁移。
- 可调用 `GET <BASE_URL>/version` 查询当前服务端版本（无需鉴权）。

**本次变更（1.2.0）**：
- 代收对外 `status` 不再返回 `expired`：超时未支付统一归一为 `pending`（处理中），请以查单结果为准。
- 代收仅在订单成功（`success`）时回调商户；失败/超时等非成功状态不回调，商户应通过查单接口获知最终结果（代付不受此限制）。

## 商户信息

- 商户名称：<YOUR_MERCHANT_NAME>
- 商户号：<YOUR_MERCHANT_NO>

## 官方 SDK

提供 5 种语言的官方 SDK：零第三方依赖（仅用各语言标准库），内置 HMAC-SHA256 请求签名 / 回调验签、测试与正式双环境（SANDBOX 本地 / PRODUCTION 传你的专有域名 `baseUrl`）、覆盖全部接口，且跨语言签名逐字节一致。**推荐直接使用 SDK 完成对接，无需自行实现签名与稳定序列化。**

| 语言 | 安装 / 引入 | 包索引 / 源码 |
|------|------|------|
| Node.js | `npm i @bebebus/merchant-openapi-sdk` | [npm](https://www.npmjs.com/package/@bebebus/merchant-openapi-sdk) |
| Python | `pip install bebebus-merchant-openapi-sdk` | [PyPI](https://pypi.org/project/bebebus-merchant-openapi-sdk/) |
| PHP | `composer require bebebus/merchant-openapi-sdk` | [Packagist](https://packagist.org/packages/bebebus/merchant-openapi-sdk) |
| Go | `go get github.com/bebebus/SDK/go` | [pkg.go.dev](https://pkg.go.dev/github.com/bebebus/SDK/go) |
| Java | 源码引入（包 `cloud.cniia.openapi.sdk`，未发 Maven） | [GitHub](https://github.com/bebebus/SDK/tree/main/java) |

- 仓库与发布说明：https://github.com/bebebus/SDK （签名算法权威说明见仓库 `SIGNING.md`，端点字段级请求/响应见 `INTERFACES.md`）。

## 代收：创建订单（pay/create）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/pay/create`
- 用途：创建代收订单，返回接口订单号与支付链接/二维码内容。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、out_order_no、amount、currency、pay_method、notify_url
- 可选字段：nonce、country、return_url、subject、remark、client_ip、extra

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| out_order_no | string | 是 | 商户订单号（幂等键；同一商户不可重复） |
| amount | int | 是 | 订单金额（= 实际金额 × 10000 的整数，例如 1.2345 传 12345）。必须是 JSON 数字正整数，不接受字符串（如 "12345"）或小数；非法返回 code=100001（HTTP 200，message 为具体字段校验错误信息）。 |
| currency | string | 是 | 币种（如 PHP/USDT；用于校验与路由） |
| pay_method | string | 是 | 支付方式（从支付方式字典中选择，如 gcash/maya；加密货币使用链名如 trc20/erc20） |
| country | string\|null | 否 | 国家 ISO 码（如 PH；法币必填，加密货币可省略） |
| notify_url | string | 是 | 回调地址（订单进入终态后，接口会向该地址发送结果） |
| return_url | string\|null | 否 | 前端回跳地址（支付完成后跳回商户页面；是否跳转取决于支付方式/场景） |
| subject | string\|null | 否 | 订单标题（可用于展示或对账说明） |
| remark | string\|null | 否 | 备注（可用于商户自定义说明/对账） |
| client_ip | string\|null | 否 | 终端用户 IP（可用于风控；不传则接口无法获取该信息） |
| extra | object\|null | 否 | 扩展字段（顶层字段，参与签名；object 会按稳定 JSON 序列化）。⚠️ 强烈建议携带下单用户个人信息 extra.customer（见下方"扩展信息与渠道要求"说明） |
| extra.customer | object | 否 | 下单用户信息（选填）。未提供时，接口不会仅因缺少该字段返回 300405；但部分支付方式要求 name/phone，缺失可能导致订单失败，建议提供。提供后接口会校验格式并转换字段（整名 name 会拆分为 first/last）：可填 name 整名（或 first_name+last_name）、email、phone |
| sign | string | 是 | 签名（按文档规则计算；pay 接口使用 api_secret_pay） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "amount": 10000,
  "currency": "PHP",
  "pay_method": "gcash",
  "country": "PH",
  "notify_url": "https://merchant.example.com/api/notify/pay",
  "return_url": "https://merchant.example.com/pay/result",
  "subject": "订单标题（可选）",
  "remark": "备注（可选）",
  "client_ip": "1.2.3.4",
  "extra": {
    "user_id": "u123456",
    "customer": {
      "first_name": "San",
      "last_name": "Zhang",
      "email": "user@example.com",
      "phone": "13800000000"
    }
  }
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| order_no | string | 是 | 接口订单号（用于后续查询与对账） |
| out_order_no | string | 是 | 商户订单号（原样回传，便于商户侧关联业务订单） |
| amount | int | 是 | 订单金额（= 实际金额 × 10000 的整数，例如 12345 表示 1.2345） |
| currency | string | 是 | 币种（原样回传请求币种，如 PHP/USDT） |
| pay_url | string\|null | 否 | 支付链接（下单成功 code=0 时，pay_url/qrcode_content/pay_params 至少一项非空；本字段可能为空，此时用其余字段唤起支付） |
| qrcode_content | string\|null | 否 | 二维码内容（下单成功 code=0 时，pay_url/qrcode_content/pay_params 至少一项非空；本字段可能为空） |
| pay_params | string\|null | 否 | 支付原生串（可选）：部分支付方式返回 App 原生唤端参数；pay_url 为空而本字段非空时，请用它在客户端唤起支付 |
| expire_at | string\|null | 否 | 过期时间（ISO8601；可空）。超时未支付不再单独表示为 expired 状态，对外统一归一为 pending（处理中），请以查单结果为准。 |
| status | string | 是 | 订单状态：pending（处理中）/success（成功）/failed（失败）。仅 success/failed 为终态；超时未支付不再单独返回 expired，统一归一为 pending。 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "currency": "PHP",
    "pay_url": "https://pay.example.com/h5/P20250101000001",
    "qrcode_content": "https://pay.example.com/h5/P20250101000001",
    "pay_params": null,
    "expire_at": "2025-01-01T12:30:00Z",
    "status": "pending"
  }
}
```

### 补充说明

- 金额 = 实际金额 × 10000 的整数，例如 1.2345 传 12345，以整数传输避免浮点误差。
- 幂等键：同一商户的 out_order_no 全局唯一；重复下单参数一致视为幂等成功，参数不一致返回幂等冲突。
- 订单未被受理时接口实时返回 code=100000 且订单置 failed 终态；此时不可用相同 out_order_no 重试（幂等只会返回该 failed 单），需换新 out_order_no 重新下单。
- 【用户信息（强烈建议必传）】部分支付方式要求终端用户姓名/手机（email 可能选填），缺失可能导致订单失败。请使用通用字段提供：姓名——extra.customer.name 整名（也可显式 extra.customer.first_name+last_name）；邮箱——extra.customer.email（或 extra.email）；手机——extra.customer.phone（或 extra.phone）。提供后接口会统一校验并转换字段，商户无需为不同支付方式分别适配。
- 【支付原生串】pay_url 为空而 pay_params 非空时（部分支付方式仅返回原生唤端参数），请在客户端用 pay_params 唤起支付。

### 常见错误

- 100101 请求过期
- 100102 IP 不在白名单
- 100104 签名错误
- 100105 IP 在黑名单
- 100000 创建订单失败（订单未被受理，已实时置 failed）
- 300404 当前条件下无可用支付方式
- 100001 参数校验失败（如 notify_url 指向内网/非法协议）
- 300402 支付方式配置不可用
- 300403 费率未配置

## 代收：查询订单（pay/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/pay/query`
- 用途：按接口订单号或商户订单号查询代收订单状态。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、order_no/out_order_no（二选一）
- 可选字段：nonce

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| order_no | string\|null | 否 | 接口订单号（与 out_order_no 二选一，至少提供一个） |
| out_order_no | string\|null | 否 | 商户订单号（与 order_no 二选一，至少提供一个） |
| sign | string | 是 | 签名（按文档规则计算；pay 接口使用 api_secret_pay） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "order_no": null
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| order_no | string | 是 | 接口订单号 |
| out_order_no | string | 是 | 商户订单号 |
| amount | int | 是 | 下单参考金额（= 实际金额 × 10000 的整数）；仅参考，入账以 actual_amount 为准 |
| actual_amount | int\|null | 否 | 用户实际支付金额（代收入账与对账的主依据，请优先使用；未知时为 null） |
| fee_amount | int\|null | 否 | 手续费（按实付重算；未知时为 null） |
| net_amount | int\|null | 否 | 净入账额 = 实付 − 手续费（未知时为 null） |
| currency | string | 是 | 币种 |
| status | string | 是 | 订单状态：pending（处理中）/success（成功）/failed（失败）。仅 success/failed 为终态；超时未支付不再单独返回 expired，统一归一为 pending。 |
| channel_order_no | null | 否 | 始终返回 null。订单查询和业务关联请使用 order_no 或 out_order_no。 |
| paid_at | string\|null | 否 | 支付成功时间（ISO8601；未成功时可能为空） |
| notify_status | string | 是 | 回调发送状态：pending/success/failed（不等同于支付结果） |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "actual_amount": 9500,
    "fee_amount": 500,
    "net_amount": 9000,
    "currency": "USDT",
    "status": "success",
    "channel_order_no": null,
    "paid_at": "2025-01-01T12:15:00Z",
    "notify_status": "success"
  }
}
```

### 补充说明

- order_no/out_order_no 至少提供一个。
- status 终态为 success/failed；pending 表示处理中（含超时未支付，已统一归一为 pending）。
- 代收仅在订单成功（success）时回调商户；失败/超时等非成功状态不回调，商户应通过本查单接口获知最终结果。

### 常见错误

- 100104 签名错误
- 300301 订单不存在

## 代付：创建订单（payout/create）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/create`
- 用途：创建代付订单并冻结余额（创建成功不代表最终出款成功）。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、out_payout_no、amount、currency、pay_method、notify_url、account_no
- 可选字段：nonce、country、account_name、bank_name、bank_code、remark、client_ip、extra

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| out_payout_no | string | 是 | 商户代付单号（幂等键；同一商户不可重复） |
| amount | int | 是 | 代付金额（= 实际金额 × 10000 的整数，例如 1.2345 传 12345）。必须是 JSON 数字正整数，不接受字符串（如 "12345"）或小数；非法返回 code=100001（HTTP 200，message 为具体字段校验错误信息）。 |
| currency | string | 是 | 币种（如 PHP/USDT；用于校验与路由） |
| pay_method | string | 是 | 支付方式（从支付方式字典中选择，如 gcash/maya；加密货币使用链名如 trc20/erc20） |
| country | string\|null | 否 | 国家 ISO 码（如 PH；法币必填，加密货币可省略） |
| notify_url | string | 是 | 回调地址（代付订单进入终态后，接口会向该地址发送结果） |
| account_name | string\|null | 否 | 收款人姓名/账户名（按通道类型解释；链上地址类可空） |
| account_no | string | 是 | 收款账号/地址（按通道类型解释；例如银行卡号/链上地址） |
| bank_name | string\|null | 否 | 银行/链名称（按通道类型解释；可空） |
| bank_code | string\|null | 否 | 银行编码（对应「查询可用银行」接口返回的 code；无银行选择时可省略；如同时传 bank_name，以 bank_code 解析结果优先） |
| remark | string\|null | 否 | 备注（可用于商户自定义说明/对账） |
| client_ip | string\|null | 否 | 终端用户 IP（可用于风控） |
| extra | object\|null | 否 | 扩展字段（参与签名；object 会按稳定 JSON 序列化）。⚠️ 强烈建议携带下单用户个人信息 extra.customer（见下方"扩展信息与渠道要求"说明） |
| extra.customer | object | 否 | 下单用户信息（选填）。未提供时，接口不会仅因缺少该字段返回 300405；但部分支付方式要求 name/phone，缺失可能导致订单失败，建议提供。提供后接口会校验格式并转换字段（整名 name 会拆分为 first/last）：可填 name 整名（或 first_name+last_name）、email、phone |
| sign | string | 是 | 签名（按文档规则计算；payout 接口使用 api_secret_payout） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_payout_no": "WD202501010001",
  "amount": 5000,
  "currency": "PHP",
  "pay_method": "gcash",
  "country": "PH",
  "notify_url": "https://merchant.example.com/api/notify/payout",
  "account_name": "张三（可选）",
  "account_no": "09171234567",
  "bank_name": "可选：银行卡代付时填银行名（gcash 等钱包类可省略）",
  "client_ip": "1.2.3.4",
  "remark": "商户提现（可选）",
  "extra": {
    "user_id": "u123456",
    "customer": {
      "first_name": "San",
      "last_name": "Zhang",
      "email": "user@example.com",
      "phone": "13800000000"
    }
  }
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 接口代付单号（用于后续查询与对账） |
| out_payout_no | string | 是 | 商户代付单号（原样回传，便于商户侧关联业务） |
| amount | int | 是 | 代付金额（= 实际金额 × 10000 的整数，例如 12345 表示 1.2345） |
| currency | string | 是 | 币种（原样回传请求币种，如 PHP/USDT） |
| status | string | 是 | 订单状态：创建时为 pending（处理中，代付一律先进入处理流程）；success（成功）/failed（失败）为终态。 |
| review_status | string\|null | 否 | 出款审批状态（启用商户出款审批时）：pending（审批中）/approved（通过）/rejected（驳回）；无需审批时为 null。 |
| fee_amount | int\|null | 否 | 手续费（可空） |
| freeze_amount | int\|null | 否 | 冻结金额（可空；建议口径 amount + fee_amount） |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "amount": 5000,
    "currency": "PHP",
    "status": "pending",
    "review_status": "pending",
    "fee_amount": 20,
    "freeze_amount": 5020
  }
}
```

### 补充说明

- 创建成功表示"已受理并冻结余额"。代付通常需要审核/下发等流程，最终结果以异步通知与查询为准。
- 幂等键：同一商户的 out_payout_no 全局唯一；重复下单参数一致视为幂等成功。
- 【用户信息（强烈建议必传）】部分支付方式要求终端用户姓名/手机（email 可能选填），缺失可能导致订单失败。请使用通用字段提供：代付可使用 account_name（收款人/账户名），或使用 extra.customer.name 整名（也可显式 extra.customer.first_name+last_name）；邮箱——extra.customer.email（或 extra.email）；手机——extra.customer.phone（或 extra.phone）。提供后接口会统一校验并转换字段，商户无需为不同支付方式分别适配。
- 【银行类代付】pay_method 为银行类时 bank_code 必传，取值用「查询可用银行（payout/banks/query）」接口返回的 code；编码非法返回 300407。

### 常见错误

- 100104 签名错误
- 300201 代付下单幂等冲突
- 300501 余额不足
- 300404 当前条件下无可用支付方式
- 300403 费率未配置
- 300401 支付方式不可用/不存在
- 100001 参数校验失败（如 notify_url 指向内网/非法协议）
- 300402 支付方式配置不可用
- 300406 缺少必要下单参数（如 bank_code）
- 300407 银行编码非法

## 代付：查询订单（payout/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/query`
- 用途：按接口代付单号或商户代付单号查询代付订单状态。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、payout_no/out_payout_no（二选一）
- 可选字段：nonce

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| payout_no | string\|null | 否 | 接口代付单号（与 out_payout_no 二选一，至少提供一个） |
| out_payout_no | string\|null | 否 | 商户代付单号（与 payout_no 二选一，至少提供一个） |
| sign | string | 是 | 签名（按文档规则计算；payout 接口使用 api_secret_payout） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 接口代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| amount | int | 是 | 代付金额（= 实际金额 × 10000 的整数，例如 12345 表示 1.2345） |
| currency | string | 是 | 币种 |
| status | string | 是 | 订单状态：pending（处理中）/success（成功）/failed（失败），仅 success/failed 为终态。 |
| sub_state | string\|null | 否 | 处理中子态（归一化）：accepted 已受理 / reviewing 审核中 / processing 出款处理中 / verifying 出款结果核实中（非终态，请继续等待回调或轮询）；终态时为 null |
| channel_order_no | null | 否 | 始终返回 null。订单查询和业务关联请使用 payout_no 或 out_payout_no。 |
| finished_at | string\|null | 否 | 完成时间（ISO8601；未终态时可能为空） |
| failed_reason | string\|null | 否 | 失败原因摘要（仅失败时可能有值） |
| notify_status | string | 是 | 回调发送状态：pending/success/failed（不等同于代付结果） |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "amount": 5000,
    "currency": "USDT",
    "status": "success",
    "sub_state": null,
    "channel_order_no": null,
    "finished_at": "2025-01-01T13:00:00Z",
    "failed_reason": null,
    "notify_status": "success"
  }
}
```

### 补充说明

- payout_no/out_payout_no 至少提供一个。
- status 终态为 success/failed；pending 表示处理中（请继续等待回调或轮询查单）。
- sub_state 是处理中状态的归一化细分（verifying 表示出款结果核实中，仍是非终态）；sub_state 仅用于展示/排查，业务判断以 status 终态（success/failed）为准。

### 常见错误

- 100104 签名错误
- 300301 订单不存在

## 代付：查询可用银行（payout/banks/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/banks/query`
- 用途：按国家 + 支付方式查询当前可用的银行列表（银行类代付下单 bank_code 的合法取值）。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、pay_method
- 可选字段：nonce、country、currency

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| pay_method | string | 是 | 支付方式（银行类如 bank；钱包类如 gcash 通常返回空列表） |
| country | string\|null | 否 | 国家 ISO 码（如 PH） |
| currency | string\|null | 否 | 币种（如 PHP） |
| sign | string | 是 | 签名（按文档规则计算；payout 接口使用 api_secret_payout） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "pay_method": "bank",
  "country": "PH",
  "currency": "PHP"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| banks | array | 是 | 可用银行列表 [{code,name}]；code 即代付下单 bank_code 的合法取值。 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "banks": [
      {
        "code": "BDO",
        "name": "BDO Unibank"
      },
      {
        "code": "BPI",
        "name": "Bank of the Philippine Islands"
      },
      {
        "code": "GCASH",
        "name": "GCash"
      },
      {
        "code": "MAYA",
        "name": "Maya"
      }
    ]
  }
}
```

### 补充说明

- 可用银行列表可能动态变化，建议下单前实时查询（或做短期缓存）。
- 纯钱包类支付方式（pay_method=gcash/maya 本身）无需选目的地，返回空 banks；银行类（pay_method=bank）的 banks 可能含电子钱包（如 GCASH/MAYA）作为代付目的地，bank_code 可传其 code。

### 常见错误

- 100104 签名错误

## 代付：查询付款凭证（payout/proof/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/proof/query`
- 用途：查询已成功代付订单的付款凭证地址（并非所有支付方式/币种都提供凭证）。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、payout_no/out_payout_no（二选一）
- 可选字段：nonce

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| payout_no | string\|null | 否 | 接口代付单号（与 out_payout_no 二选一，至少提供一个） |
| out_payout_no | string\|null | 否 | 商户代付单号（与 payout_no 二选一，至少提供一个） |
| sign | string | 是 | 签名（按文档规则计算；payout 接口使用 api_secret_payout） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 接口代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| proof_url | string | 是 | 付款凭证地址（有访问时效，见 expires_in；请即取即用，勿持久化该 URL） |
| expires_in | int\|null | 否 | 凭证地址有效期（秒，由渠道决定，如 1800=30 分钟；null 表示渠道未声明） |
| queried_at | string\|null | 否 | 接口返回的查询时间（可空；格式可能不是 ISO8601） |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "proof_url": "https://proof.example.com/xxx.pdf",
    "expires_in": 1800,
    "queried_at": "2025-01-01 13:00:00"
  }
}
```

### 补充说明

- 仅出款成功（status=success）的订单可查凭证；处理中/失败订单返回 300409。
- 并非所有渠道/币种都提供凭证；部分渠道仅支持查询近几天内的订单（超窗返回 300409）。
- 凭证 URL 有访问时效（expires_in 秒），请下载转存而非保存 URL。

### 常见错误

- 100104 签名错误
- 300301 订单不存在
- 300408 渠道不支持凭证查询
- 300409 凭证暂不可用

## 代付：查询付款收据图片（payout/receipt/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/receipt/query`
- 用途：查询/生成代付订单的付款收据图片（PNG）。仅 status=success 的代付单可生成；render-once 落盘复用。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、payout_no/out_payout_no（二选一）
- 可选字段：nonce、lang、inline

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| payout_no | string\|null | 否 | 接口代付单号（与 out_payout_no 二选一，至少提供一个） |
| out_payout_no | string\|null | 否 | 商户代付单号（与 payout_no 二选一，至少提供一个） |
| lang | string\|null | 否 | 收据语言（可选，枚举：en / zh-CN / zh-TW；不传时按订单国家自动派生） |
| inline | int\|null | 否 | 传 1 时直接返回 base64 图片数据（image_base64 + mime）；不传或传 0 时返回带时效签名的下载链接（receipt_url） |
| sign | string | 是 | 签名（按文档规则计算；payout 接口使用 api_secret_payout） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001",
  "lang": "zh-CN",
  "inline": 0
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 接口代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| lang | string | 是 | 收据实际使用的语言（en / zh-CN / zh-TW） |
| receipt_url | string\|null | 否 | 收据图片下载地址（相对路径，如 /api/open/v1/payout/receipt/file?token=…；商户需拼接自己的 openapi_base 得到完整下载地址；GET 即可下载；仅 inline=0 或未传时返回；链接带时效，有效期见 expires_in；请即取即用，勿持久化该 URL） |
| expires_in | int\|null | 否 | 下载链接有效期（秒，如 3600=1 小时；null 表示未声明有效期）；仅 inline=0 或未传时有意义 |
| mime | string\|null | 否 | 图片 MIME 类型（如 image/png）；仅 inline=1 时返回 |
| image_base64 | string\|null | 否 | 收据图片 Base64 编码数据（不含 data URI 前缀）；仅 inline=1 时返回 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "lang": "zh-CN",
    "receipt_url": "/api/open/v1/payout/receipt/file?token=eyJhbGciOiJIUzI1NiJ9...",
    "expires_in": 3600
  }
}
```

### 补充说明

- 仅出款成功（status=success）的代付单可生成收据；非 success 状态返回 300410。
- render-once：首次生成后图片落盘复用，相同参数重复查询直接返回缓存结果。
- lang 不传时接口按订单国家自动选择语言（如 PH → en）；显式指定可覆盖。
- inline=1 时响应体直接含 Base64 图片（image_base64 + mime），适合服务端即时处理；inline=0（默认）时返回带时效签名的 receipt_url 下载链接，适合前端跳转/嵌入。
- 下载链接有时效（expires_in 秒），请勿持久化 URL；需长期保存请下载图片转存至自有存储。

### 常见错误

- 100104 签名错误
- 300301 订单不存在
- 300410 代付收据暂不可用（订单非 success）
- 300411 收据生成失败

## 通用：查询可用支付方式（pay-methods/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/pay-methods/query`
- 用途：查询可用支付方式字典（下单 pay_method/country 的合法取值）。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign
- 可选字段：nonce、country

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| country | string\|null | 否 | 国家 ISO 码（如 PH；不传返回全部） |
| sign | string | 是 | 签名（按文档规则计算；本接口使用 api_secret_pay） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "country": "PH"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| methods | array | 是 | 支付方式列表 [{pay_method,name,country,currency}]；pay_method 即下单接口的合法取值 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "methods": [
      {
        "pay_method": "gcash",
        "name": "GCash",
        "country": "PH",
        "currency": "PHP"
      },
      {
        "pay_method": "maya",
        "name": "Maya",
        "country": "PH",
        "currency": "PHP"
      }
    ]
  }
}
```

### 补充说明

- 返回的是当前可用支付方式字典；某支付方式当前是否可下单以创建订单接口的实际返回为准（无可用支付方式时报 300404）。

### 常见错误

- 100104 签名错误

## 通用：查询账户余额（balance/query）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/balance/query`
- 用途：查询商户交易账户各币种的可用/冻结余额。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign
- 可选字段：nonce、currency

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（从商户后台获取） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| currency | string\|null | 否 | 币种（如 PHP；不传返回全部币种） |
| sign | string | 是 | 签名（按文档规则计算；本接口使用 api_secret_pay） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "currency": "PHP"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| balances | array | 是 | 余额列表 [{currency,available,frozen}]；金额 = 实际金额 × 10000 的整数 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "balances": [
      {
        "currency": "PHP",
        "available": 12345600,
        "frozen": 50000
      }
    ]
  }
}
```

### 补充说明

- available 为可用余额，frozen 为冻结金额（含在途代付冻结）；均为最小单位整数。

### 常见错误

- 100104 签名错误

## 通用：服务版本（GET /version）

- 方法：`GET`
- 接口：`<BASE_URL>/version`
- 用途：查询服务端 OpenAPI 版本（无需鉴权）；所有 /api/open/ 响应均带 X-Api-Version 头。

### 请求说明

- Content-Type：`application/json`
- 必填字段：无
- 可选字段：无

_无_

#### 请求示例
```json
{}
```

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| major | string | 是 | 大版本（路径前缀，如 v1） |
| version | string | 是 | 语义化版本（major.minor.patch） |
| base_path | string | 是 | OpenAPI 路径前缀 |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "major": "v1",
    "version": "1.2.0",
    "base_path": "/api/open/v1"
  }
}
```

### 补充说明

- 无需鉴权；用于探活与版本灰度排查。

## 代付：下载付款回单文件（GET /payout/receipt/file）

- 方法：`GET`
- 接口：`<BASE_URL>/payout/receipt/file`
- 用途：下载代付付款回单图片（二进制 PNG）。本端点不直接调用，而是由「查询付款收据图片（payout/receipt/query）」返回的 receipt_url 派生，商户拼接专有域名后直接 GET 即可下载。

### 请求说明

- Content-Type：`application/json`
- 必填字段：token
- 可选字段：无

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| token | string | 是 | 下载令牌（query 参数）。由 payout/receipt/query（inline=0/未传时返回的 receipt_url）签发，内含代付单号/商户/语言/有效期，带时效；请勿自行构造或长期持久化。 |

#### 请求示例
```json
{}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

_无_

#### 返回示例
```json
{}
```

### 补充说明

- 本端点返回的是**二进制图片**（`Content-Type: image/png`，`content-disposition: inline`），而非 `{code,message,data}` JSON 信封；下载成功直接得到 PNG 字节流。
- 调用方式：先用「查询付款收据图片（payout/receipt/query）」（inline=0 或不传）拿到 `receipt_url`（相对路径），再拼接自己的 openapi_base 得到完整地址，最后直接 `GET` 该地址下载。
- **官方 SDK 不封装本端点**：receipt_url 是带时效签名的下载链接，请用任意 HTTP 客户端自行 GET（无需再签名/验签）；链接过期（token 失效）会返回 `100001`，重新调用 payout/receipt/query 取新链接即可。
- 鉴权方式与其它接口不同：本端点不走 merchant_no/api_key/sign 鉴权，仅凭 query 中的 `token` 鉴权（HMAC 签发），故无需也不应附带签名字段。

### 常见错误

- 100001 token 非法或已过期（重新调用 payout/receipt/query 取新链接）
- 300410 代付收据暂不可用（订单非 success）

## 测试沙箱：手动完成代收测试单（pay/test/complete）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/pay/test/complete`
- 用途：【仅测试密钥】把一笔测试代收单手动推到终态（success/failed），用于无真实支付流程的对接联调；正式密钥调用被拒。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、out_order_no/order_no（二选一）、result
- 可选字段：nonce、actual_amount

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（必须为**测试**环境的密钥；正式密钥调用会被拒） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| out_order_no | string\|null | 否 | 商户订单号（与 order_no 二选一，至少提供一个） |
| order_no | string\|null | 否 | 接口订单号（与 out_order_no 二选一，至少提供一个） |
| result | string | 是 | 期望终态：success（成功）或 failed（失败） |
| actual_amount | int\|null | 否 | 用户实付金额（= 实际金额 × 10000 的整数，可选；仅 result=success 时生效，不传则按订单 amount；result=failed 时忽略） |
| sign | string | 是 | 签名（按文档规则计算；本接口使用 api_secret_pay 测试密钥） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "order_no": null,
  "result": "success",
  "actual_amount": 10000
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ok | bool | 是 | 是否处理成功（恒为 true，失败由 code 表达） |
| order_no | string | 是 | 接口订单号 |
| status | string | 是 | 推送后的订单终态：success/failed |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "ok": true,
    "order_no": "P20250101000001",
    "status": "success"
  }
}
```

### 补充说明

- 【仅测试密钥】只有测试环境密钥（env=test）可调用；正式密钥调用返回 100001（仅测试密钥可调用测试完成端点）。
- 仅可作用于测试单（is_test=1）：用测试密钥下单的订单会被标记 is_test=1、走 mock、不动真钱、不计统计。对非测试单、不存在的单或已终态的单调用，返回 100001（订单不存在 / 非测试订单不可手动完成 / 订单已是终态）。
- 置为 success 后会照常入队并投递商户回调（沙箱「回调照发」），可用于联调回调验签与入账逻辑；推送结果同样可经 pay/query 查询。

### 常见错误

- 100104 签名错误
- 100001 仅测试密钥可调用 / 订单不存在 / 非测试订单 / 订单已是终态（message 为具体原因）

## 测试沙箱：手动完成代付测试单（payout/test/complete）

- 方法：`POST`
- 接口：`<BASE_URL>/merchant/payout/test/complete`
- 用途：【仅测试密钥】把一笔测试代付单手动推到终态（success/failed），用于无真实支付流程的对接联调；正式密钥调用被拒。

### 请求说明

- Content-Type：`application/json`
- 必填字段：merchant_no、api_key、timestamp、sign、out_payout_no/payout_no（二选一）、result
- 可选字段：nonce

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号（从商户后台获取） |
| api_key | string | 是 | API Key（必须为**测试**环境的密钥；正式密钥调用会被拒） |
| timestamp | int | 是 | Unix 时间戳（秒），用于过期校验（建议 ±300 秒窗口） |
| nonce | string\|null | 否 | 防重放随机串；不使用时建议省略或传 null（不要传空字符串） |
| out_payout_no | string\|null | 否 | 商户代付单号（与 payout_no 二选一，至少提供一个） |
| payout_no | string\|null | 否 | 接口代付单号（与 out_payout_no 二选一，至少提供一个） |
| result | string | 是 | 期望终态：success（成功）或 failed（失败） |
| sign | string | 是 | 签名（按文档规则计算；本接口使用 api_secret_payout 测试密钥） |

#### 请求示例
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_payout_no": "WD202501010001",
  "payout_no": null,
  "result": "success"
}
```

> 签名说明：sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算；object/array 字段会按稳定 JSON 序列化。

### 返回字段与示例

- 统一返回结构：`{ code, message, data }`；业务失败通常仍为 HTTP 200。

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ok | bool | 是 | 是否处理成功（恒为 true，失败由 code 表达） |
| payout_no | string | 是 | 接口代付单号 |
| status | string | 是 | 推送后的代付终态：success/failed |

#### 返回示例
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "ok": true,
    "payout_no": "W20250101000001",
    "status": "success"
  }
}
```

### 补充说明

- 【仅测试密钥】只有测试环境密钥（env=test）可调用；正式密钥调用返回 100001（仅测试密钥可调用测试完成端点）。代付测试完成无 actual_amount 参数（代付按下单金额）。
- 仅可作用于测试单（is_test=1）：测试单不动真实资金（创建时的冻结亦不在此解冻），不进入真实支付流程、不计统计。对非测试单、不存在的单或已终态的单调用，返回 100001（订单不存在 / 非测试订单不可手动完成 / 订单已是终态）。
- 代付终态（success/failed）均会照常入队并投递商户回调（沙箱「回调照发」），可用于联调代付回调验签；推送结果同样可经 payout/query 查询。

### 常见错误

- 100104 签名错误
- 100001 仅测试密钥可调用 / 订单不存在 / 非测试订单 / 订单已是终态（message 为具体原因）

## 签名与通用字段

通用字段（建议放在 JSON 请求体中）：`merchant_no`、`api_key`、`timestamp`、`nonce`（可选）、`sign`。
建议算法：HMAC-SHA256。把所有参与签名字段（不含 sign）按字段名 ASCII 升序排序，拼成 `key=value` 并用 `&` 连接，末尾追加 `&secret=...`，对 raw string 做 HMAC-SHA256，结果转 16 进制小写作为 sign。
时间戳窗口为 **±300 秒**（请求时间与服务端相差超过此范围返回 `100101`）。防重放：传 `nonce` 时按 nonce 去重，不传时按「签名指纹」兜底去重；两类去重记录均保留 **300 秒**，即同一 nonce / 同一已签名请求在 300 秒内不可重复（重复返回 `100103`）。建议为每个请求生成全局唯一 nonce（如 UUID），并保证其在至少 300 秒内不重复。

> 提示：以上签名规则已由「官方 SDK」内置实现，对接时直接调用 SDK 即可，无需手写签名与稳定序列化。

## 金额口径

所有金额统一约定为 **金额 = 实际金额 × 10000 的整数**（最小记账单位 0.0001），例如 `1.2345` 传 `12345`，以整数传输避免浮点误差。该口径适用于全部请求、响应与回调的金额字段，且不区分币种（请勿按「元」理解）。

## 测试密钥沙箱

**测试环境密钥（env=test）**可用于对接联调：用测试密钥发起的代收/代付下单会被标记为测试单（`is_test=1`），**走 mock 短路、不进入真实支付流程、不动真实资金、不计入统计与对账**，但**回调照发**（可联调回调验签与入账逻辑）。
测试单不会进入真实支付流程，因此提供两个**手动完成端点**让你自行把测试单推到终态：`POST /merchant/pay/test/complete`（代收，用 api_secret_pay，可选 actual_amount）与 `POST /merchant/payout/test/complete`（代付，用 api_secret_payout），`result` 取 `success`/`failed`，详见上方对应端点说明。
> 注意：手动完成端点**仅测试密钥（env=test）可调用**，正式密钥调用一律被拒（返回 `100001`，message 为「仅测试密钥可调用测试完成端点」）；且只能作用于测试单（`is_test=1`）——对正式单、不存在的单或已终态的单调用，同样返回 `100001`。**正式（env=prod）密钥无此沙箱行为：正式单不可手动完成。**

## 回调说明

订单进入终态后，接口会按创建订单时提供的 `notify_url` 以 `POST application/json` 方式发送支付/代付结果。
回调同样携带签名字段，请商户侧按对应业务（pay/payout）使用各自密钥验签。

**代收回调时机**：代收仅在订单成功（`status=success`）时回调商户；失败/超时等非成功状态不回调，商户应通过查单接口（`pay/query`）获知最终结果。
（代付不受此限制：代付在终态 `success/failed` 均会回调。）

**成功应答规则**：商户处理成功后，必须返回 HTTP 200 且响应体为 `success` 或 `ok`（大小写不敏感）；
也接受 JSON 格式：`{"success":true}` / `{"code":0}` / `{"message":"success"}` / `{"message":"ok"}`。
未按此返回（即使 HTTP 200 但 body 为其他内容，如空响应、HTML 错误页等）本次回调视为失败，
将按策略重试，连续失败达阈值会触发告警。

### 代收回调字段

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号 |
| order_no | string | 是 | 接口订单号 |
| out_order_no | string | 是 | 商户订单号 |
| amount | int | 是 | 下单参考金额（= 实际金额 × 10000 的整数）；仅参考，入账以 actual_amount 为准 |
| actual_amount | int\|null | 否 | 用户实际支付金额（代收入账与对账的主依据，请优先使用；未知时为 null） |
| fee_amount | int\|null | 否 | 手续费（按实付重算；未知时为 null） |
| net_amount | int\|null | 否 | 净入账额 = 实付 − 手续费（未知时为 null） |
| currency | string | 是 | 币种 |
| status | string | 是 | 归一状态；代收仅在成功时回调，故此处恒为 success |
| channel_order_no | null | 否 | 始终返回 null；请使用 order_no 或 out_order_no 关联订单。该字段为 null，不参与签名。 |
| paid_at | string\|null | 否 | 支付成功时间（ISO8601；未成功时可能为空） |
| sign | string | 是 | 签名（代收回调用 api_secret_pay 计算） |

### 代付回调字段

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号 |
| payout_no | string | 是 | 接口代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| amount | int | 是 | 代付金额（= 实际金额 × 10000 的整数） |
| currency | string | 是 | 币种 |
| status | string | 是 | 归一状态：success/failed（代付终态均回调） |
| fee_amount | int\|null | 否 | 手续费（未知时为 null） |
| channel_order_no | null | 否 | 始终返回 null；请使用 payout_no 或 out_payout_no 关联订单。该字段为 null，不参与签名。 |
| finished_at | string\|null | 否 | 完成时间（ISO8601） |
| failed_reason | string\|null | 否 | 失败原因（失败时返回） |
| sign | string | 是 | 签名（代付回调用 api_secret_payout 计算） |

> 注意：代付回调体**不包含** `notify_status`。`notify_status` 只在查单响应中返回，不会出现在回调体中。

### 退款回调字段

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号 |
| order_no | string\|null | 否 | 来源代收单的接口订单号（被退款的原代收单；极端情况下可能为 null） |
| out_order_no | string\|null | 否 | 来源代收单的商户订单号（被退款的原代收单；极端情况下可能为 null） |
| source_channel_order_no | null | 否 | 始终返回 null；退款回调不提供原代收单的其他订单编号。该字段为 null，不参与签名。 |
| refund_no | string | 是 | 接口退款单号 |
| out_refund_no | string | 是 | 商户退款单号 |
| amount | int | 是 | 退款金额（= 实际金额 × 10000 的整数） |
| currency | string | 是 | 币种 |
| status | string | 是 | 归一状态：success/failed（退款终态均回调，与代付终态集合一致） |
| channel_order_no | null | 否 | 始终返回 null；退款订单关联请使用 refund_no 或 out_refund_no。该字段为 null，不参与签名。 |
| finished_at | string\|null | 否 | 完成时间（ISO8601） |
| failed_reason | string\|null | 否 | 失败原因（对外脱敏的通用文案；失败时返回，其余为 null） |
| sign | string | 是 | 签名（退款回调用 api_secret_pay 计算，与代收共用密钥） |

> 退款回调说明：退款回调体**用 `api_secret_pay` 验签**（与代收共用密钥，商户无需额外维护退款回调密钥）；验签算法与代收/代付回调一致（见下文「回调签名验证」）。`channel_order_no` / `source_channel_order_no` 始终为 `null`，**不参与签名**。`refund_no` / `out_refund_no` 为退款单号，`order_no` / `out_order_no` 关联被退款的原代收单。

### 回调签名验证

回调验签算法与请求签名**完全一致**（见「签名与通用字段」一节）：
1. 取回调 JSON 顶层「除 `sign` 外、且值非 `null`」的所有字段；
2. 按字段名 ASCII（码点）升序排序，拼成 `key=value` 用 `&` 连接；
3. 末尾追加 `&secret=<对应密钥>`，对该 raw string 做 HMAC-SHA256，输出 16 进制小写；
4. 与回调体的 `sign` 比对（建议用常量时间比较，如 `crypto.timingSafeEqual`，防时序攻击）。

密钥选择：**代收回调用 `api_secret_pay`，代付回调用 `api_secret_payout`**。`channel_order_no` 等值为 `null` 的字段**不参与签名**（与请求签名「跳过 null」一致）。

> **安全红线（务必遵守）**：① **先验签通过、再处理业务与入账**，严禁未验签即按回调内容改单；② 入账 / 对账金额以 `actual_amount`（用户实付）与 `net_amount`（净入账 = 实付 − 手续费）为准，**切勿使用 `amount`**（仅为下单参考额）；③ 回调可能因重试重复投递，请按 `out_order_no` / `out_payout_no` **幂等**处理，重复通知不可重复入账；④ 回调来源 IP **不固定**，**请勿用 IP 白名单限制回调来源——验签是唯一可信校验**。

### 重试 / 超时 / 安全

- **超时**：回调服务等待商户应答 `8000ms`，无应答即判本次失败。
- **重试**：最多 `6` 次（含首次），退避间隔依次约 `1分钟 / 2分钟 / 5分钟 / 10分钟 / 30分钟 / 60分钟`（超过取最后一档）；达上限仍失败则该回调任务置 `failed`，商户可用查单接口兜底获取终态。
- **不跟随重定向**：回调服务对 `notify_url` 不跟随 30x 重定向。
- **SSRF 防护**：`notify_url` 指向内网 / 环回 / 链路本地地址会被拦截，直接判失败且**不重试**（地址不会自行变化）；下单时此类 `notify_url` 亦会返回 `100001`。

### 字段口径与对外约定

- 回调金额同请求口径：= 实际金额 × 10000 的整数。
- `status` 为接口统一状态值：代收恒为 `success`；代付为 `success/failed`。

### 代收 vs 代付差异

- **触发时机**：代收**仅在 `success`** 时触发回调；代付在终态 `success/failed` **均**触发。
- **字段差异**：代收特有 `actual_amount` / `net_amount` / `paid_at`；代付特有 `finished_at` / `failed_reason`。

## IP 黑白名单说明

- 默认不启用白名单（允许所有来源 IP 访问 OpenAPI）。
- 当启用策略后，命中黑名单直接拒绝；存在白名单时必须命中白名单才允许访问。
- 导出文档不包含商户真实白名单配置，请在商户后台自行查看。

## 错误码总表

所有业务错误统一返回 **HTTP 200** + 信封 `{ code, message, data }`，**不使用 HTTP 4xx/5xx 表达业务失败**（传输层 4xx/5xx 仅代表网络/网关异常，应排查链路或重试）。各端点「常见错误」只列高频项，完整错误码以本表为准；鉴权类错误（`100xxx`）对所有需鉴权端点通用。

| 错误码 | 含义 | HTTP | 可重试 | 说明 / 排查 |
|---|---|---|---|---|
| `100000` | 通用业务失败 / 鉴权失败统一码 | 200 | 视情况 | 订单未被受理时已实时置 failed，勿用同 out_order_no 重试；鉴权阶段统一为「认证失败」话术 |
| `100001` | 参数校验失败 | 200 | 否 | message 为具体字段校验错误信息（英文，如 `"amount" must be a number`）；amount 必须为 JSON 整数，字符串/小数均返回此码。修正参数后重发 |
| `100101` | 请求过期 | 200 | 是 | timestamp 超出 ±300 秒窗口；校准本地时间后重试 |
| `100102` | IP 不在白名单 | 200 | 否 | 在商户后台把来源 IP 加入白名单后重试 |
| `100103` | 重放 / 重复请求 | 200 | 否 | nonce 或签名指纹在 300 秒内重复；换新 nonce（或改参数）后重试 |
| `100104` | 签名错误 | 200 | 否 | 核对密钥与签名算法（pay 用 api_secret_pay、payout 用 api_secret_payout） |
| `100105` | IP 在黑名单 | 200 | 否 | 从黑名单移除来源 IP 后重试 |
| `100106` | 鉴权失败次数过多（限流） | 200 | 是 | 同 merchant_no + 来源 IP 在 60 秒内鉴权失败达 60 次触发；等待约 60 秒再试 |
| `200002` | 服务账户已禁用 | 200 | 否 | 联系服务商处理 |
| `210002` | 商户已禁用 | 200 | 否 | 联系服务商处理 |
| `300101` | 代收单幂等冲突 | 200 | 否 | 同 out_order_no 已存在但参数不一致；对齐参数或换新单号 |
| `300201` | 代付单幂等冲突 | 200 | 否 | 同 out_payout_no 已存在但参数不一致；对齐参数或换新单号 |
| `300301` | 订单不存在 | 200 | 否 | 核对订单号（order_no / out_order_no / payout_no / out_payout_no） |
| `300401` | 支付方式不可用 / 不存在 | 200 | 否 | 支付方式不可用或不存在 |
| `300402` | 支付方式配置不可用 | 200 | 是 | 当前请求未匹配到有效配置；修改参数或稍后重试 |
| `300403` | 费率未配置 | 200 | 否 | 联系服务商配置费率后重试 |
| `300404` | 当前条件下无可用支付方式 | 200 | 是 | country + currency + pay_method 当前无可用组合；改参数或稍后重试。可用 `pay-methods/query` 查询支付方式 |
| `300405` | 缺少必要附加信息（extra.customer） | 200 | 否 | 请按 `data.missing_fields` 补齐 extra.customer 中的字段 |
| `300406` | 缺少必要下单参数（如 bank_code） | 200 | 否 | 补齐该路由必填字段后重试 |
| `300407` | 银行编码非法 | 200 | 否 | 用「查询可用银行」(payout/banks/query) 返回的 code |
| `300408` | 当前支付方式不支持代付凭证查询 | 200 | 否 | 当前支付方式不支持凭证查询 |
| `300409` | 代付凭证暂不可用 | 200 | 是 | 订单未成功 / 凭证暂未生成 / 超出查询窗口；确认成功且在窗口内再试 |
| `300410` | 代付收据暂不可用（订单非 success） | 200 | 是 | 等订单出款成功后再试 |
| `300411` | 收据生成失败 | 200 | 是 | 渲染异常；稍后重试 |
| `300501` | 余额不足 | 200 | 是 | 商户可用余额 < 冻结额（amount + fee）；充值后重试 |

## 频率限制与轮询建议

- **限流仅作用于鉴权失败**：同一 `merchant_no` + 来源 IP 在 60 秒内鉴权失败累计达 60 次后，后续请求被拒并返回 `100106`，窗口滚动恢复（约 60 秒）。成功鉴权的正常业务请求**无 QPS 硬上限**。
- **不要无退避地重试鉴权失败**（签名错误、时间偏移等），否则极易触发 `100106`；先修正原因再请求。
- **查单轮询建议**：终态优先依赖回调；未收到回调时再轮询查单，并使用指数退避（例如首次延迟 3~5 秒，随后 5s → 15s → 30s → 60s 递增，直至终态或合理上限）。避免对未终态订单高频空轮询。

## 对接须知

- **入账金额以 `actual_amount` 为准**：代收按用户**实付**金额入账与对账，请以回调 / 查单中的 `actual_amount`（实付）与 `net_amount`（净入账 = 实付 − 手续费）为准；`amount` 仅为下单参考额，不可作为入账依据。
- **金额上下限**：各 `pay_method` 的金额上下限会**实时校验**，不预先公布固定值；超限下单会返回相应错误码（如 `300402` / `300404`）。请勿在客户端硬编码金额区间。
- **订单有效期**：代收订单 `expire_at` 以创建订单接口返回值为准，不要硬编码固定时长；超时未支付统一为 `pending`，请以查单结果为准。
- **回调来源**：回调出口 IP 不固定，请勿用 IP 白名单限制回调来源，以**验签**作为唯一可信校验（见「回调签名验证」与上方安全红线）。

=== 接口文档结束 ===

现在请开始。如对字段或流程有歧义，请先向我提问，再动手写代码。
