# 豆包智能服务支付能力接入指南

本文指导 Agent 实现支付业务闭环，不替代 API 说明书。先按“接入决策”确定范围，再读取命中的接口详情。请求字段、响应字段、平台状态枚举和错误码，以 [支付 OpenAPI 索引](server/openapi/payment/Index.md)、对应接口文档及目标项目当前安装的 SDK 类型声明为准；不要凭经验补字段或硬编码未经确认的状态值。

## 接入决策

先选择满足需求的最小范围，只读取和实现命中的分支：

| 用户需求 | 必须实现 | 继续读取 |
| --- | --- | --- |
| 普通支付 | 可信业务订单、预下单、收银台、主动查询、支付回调、结果恢复、真实履约完成 | 本文“普通支付实施顺序” |
| 退款 | 普通支付全部能力，以及退款申请、查询、回调、可退金额控制 | 再读“退款分支” |
| 自动续费或代扣 | 先线下确认向用户展示并签署的协议，再实现签约状态、签约回调、协议支付创建/查询/关闭、代扣回调和解约 | 再读“签约与协议支付分支” |
| 只展示订单 | 只读业务订单及必要的平台查询结果 | 不生成支付、退款、履约、代扣或解约请求 |

需求未明确退款时，先完成普通支付，把退款列为待确认项。普通支付不得依赖签约能力。运行态 Skill、MCP Tool 和 UI 只声明已经实现并验证的能力。

进入编码前先输出一份简短计划，至少包括：本轮范围与排除项、组件调用链、需要新增或复用的接口和数据表、回调地址，以及当前阶段真正缺失的权限、凭据、证书或可达性。

## 不可违反的约束

- 金额由业务服务端根据可信商品、数量、优惠和订单规则计算。前端和 MCP 输入只能表达购买意图。
- 金额使用最小货币单位的整数或其它无精度损失类型，调用平台时再按接口文档序列化。
- `app_secret`、私钥、应用级 Token、用户 SessionToken 和可复用支付凭证只存在于需要它们的服务端安全边界。
- 每类写操作使用独立、稳定的业务幂等键；普通订单、退款单、签约单和协议支付单不得复用外部单号。
- `requestOrder` 成功、`getOrderPayment` 返回、用户回到智能服务或退款接口受理成功，都不是最终资金结果。
- 最终状态由业务服务端结合已验签回调和平台主动查询收敛；前端 JS API 结果只用于交互反馈。
- 回调先验签，再解析和处理业务；同时校验应用、订单、金额、事件时效、nonce 和允许的状态迁移。
- 回调可能缺失、重复、乱序或晚到。重复事件幂等成功，旧事件不能覆盖更新的终态。
- 支付成功与履约完成分开。只有商品或服务真实交付后才通知平台履约完成。
- 生产代码不得只用进程内变量保存订单、退款、回调幂等状态或防重放信息。
- 密钥、Token、完整签名、完整回调 Body 和完整支付明细不得进入前端、Manifest、运行态 Skill、代码仓库或日志。

## 环境与确认边界

| 环境 | 凭据与地址 | 可以得出的结论 |
| --- | --- | --- |
| fixture / 单元测试 | 使用测试配置和本地 handler，不访问平台 | 参数构造、验签、状态迁移、幂等、乱序和防重放逻辑 |
| 本地 sandbox | 按 [本地调试总流程](local-debug/overview.md) 启动模拟器和服务；回调由模拟器在本地发起 | sandbox 实际支持范围内的支付、查询、回调、退款或履约链路 |
| 正式联调 | 目标应用正式 AppID / AppSecret、匹配证书和平台可达的 HTTPS Endpoint | 正式商户、真实资金和生产回调的最终验收 |

不要随机生成 AppID，不要混用不同应用的 AppID、AppSecret、证书、订单和回调地址。缺少公网域名不阻塞 fixture 和本地 sandbox 阶段，但 sandbox 是否支持某项资金能力要以实际接口结果为准，不提前声称已验证。

执行真实资金或授权状态写操作前，向用户确认环境、订单、金额和预期动作。fixture 与 sandbox 验证不因此暂停。三种环境的验证结论分开记录，不把较低环境的结果描述成正式联调成功。

当前阶段需要满足的前置条件：

- 目标应用具备本轮需要的支付能力；不得伪造支付权限、商户号、商户 UID、开户状态、账期或手续费。
- 服务端可用 `app_secret` 按 [获取调用凭证 Token](server/openapi/token/get-client-token.md) 获取应用级 Token。
- 当前环境已准备匹配的 ECC P-256 私钥、CSR、开发者证书和平台证书；具体要求读取 [签名认证及加密传输](server/openapi/security/signature-authentication-and-encryption.md)。
- 平台请求能到达本轮所需的回调 Endpoint。
- 业务服务端有跨请求、进程重启和多实例可用的持久化存储。

只把阻塞当前所选环境的缺失项报告为阻塞，不伪造成功响应。

## Sandbox 验证方式

Sandbox 用于在不产生真实资金、且不依赖正式商户和公网回调地址的情况下验证普通支付链路。按 [本地调试总流程](local-debug/overview.md) 启动 `dbx dev` 和业务服务端，并遵守以下沙箱专用差异：

| 项目 | Sandbox 规则 | 不能推导的正式结论 |
| --- | --- | --- |
| `merchantUid` | 可以填写任意测试值，不校验真实商户绑定；但仍必须非空且不超过 64 个字符 | 不能证明正式应用已绑定商户或具备收款权限 |
| 开发者公钥 | 与登录共用同一套开发者密钥材料；沙箱 App 在 Web 模拟器中输入与服务端私钥配对的 CSR，真实 App 使用平台配置的「智能服务应用证书」 | 不能证明正式开发者证书仍在有效期内 |
| 支付动作 | 模拟收银台确认后由支付 mock 推进状态，不输入真实支付密码、不扣真实资金 | 不能证明真实渠道支付、结算、手续费或账期正确 |
| 回调地址 | 所有 sandbox 回调都由模拟器在本地发起；可以指向模拟器运行环境能够访问的任意服务，不限于 localhost / `127.0.0.1` | 不能证明生产 HTTPS 域名、证书和公网路由可达 |

沙箱 App 的“输入证书”框实际登记的是 CSR 中的 ECC P-256 公钥，不是私钥；真实 App 则读取该 App 在平台配置的「智能服务应用证书」。Sandbox 支付在校验 `requestOrder` 的 `dbAuthorization` 时读取当前 App 对应的开发者公钥，登录手机号加密也读取同一份公钥。业务服务端必须使用配对私钥签名 `data`，不要为登录和支付分别准备两套密钥，也不要把私钥粘贴到页面。使用沙箱 App 新建或切换 Web 模拟器会话后，重新确认该会话已经录入正确 CSR。

这里说的公钥用途是“平台验证开发者生成的 `dbAuthorization`”。支付结果回调方向相反：业务服务端仍需按当前 sandbox 提供的回调验签材料验证 `X-DB-Authorization`，不要把开发者私钥当成回调验签密钥，也不要用正式平台证书验证 sandbox 回调。

### 验证步骤

1. 按 [本地调试总流程](local-debug/overview.md) 选择 App 配置路径、设置业务 Server 启动环境变量，并确认 MCP endpoint 指向本轮实际使用的 Server。
2. 启动业务服务端及持久化存储，确认创建订单、预下单材料、订单查询和支付回调 Endpoint 均可访问。AppSecret、私钥和完整 Token 不写入文件或日志。
3. 登录需要解密手机号时，按当前 App 类型处理证书：沙箱 App 按 [沙箱 App 的 CSR 步骤](local-debug/sandbox-app.md#在调试器中上传-csr) 在 Web 调试器中上传 CSR；真实 App 按 [真实 App 证书步骤](local-debug/real-app.md#使用平台配置的智能服务应用证书) 使用平台配置的「智能服务应用证书」。
4. 启动 `dbx dev --mcp-endpoint <mcp_endpoint>` 并打开返回的完整 Web 调试地址。保持 Web 模拟器和回调接收服务持续运行；支付、退款、签约、代扣等 sandbox 回调都由模拟器在本地调用。
5. 创建一笔测试业务订单：使用新的 `outOrderNo`、最小货币单位整数金额，以及任意非空且不超过 64 字符的测试 `merchantUid`。重复验证同一业务操作时复用幂等键；只有要创建另一笔测试订单时才换订单号。
6. 服务端按 [预下单 data 与 dbAuthorization](server/openapi/payment/request-order-data-and-authorization.md) 生成同一份 `data` 和 `dbAuthorization`；签名上下文使用 `POST /requestOrder`，前端不解析或重新序列化 `data`。
7. 前端依次调用 `requestOrder({ data, dbAuthorization })` 和 `getOrderPayment({ orderId })`，在模拟收银台确认支付。JS API 返回后仍显示确认中，不直接认定支付成功。
8. `payNotifyUrl` 可以指向模拟器运行环境能够解析并访问的任意 HTTP 服务，包括本机、容器、局域网或其它本地可达服务；不要求公网域名。先从模拟器所在环境验证 DNS / IP、端口和路径可达，再验证回调原始 Body、签名 Header、幂等落库和标准成功响应。退款、签约与代扣回调遵守同一规则。
9. 前端查询业务服务端；服务端必要时调用 sandbox 的 `order_query`。最终同时核对本地订单状态、平台查询结果、回调订单号与金额，而不是只看模拟收银台页面。

至少记录并验证以下结果：

- 修改 `data` 任意字节、改用不配对的 CSR 或私钥、使用错误签名路径时，`requestOrder` 被拒绝。
- 正常链路经历预下单和处理中状态，模拟确认后收敛为支付成功。
- 支付回调由模拟器调用 `payNotifyUrl`，验签通过，并只产生一次有效状态迁移；重复回调命中幂等处理。
- `order_query` 返回的订单号、金额和支付状态与本地持久化一致。
- 本地服务重启后仍能查询订单并处理回调，不依赖进程内状态。

Sandbox 验证结论写成“普通支付 sandbox 闭环通过”，不要写成“真实支付已接通”。退款、协议支付和履约只有各自实际跑通创建/查询/回调或通知后，才能分别记录为已验证。

## 组件职责

| 组件 | 负责 | 不负责 |
| --- | --- | --- |
| Page / Widget | 收集支付意图、请求业务服务端、调用支付 JS API、展示处理中和最终状态 | 计算可信金额；保存密钥或平台 Token；直接调用服务端 OpenAPI |
| MCP Tool | 创建或查询业务动作上下文，返回稳定业务单号和已收敛状态 | 根据模型输入写死金额或资金结果；返回密钥、Token 或可复用凭证 |
| 业务服务端 | 计算金额、持久化、签名、平台调用、回调验签、状态收敛、对账和履约 | 信任前端传入的最终金额或支付状态 |
| 豆包开放平台 | 预下单、收银台、平台订单/退款/签约状态、异步回调和担保交易结算 | 替代开发者保存业务订单和履约事实 |

MCP Server 可以与业务服务端同进程部署，但仍要分层：Tool 调用业务服务，业务服务访问仓储和平台 client。不要在 Tool handler 中散落签名、Token 获取或状态迁移逻辑。

## 普通支付主链路

```mermaid
sequenceDiagram
    autonumber
    actor U as 用户
    participant M as Page / Widget
    participant J as 支付 JS API
    participant S as 业务服务端
    participant P as 豆包开放平台

    U->>M: 确认商品或服务
    M->>S: 创建业务订单（购买意图 + 幂等键）
    S->>S: 计算可信金额并持久化
    M->>S: 请求预下单材料（业务订单号）
    S-->>M: 返回同一订单的 data + dbAuthorization
    M->>J: requestOrder({ data, dbAuthorization })
    J-->>M: 返回预下单号
    M->>J: getOrderPayment（预下单号）
    J-->>M: 用户完成、取消或关闭收银台
    M->>S: 查询业务订单状态
    opt 本地状态需要收敛
        S->>P: order_query
        P-->>S: 返回平台订单详情
        S->>S: 持久化合法状态迁移
    end
    S-->>M: 返回业务订单状态
    P-->>S: 支付结果回调（可能早于或晚于查询）
    S->>S: 验签、幂等落库、推进状态
    S-->>P: 标准成功响应
```

前端只查询业务服务端。业务服务端不必在每次页面读取时都请求平台：本地已有可信终态时可直接返回；处于处理中、回调缺失、状态冲突或对账扫描命中时再调用 [订单查询 `order_query`](server/openapi/payment/order-query.md)，并使用有限重试和退避。

预下单材料的字段、公私钥方向、`POST /requestOrder` 签名上下文和字节一致性要求，读取 [预下单 data 与 dbAuthorization](server/openapi/payment/request-order-data-and-authorization.md)。业务服务端必须成对生成二者；Signature 使用 ASN.1 DER 后保留 `=` padding 的 URL-safe Base64，前端只原样透传，不能重新序列化 `data` 或删除 Signature padding。`dbAuthorization` 不等于订单查询等服务端 OpenAPI 使用的 `x-DB-AccessToken`。

## 先定位 SDK 类型

模板仓库不固定 SDK 版本，也不保证支付 API 的导出路径。进入目标项目后必须从实际依赖定位，不能照抄其它项目的 import：

1. 读取目标项目 `package.json` 和 lockfile，确定实际使用的豆包智能服务 framework / API 包及版本。
2. 在源码和已安装依赖中搜索符号：

   ```bash
   rg -n "requestOrder|getOrderPayment" src package.json pnpm-lock.yaml node_modules 2>/dev/null
   ```

3. 顺着匹配到的导出文件读取 `.d.ts`、源码或包的 `exports`，确认 import 路径、参数、返回值、异常和取消语义。
4. 如果依赖尚未安装，先按项目 lockfile 安装；仍找不到符号时，将“当前 SDK 未导出支付 API”报告为阻塞，不自行发明 API。

签约 JS API 也按同样方式定位。服务端字段则逐项读取命中的 OpenAPI 文档。

## 本地状态机

平台原始状态和值原样保存在独立字段中；业务状态使用项目自己的稳定枚举。下表是推荐的本地状态机，不是平台枚举声明：

| 对象 | 推荐本地状态 | 允许的主要迁移 |
| --- | --- | --- |
| 支付订单 | `CREATED`、`PAYING`、`PAID`、`PAY_FAILED`、`CLOSED` | `CREATED -> PAYING`；`CREATED/PAYING -> PAID/PAY_FAILED/CLOSED` |
| 退款单 | `CREATED`、`PROCESSING`、`SUCCEEDED`、`FAILED` | `CREATED -> PROCESSING`；`CREATED/PROCESSING -> SUCCEEDED/FAILED` |
| 履约 | `NOT_FULFILLED`、`FULFILLED`、`NOTIFIED`、`NOTIFY_FAILED` | `NOT_FULFILLED -> FULFILLED -> NOTIFIED/NOTIFY_FAILED`；失败可重试通知 |

实现时先读取接口文档中的当前平台枚举，再显式编写“平台状态 -> 本地状态”映射及未知值处理。未知平台状态不得自动当作成功。终态是否允许因主动查询结果而修正，要结合平台语义和业务规则明确决定；任何来源都不能无条件回滚终态。

状态更新使用事务或原子条件更新，并记录来源、事件时间和版本号。回调与主动查询竞争时，以合法迁移、平台事件时间和已持久化版本共同裁决，不以最后到达者简单覆盖。

## 持久化：要求与建议

必须持久化的业务事实包括：业务单号与平台单号关联、可信金额、平台原始状态、本地状态、幂等键、回调去重与验签结果、履约状态，以及退款或签约分支所需的关联关系。

没有可复用模型时，可采用以下参考拆分；表名和字段可以适配项目，不要求逐字照抄：

- `payment_orders`：业务/平台订单号、用户、金额、币种、商品快照、平台/本地支付状态、支付时间、履约状态、幂等键和版本号。
- `payment_refunds`：业务/平台退款单号、原订单、退款金额、原因、平台/本地退款状态、幂等键和结果时间。
- `payment_callback_events`：事件类型、平台日志 ID、事件唯一键、关联单号、验签与处理状态、重试次数和接收时间。
- 防重放记录：证书序列号、nonce、timestamp 和过期时间；可单独建表，也可使用具备原子写入和过期能力的共享存储。
- 需要协议支付时，增加签约关系与每笔代扣单的持久化对象。

SQLite 适合单实例本地阶段，但不是生产环境的硬性选择。多实例部署应使用满足一致性、唯一约束和原子更新要求的共享存储。

## 业务接口最小契约

沿用项目现有路由命名，不强制照抄路径：

| 能力 | 调用方 -> 提供方 | 最小职责 |
| --- | --- | --- |
| 创建业务订单 | Page / Widget 或 MCP Tool -> 业务服务端 | 输入商品/服务选择与幂等键；返回业务订单号、服务端计算的摘要和状态 |
| 获取预下单材料 | Page / Widget -> 业务服务端 | 输入业务订单号；按专项文档返回成对的 `data` 和 `dbAuthorization` |
| 查询业务订单 | Page / Widget 或 MCP Tool -> 业务服务端 | 返回已由服务端收敛的支付与履约状态 |
| 支付结果回调 | 平台 -> 当前环境可达 Endpoint | 接收原始请求和签名 Header；验签、幂等落库并返回标准响应 |
| 确认履约完成 | 履约系统或受控业务动作 -> 业务服务端 | 校验真实交付与幂等键，再通知平台 |
| 申请/查询退款 | Page / Widget、MCP Tool 或运营系统 -> 业务服务端 | 校验权限与可退金额；返回业务退款单和状态 |
| 退款结果回调 | 平台 -> 当前环境可达 Endpoint | 验签、幂等更新退款单并返回标准响应 |

## 普通支付实施顺序

### 1. 服务端公共基础

- 从安全配置读取 AppID、`app_secret`、开发者私钥、开发者证书和平台证书。
- 按 `expires_in` 缓存应用级 Token，在确认失效时刷新，不为每个请求重新获取。
- 按 [签名认证及加密传输](server/openapi/security/signature-authentication-and-encryption.md) 实现统一 signer、callback verifier、证书选择、时间窗校验和 nonce 去重；签名必须是 ASN.1 DER + padded Base64URL，并用测试防止 `r || s`、Raw Base64URL 和双重 SHA-256。
- 实现统一平台 client，保留脱敏后的 `log_id` / `X-DB-Logid`，并按接口文档选择认证方式。
- 实现仓储及显式状态迁移函数，使领域逻辑可单测。

### 2. 创建可信业务订单

服务端根据商品或服务选择重新读取可信数据并计算金额，生成唯一业务订单号，保存商品快照和初始状态。同一次操作重复提交相同幂等键时返回已有订单；只有业务语义明确要求新订单时才生成新单号。

### 3. 预下单并拉起收银台

服务端重新读取已落库订单，按 [预下单 data 与 dbAuthorization](server/openapi/payment/request-order-data-and-authorization.md) 使用可信金额、商品快照、回调 URL、开发者私钥和证书生成成对的预下单材料。签名使用最终 `data` 的原始字节；前端不得修改或重新序列化。然后按实际 SDK 类型依次执行：

1. 调用 `requestOrder` 并保存预下单号。
2. 预下单成功后调用 `getOrderPayment`。
3. 防止按钮连点和并发拉起；页面重建后通过业务订单号恢复。
4. 收银台结束后展示“正在确认”，查询业务服务端，不直接展示成功。
5. 对取消、失败、超时和未知结果展示可恢复状态；未知结果不得自动创建新订单。

### 4. 主动查询与支付回调

在收银台返回、处理中超时、回调缺失/冲突和定时对账时，服务端按需调用 [订单查询](server/openapi/payment/order-query.md)。查询结果先落库，再返回前端。

按 [支付结果回调](server/openapi/payment/payment-result-callback.md) 提供 POST Endpoint，并严格按以下顺序处理：

1. 保留验签所需的原始请求字节和 Header，读取 `X-DB-Logid`。
2. 验证证书、timestamp、nonce 和签名；失败时不进入业务处理。
3. 验签后解析外层结构和 `msg`，校验事件类型、版本、AppID、订单号和金额。
4. 以事件唯一键幂等落库，在事务中执行合法状态迁移。
5. 持久化成功后按接口文档返回标准成功响应。
6. 平台公钥:
```
-----BEGIN CERTIFICATE-----
MIICQjCCAeigAwIBAgIUGIRkZ+w59T2BI3V/3nIbZTUi60gwCgYIKoZIzj0EAwIw
VzESMBAGA1UEChMJQnl0ZURhbmNlMR8wHQYDVQQLExZCeXRlRGFuY2UgU21hcnQg
RGV2aWNlMSAwHgYDVQQDExdCeXRlRGFuY2UgU21hcnQgU0lHTiBDQTAeFw0yNTEy
MjUwNzEyMzFaFw00NTEyMzExNjAwMDBaMGwxEjAQBgNVBAoTCUJ5dGVEYW5jZTEf
MB0GA1UECxMWQnl0ZURhbmNlIFNtYXJ0IERldmljZTE1MDMGA1UEAwwsQnl0ZURh
bmNlIFNtYXJ0IFNpZ24gQ2VydCBvY2Vhbl9taW5pX3Byb2dyYW0wWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAASKHX833IEMrmq63RBLzy7P8S7T//RqBkknSE5gGDAF
dWIpDoX2TAKuqxD2WovO6r21gvOVPDbdyURH1AKZTTlno30wezAMBgNVHRMBAf8E
AjAAMB8GA1UdIwQYMBaAFGAhJ9u6CohR7TvkNcR6J8OTi+EBMEoGCCsGAQUFBwEB
BD4wPDA6BggrBgEFBQcwAYYuaHR0cDovL25leHVzLXByb2R1Y3Rpb24uYnl0ZWRh
bmNlLm5ldC9wY2Evb2NzcDAKBggqhkjOPQQDAgNIADBFAiEAh19Gb5HCiLddskJJ
kJ8URzW5UG6Dwi8hXJxC5bzZSUUCIB7YN1rJU6VEkPDSAW8MeIRjDtqprlZwW/GD
urTcUjTD
-----END CERTIFICATE-----
```

未知订单、金额不一致或非法迁移应记录脱敏告警，并通过主动查询确认。不要在验签前打印完整 Body，也不要因收到回调或返回 HTTP 200 就直接标记成功。

### 5. 真实履约

虚拟商品实际发放、服务实际完成或实物订单确认收货后，服务端才调用 [推送履约完成通知 `fulfill_push_finish`](server/openapi/payment/fulfill-push-finish.md)。调用前确认订单已支付且未成功通知；调用结果持久化，失败可按幂等策略重试。

平台收到履约状态后才会按线下约定触发后续结算。Agent 不推断商户账期、手续费、实际到账金额或时间。

## 退款分支

仅在需求包含退款时实现：

1. 确认原订单已支付，重新计算剩余可退金额并校验业务权限。
2. 生成唯一业务退款单号，先持久化退款意图，再调用 [退款创建 `refund_create`](server/openapi/payment/refund-create.md)。重复幂等键返回同一退款单。
3. 把平台受理保存为处理中，不展示成退款到账。
4. 按 [退款结果回调](server/openapi/payment/refund-result-callback.md) 验签、校验金额并幂等更新。
5. 用户回访、回调缺失或状态异常时调用 [退款查询 `refund_query`](server/openapi/payment/refund-query.md) 收敛状态。
6. 部分退款需保证历次成功退款之和不超过可退金额，并校验商品维度与订单总额一致。

## 签约与协议支付分支

仅在自动续费、周期扣款或用户明确要求协议支付时实现。进入技术接入前，必须先由商户、平台和相关业务方线下沟通并确认最终给用户展示和签署的协议，包括协议模板或 `service_id`、服务与扣款规则、有效期、续费/代扣方式、用户取消和解约入口等内容。Agent 不自行编写法律协议、不根据示例推断协议条款，也不把尚未线下确认的协议描述成可签署。

线下协议未确认时，可以完成与协议内容无关的数据模型、接口 abstraction、幂等和回调测试，但真实签约入口保持禁用，并把协议模板、`service_id` 和正式签约配置列为阻塞项。Sandbox 只能验证签约与代扣的技术链路，不能证明协议内容已获确认或具备正式效力。

1. 在线下确认的协议模板和 `service_id` 就绪后，按当前 SDK 类型发起签约并持久化业务签约单，不自行发明 JS API 入参或协议内容。
2. 使用 [签约结果回调](server/openapi/payment/sign-result-callback.md) 和 [查询签约订单](server/openapi/payment/query-sign-order.md) 收敛签约状态。
3. 仅在签约已确认可用时，使用对应用户 SessionToken 调用 [创建协议支付订单](server/openapi/payment/create-sign-pay.md)；SessionToken 边界按 [登录认证指南](auth.md) 实现。
4. 使用 [查询协议支付订单](server/openapi/payment/query-sign-pay.md) 和 [代扣支付结果回调](server/openapi/payment/withhold-payment-result-callback.md) 收敛每笔代扣。
5. 处理中代扣因业务取消需要终止时调用 [关闭协议支付订单](server/openapi/payment/close-sign-pay.md)；已支付订单走退款。
6. 终止长期授权时调用 [解除签约](server/openapi/payment/terminate-sign.md)。关闭单笔代扣和解除长期签约不得混用。

保存用户授权依据、签约状态和每笔代扣的业务原因。涉及真实授权或扣款时遵守“环境与确认边界”。

## MCP Tool 与运行态 Skill

- Tool 创建业务订单或返回支付入口，不直接声称支付完成。
- Tool result 只返回稳定业务单号、已收敛状态和展示所需数据。
- 用户确认商品、服务或金额时，先由卡片/Page 展示服务端订单摘要，再由用户操作进入收银台。
- 查询 Tool 读取业务服务端状态；必要时由服务端查询平台，不由模型根据对话推断结果。
- 退款、关闭代扣和解约 Tool 必须校验明确用户意图、业务权限和幂等键。
- 运行态 Skill 只声明实际完成并验证的能力。

## 可观测性与测试

为支付链路生成业务 trace ID，使前端请求、业务订单、平台请求、回调和对账任务可关联。日志记录动作类型、脱敏单号、状态迁移、耗时、平台 log ID、验签结果、幂等命中和查询原因；不记录密钥、Token、完整签名、完整回调 Body、完整支付明细或非必要用户信息。数据边界读取 [用户数据合规](data-compliance.md)。

至少验证：

- 篡改前端金额不改变服务端订单，重复创建和重复点击命中幂等结果。
- `requestOrder` 失败，以及 `getOrderPayment` 取消、失败、超时、返回和页面重建。
- 回调正常、重复、乱序、签名/证书错误、timestamp 过期和 nonce 重放。
- 查询先于回调、回调先于查询、回调缺失后主动查询、服务重启后恢复。
- 支付成功但未履约时不通知；真实履约后只产生一次有效通知。
- 包含退款时验证全额、部分、重复、超额拒绝和回调缺失后的查询。
- 包含协议支付时验证未签约拒绝、重复回调、处理中、失败、关闭和解约。
- 日志能用 trace ID 与平台 log ID 排障且没有敏感信息。

## 最终交付模板

完成实现或阶段性交付时，按以下结构汇报，删除不适用项，不把计划描述成已完成：

```markdown
### 支付接入结果
- 范围：普通支付 / 退款 / 协议支付；明确排除：...
- 环境与验证：fixture / sandbox / 正式联调；实际跑通：...
- 组件链路：Page/Widget -> 业务服务 -> 平台；MCP Tool：...
- 业务接口与回调：创建订单 ...；查询 ...；支付回调 ...；履约 ...
- 持久化：订单 ...；退款 ...；回调幂等与防重放 ...
- 安全：凭据位置 ...；签名/验签 ...；日志脱敏 ...
- 测试：通过 ...；未验证 ...
- 阻塞或下一步：缺少的权限、证书、Endpoint 或正式联调动作 ...
```

后端不在当前仓库时，不伪造实现或声称已接通。写清后端需要提供的 Endpoint、契约、持久化字段、回调 URL、调用时机和安全要求，并让前端依赖可替换的 client abstraction。

## 完成判据

只有同时满足以下条件，才能把对应范围描述为完成：

- 凭据、证书、订单和回调地址与所选环境一致，结论没有超出实际验证范围。
- 可信金额、业务单号、退款金额和最终状态由服务端控制并持久化。
- 前端执行 `requestOrder -> getOrderPayment -> 查询业务服务端`，未把 JS API 返回当成支付成功。
- 已验签回调与主动查询能幂等收敛状态，并覆盖重复、乱序、丢失和重启恢复。
- 支付与履约分离，真实交付后才通知履约完成。
- 退款和协议支付只在需求范围内实现，并覆盖查询、回调和失败恢复。
- 密钥、Token、签名和支付数据没有泄露到不应出现的位置。
- 关键失败、对账及恢复路径已有与当前阶段相称的测试。
