---
name: xiaodou-open-platform-integration
description: 小豆支付开放平台商家接入 Skill。用于帮助商家开发者（及其 AI 编码助手）把开放平台支付接起来：RSA2 请求签名（XD-Signature-v1）、下单/重拉起/查单/关单/退款、收银页终端能力矩阵（PC 扫码 / 手机支付宝手势主按钮 / 手机微信外推荐路径 / 微信内零点击）、平台事件通知验签（XD-Webhook-v1）与 eventId 去重、**平台响应验签（XD-Response-v1）**、错误码排障、上线检查清单与凭证轮换（含密钥标识 kid 格式与本地派生）。触发词：开放平台接入、商家 API、XD-Signature-v1、XD-Response-v1、XD-Webhook-v1、qrCode 渲染、payUrl 中转链、收银页分端、多到账账号、比例分账、上线检查清单、密钥轮换、kid 密钥标识、公钥指纹、notifyUrl 回调地址、returnUrl 返回商家、商家小票、配置提交即生效、feeProjection 费率投影。
---

# 小豆支付开放平台 · 商家接入 Skill

> 本 Skill 与同仓 `docs/` 是**同一份内容源的两个形态**：`docs/` 给人读，本目录给 AI 编码助手直接消费。两者**同属一个 `skillVersion`**——但**两个下载包各有各的 `contentHash`**（文档包 ≠ Skill 包），且 **Skill 包内不含 `MANIFEST.json`**。核对版本请以门户下载页/发布记录登记的那一对（包 ↔ hash）为准，不要拿一个包的 hash 去对另一个包。
> 术语：本 Skill 是给**商家开发者**用的接入资料；「支付机构官方 Skill」是另一件东西，不要混用。

## 何时用

商家要「把开放平台支付接进自己的服务端/页面」时用。典型任务：

- 写一个能通过验签的下单请求（六头 + 规范串）。
- 决定收款页怎么呈现（自渲染官方收款码 / 渲染收银页二维码 / 302 跳转）。
- 接平台事件通知并做去重与幂等落账。
- 排一个 `401/403/409/422/429/503`。
- 上线前逐条自检；或轮换签名密钥。

## 铁律（先记这七条，能避免绝大多数事故）

1. **签名只认规范串**：请求 11 行、响应 8 行、事件 7 行，**三套不同**，不要复用同一段拼串代码。请求与响应两侧的 `Content-Type` 都取「小写去 `;` 参数」后的值——**没带这个头时该行是空行**（`GET` 且无请求体就是这样）；**所有 `POST` 必须带 `Content-Type: application/json`**（含无请求体的重拉起/关单）。
2. **重试复用同一个 `requestId` 与同一份请求体**（body 逐字节相同，含「不传 body」本身），**但 nonce 必须换新**；`409` 的正确动作是**查单**，不是换单号重下单。业务摘要成员见 `references/api-workflow.md` §2——**`returnUrl` 在摘要内，改了再重试就是 `409`**。
3. **收银页打开 / 回跳 / 用户点完成都不是支付终态**；终态只认查单与经验签的事件。
4. **`UNKNOWN` 不判死**：不重新下单、不重发退款、不提示买家失败，等事件或查单收敛。
5. **收款材料只从响应取**：`payUrl`/`qrCode` 用原值，不拼接、不转发第三方、不进日志；查询接口两者**恒为空**，需要新码走重拉起。
6. **事件必须用原始字节验签 + 按 `eventId` 去重**，成功才回 2xx 且 body 字节正好是 `success`。
7. **私钥永不进浏览器、不进仓库、不进日志**；费率**不要写死进代码**：入账以每单 `feeProjection` 为准（对外统一档与手续费见仓库 `README.md`），单独约定过的费率与限额以门户「费率说明」页/平台答复为准。

**能力事实（2026-10-17 口径）**：**微信 JSAPI 已开放**——仅限买家在微信内打开收银页时调起；微信外手机浏览器与商家 App 内没有「直接拉起」形态。**比例分账已支持**：`SINGLE` / `ROUND_ROBIN` / `RATIO` 三种方式，`RATIO` 为 2–4 个参与账号、配置面每项 `0 < shareBps < 10000`、合计 10000bps，`RENORMALIZE` 后单笔生效腿可只剩 1 条；查询接口只读返回 `routing.ratios` 配置快照，写面在商家后台。

## 工作流

```text
① 读 references/signature-spec.md   → 用 examples/ 里对应语言实现签名，先跑通 golden 向量
② 读 references/api-workflow.md     → 下单 → （需要新材料时）重拉起 → 查单；幂等三层怎么用
③ 读 references/terminal-matrix.md  → 决定收款页形态（PC 扫码 / 手机分端 / 微信推荐路径 / 宿主内跳浏览器）
④ 读 references/webhook-verification.md → 接事件、验签、去重、幂等落账、返回 success
⑤ 读 references/error-codes.md      → 按 code 分派错误，不要按 message 判错
⑥ 读 references/go-live-checklist.md → 上线前逐条自检（平台不提供沙箱）
⑦ 读 references/key-management.md   → 生成/轮换/停用/应急吊销；注意轮换是原子的
```

## 资产

| 资产 | 路径 | 在 Skill 包里可取到？ | 说明 |
| --- | --- | --- | --- |
| 四语言签名示例 | `examples/{node,java,php,python}/`（索引见 [`examples/README.md`](./examples/README.md)） | ✅ 在包内 | 与 `docs/` 同源的参考实现：请求签名四语言 + 事件验签 / **响应验签**（Node），含 golden 向量 |
| 人读文档（14 篇） | `docs/` | ❌ 不在包内 | 含 API 参考与终端能力矩阵；用门户「开发者文档」页的「下载文档包」或公开仓的 `docs/` |
| 本地收银 demo | `demo/h5-cashier/` | ❌ 不在包内 | 零依赖，loopback + mock，全链可跑（含负向用例）；用公开仓全量目录 |

> **路径基准（别踩这个坑）**：本 Skill 有**两种落地形态**，可用路径不一样——
> - **Skill 下载包**（门户「开发者文档」页的「下载接入包（含示例与 Skill）」按钮打出的 `.tar.gz`）：按登记文件集只含 `skill/**` 与 `examples/**`，**`docs/`、`demo/` 与仓库根 `README.md` 不在包内**。所以本文档、`references/` 与 `examples/README.md` 里所有指向 `docs/…`、`demo/…`、`README.md` 的引用在这个形态下**都取不到**——请如实告诉用户「人读文档要另外下载」，不要凭路径猜文件位置、更不要因此编造内容。
> - **公开仓（完整目录）**：`skill/` 与 `examples/`、`docs/`、`demo/` 同级存在，三者都能取到。
> - 只把 `skill/` 目录单独拷进编码助手的技能目录时，连 `examples/` 也会取不到（`skill/examples/` 里的相对引用按包根解析）；这时把 `examples/` 一并带上。
>
> **不要**把示例代码复制进 `skill/` 内形成第二份源——那一定会漂移。

## 边界（不要做的事）

- 不要照抄 demo 的样式去仿造平台收银页；真实收银页由平台承载且**品牌中立**。
- 不要为「微信在手机浏览器直接拉起支付」「微信 PC 原生扫码直出付款码」写代码——**平台没有这些形态**（微信支付只能在微信内调起；PC 扫码是中转链）。
- 不要在自有 App 的 WebView 内嵌收银页；必须跳系统浏览器。
- 不要把 mock 全绿当成「生产可用」：**mock 只证明签名与解析没错**，生产首单必须真实验证。
- 平台**暂不提供**沙箱/测试应用/回调模拟器；替代路径=本地 demo + 上线检查清单 + 首单人工陪跑（不承诺 SLA）。
