Add WeChat Pay local skills
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# 商户模式产品介绍
|
||||
|
||||
> 来源:微信支付 V2 文档中心 · 委托代扣([产品介绍](https://pay.weixin.qq.com/doc/v2/merchant/4011986647.md) / [接入流程](https://pay.weixin.qq.com/doc/v2/merchant/4011986709.md) / [周期扣费](https://pay.weixin.qq.com/doc/v2/merchant/4011986682.md))。本页面只讲产品定位、业务模式与签约方式选型,**不含**密钥、回调 URL、证书等接入细节,那些请见 [📄 商户模式开发参数与业务规则](../接入指南/开发参数与业务规则.md)。
|
||||
|
||||
## 一、产品概览
|
||||
|
||||
委托代扣是微信支付为商户和用户提供的、**可以在交易场景之外完成支付**的能力。用户在签约阶段一次性完成"授权扣款"协议的签订,后续商户即可按约定规则**无需用户每次输入密码**地从用户微信账户发起扣款。
|
||||
|
||||
适用对象:
|
||||
|
||||
- **直连商户**(本文档)
|
||||
- 需要服务商身份接入的请见 [📄 服务商模式产品介绍](../../2-服务商/产品选型/产品介绍.md)
|
||||
|
||||
> 委托代扣的"扣款入口"分两类:**纯签约**(先签约,再发起独立的「申请扣款」接口)和**支付中签约**(一次完成"支付 + 签约")。两类入口签约成功后均使用同一套「申请扣款」接口完成后续扣款。
|
||||
|
||||
## 二、业务模式选型(先决定走哪种)
|
||||
|
||||
委托代扣**仅针对以下两类业务场景开放**,先确定业务模式,再决定签约方式:
|
||||
|
||||
| 业务模式 | 场景特点 | 解决问题 | 方案示例 | 扣款延迟 | 扣前通知 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **周期扣款(自动续费)** | 基于规则而非用户行为触发的、可周期性或多次扣款模式;主要是**预付费**场景,特殊民生类除外(如水电煤气缴费) | 减少用户重复的支付操作,保证服务的如期续约 | 会员续费 | 有延迟 | **必须**下发,二选一:通知后 24 小时自动扣费 / 预扣费通知 |
|
||||
| **先享后付(免密支付)** | 基于用户行为触发,且是商户先提供服务,服务完结后再扣款的**后付费**模式 | 解决用户在场景内不方便支付的问题 | 乘车码、停车后扣费 | **无延迟**,立即扣款 | 不下发 |
|
||||
|
||||
### 周期扣款的两种通知方式
|
||||
|
||||
只允许选其中一种(**两种模式只能二选一**),申请模板时确定:
|
||||
|
||||
| 通知方式 | 特点 | 商户接入工作 | 注意事项 |
|
||||
| --- | --- | --- | --- |
|
||||
| **通知后 24 小时扣费**(默认) | 商户调用「申请扣款」后微信自动下发通知,24 小时后自动完成扣费 | 商户**只需**调用「申请扣款」 | 受理后订单 24 小时内持续"处理中",**勿重复扣款**避免触发频率限制 |
|
||||
| **预扣费通知模式** | 使用单独的「预扣费通知」V3 接口下发消息,扣费等待期 + 可扣费期分离 | 必须**先调【预扣费通知 API】**再调「申请扣款」 | 通知日 + 第二日为扣费等待期,第 3-9 天为可扣费期,仅 7:00-22:00 可发起扣费 |
|
||||
|
||||
> 申请委托代扣模板时一般为「24 小时自动扣费」模式,如需「预扣费通知」模式,需联系运营协助申请修改。
|
||||
|
||||
### 首次签约扣款的特殊规则
|
||||
|
||||
如果用户为**首次签约**(含解约后重新签约),从用户签约成功时间开始算:
|
||||
|
||||
- **12 小时内**发起扣款 → **立即执行,无延迟**(两种通知模式都适用)
|
||||
- 超过 12 小时 → 按通知模式各自规则执行(24h / 预扣费通知)
|
||||
|
||||
## 三、签约方式选型
|
||||
|
||||
签约方式按**两个独立维度**划分,**先选维度 1(签约时机),再按维度 2(用户终端)选具体接入方式**:
|
||||
|
||||
- **维度 1 · 签约时机**:决定签约和首次扣款的关系,分为**纯签约**和**支付中签约**两大类。
|
||||
- **维度 2 · 用户终端**:决定客户端用什么 SDK / 跳转方式唤起签约页,仅在「纯签约」大类下需要选;「支付中签约」的客户端形态由你选用的基础支付 `trade_type` 决定,与此处分类无关。
|
||||
|
||||
> **术语速查**(下方"接入流程"列里的英文 API 都在这里):
|
||||
>
|
||||
> - **WXLaunchMiniProgram**:微信开放平台提供的"从原生 APP 调起微信小程序"API。委托代扣用它跳到"微信支付签约小程序"完成签约,是 APP 端签约**当前唯一**的官方推荐方案。
|
||||
> - **wx.navigateToMiniProgram**:微信小程序框架内置 API,作用是从一个小程序跳到另一个小程序。本场景下用于从你的业务小程序跳到微信支付签约小程序。
|
||||
> - **`redirect_url`**:H5 预签约接口返回的一次性签约页地址,前端浏览器跳到该 URL 即可唤起签约。
|
||||
> - **`pay/contractorder`**:商户模式独有的"支付 + 签约一次完成"统一下单接口(服务商模式不支持)。
|
||||
|
||||
### 维度 1:按签约时机选大类
|
||||
|
||||
| 大类 | 触发时点 | 后续扣款流程 | 适用场景 | 备注 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **纯签约** | 签约和支付完全解耦:用户先在签约页授权 → 服务端单独存 `contract_id` | 后续每次扣款都调「申请扣款」`pappayapply` | 绝大多数场景(会员续费、先享后付等);签约阶段没有金额发生 | 按维度 2 选 4 种接入方式之一 |
|
||||
| **支付中签约** | 用户**首次支付**的同时完成签约(`pay/contractorder` 一次完成"支付 + 签约") | 第一笔即支付成功,**后续**仍调「申请扣款」`pappayapply` | 想让用户首次扣款"无感"且能开通对应基础支付产品权限 | **服务商模式不支持**;客户端形态跟随你选用的基础支付 `trade_type`(JSAPI / APP / H5 / Native / 小程序),无需在维度 2 中再选 |
|
||||
|
||||
### 维度 2:纯签约下按用户终端选接入方式
|
||||
|
||||
> "业务流程"列以**用户感知**为主线讲清"用户在哪里点 → 跳到哪 → 看到什么 → 微信怎么回应 → 签完干嘛";技术细节(具体 API 路径、SDK 名、参数有效期等)放在"备注"列,不抢主线。
|
||||
|
||||
| 接入方式 | 用户场景 | 业务流程(用户/商户/微信 三方协作) | 是否需邮件单独申请 | 备注(接口与实现要点) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **公众号纯签约** | 微信内 H5(公众号网页) | ① 用户在商户公众号网页点击"开通免密支付";② 浏览器跳转到**微信内置的签约页**;③ 用户在签约页确认开通,微信展示"开通成功";④ 商户后端收到"签约结果通知"(含 `contract_id`),落库;⑤ 后续商户调「申请扣款」即可在不打扰用户的情况下扣费。 | 否 | 接口:`papay/entrustweb`(GET 拼好签名后直接跳转);在微信内 H5 唤起,**不涉及浏览器域名白名单**,无需配置签约域名 |
|
||||
| **APP 纯签约** | 商户原生 APP(Android / iOS / **鸿蒙**) | ① 用户在商户 APP 内点击"开通免密支付";② APP 调起"微信支付签约小程序",用户进入微信完成授权;③ 关闭签约小程序自动回到商户 APP;④ 商户后端收到"签约结果通知",落库;⑤ 后续商户调「申请扣款」无感扣费。 | **是**,需单独申请 APP 签约权限 | 接口:服务端先调 `papay/preentrustweb` 取 `pre_entrustweb_id`,APP 端用 `WXLaunchMiniProgram` 调起签约小程序(`path` 携带 `pre_entrustweb_id`)。**唯一支持鸿蒙**的签约方式 |
|
||||
| **小程序纯签约** | 微信小程序内 | ① 用户在商户小程序内点击"开通免密支付";② 跳转到"微信支付签约小程序",用户在其中确认授权;③ 自动跳回商户小程序,`onShow` 拿到本次返回结果(成功 / 取消);④ 商户后端收到"签约结果通知"(**前端结果仅供 UI 提示,最终状态以异步通知为准**);⑤ 后续商户调「申请扣款」无感扣费。 | 否 | 接口:服务端 `papay/preentrustweb`,客户端 `wx.navigateToMiniProgram`(需在 `app.json` 的 `navigateToMiniProgramAppIdList` 中加入签约小程序 appid `wxbd687630cd02ce1d`) |
|
||||
| **H5 纯签约** | 浏览器 H5(含外投广告 APP) | ① 用户在浏览器 H5 页面点击"开通免密支付";② 商户后端用一次"预签约"取到本次签约的一次性入口地址;③ 浏览器跳到该地址,用户在页面内扫码 / 唤起微信完成授权;④ 商户后端收到"签约结果通知",落库;⑤ 后续商户调「申请扣款」无感扣费。 | **是**,需单独申请 H5 签约权限并**配置签约域名白名单** | 接口:服务端 `papay/h5entrustweb`(建议带上用户 IP / UA 等风控参数)取 `redirect_url`,浏览器 `location.href = redirect_url` 跳转。`redirect_url` 有效期 10 分钟、且**仅可访问一次** |
|
||||
|
||||
> **存量 `OpenBusinessWebview` 兼容说明**:APP 端签约还有一个旧 SDK `OpenBusinessWebview`,自 2025-09-22 起官方已停止新申请。**仅在该日期前已申请过该权限的存量模板**仍可继续运行,但官方建议尽快迁移到 `WXLaunchMiniProgram`。新接入或新模板**无需了解此 SDK**,直接走上表的"APP 纯签约"即可。
|
||||
|
||||
### 支付中签约的接入要点
|
||||
|
||||
支付中签约不再细分用户终端,因为它直接复用基础支付的 `trade_type` 来决定客户端形态:
|
||||
|
||||
- **业务流程**:① 用户在商户下单页点"立即支付"(首次);② 商户后端用一次接口同时返回"支付参数 + 签约信息";③ 用户在客户端按选用的 `trade_type` 调起支付(JSAPI / APP / H5 / Native / 小程序,与基础支付完全相同);④ 用户支付成功 = 同时完成首次扣款 + 签约协议生效;⑤ 商户后端收到**两类**异步通知(扣款通知 + 签约通知),分别落库订单与协议;⑥ 后续商户调「申请扣款」即可无感扣费。
|
||||
- **接口与实现要点**:服务端调 `pay/contractorder`(一次下单同时返回支付参数 + 签约信息),客户端调起方式与对应 `trade_type` 的基础支付完全一致。
|
||||
- **关键前置**:必须**已开通对应基础支付产品权限**(例如选 JSAPI → 已开通 JSAPI 支付,选 APP → 已开通 APP 支付)。
|
||||
- **不适用**:仅商户模式支持,**服务商模式不可用**——服务商下若需"支付 + 签约一次完成",需子商户自行接入直连模式。
|
||||
|
||||
### 选型决策树
|
||||
|
||||
1. **维度 1 · 是否要在用户首次扣款时同时完成签约?**
|
||||
- 是 → **支付中签约**(需开通对应基础支付产品权限;服务商模式不支持)
|
||||
- 否 → 进入第 2 步选纯签约的具体方式
|
||||
2. **维度 2 · 用户接入终端是什么?**
|
||||
- 微信公众号 H5 → **公众号纯签约**
|
||||
- 微信小程序 → **小程序纯签约**
|
||||
- 浏览器 H5 → **H5 纯签约**(需单独申请权限 + 配置签约域名)
|
||||
- 原生 APP(Android / iOS / 鸿蒙)→ **APP 纯签约**(用 `WXLaunchMiniProgram` 调起签约小程序;存量 `OpenBusinessWebview` 模板可继续运行,但建议迁移)
|
||||
|
||||
## 四、接入前置条件 / 准入 checklist
|
||||
|
||||
按下表全部完成后再进入开发环节(参数获取与具体取值规范详见 [📄 商户模式开发参数与业务规则](../接入指南/开发参数与业务规则.md)):
|
||||
|
||||
| # | 准入项 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 已开通「委托代扣」产品权限 | 详见 [📄 V2 接入流程 · 申请委托代扣权限](https://pay.weixin.qq.com/doc/v2/merchant/4011986709) |
|
||||
| 2 | 已申请并通过审核「委托代扣模板 ID」 | 在商户平台「交易中心 → 高级业务 → 委托代扣模版管理」提交申请,**审核通过后**模板 ID 才能用于扣款接口开发。申请页关键字段:业务类型(免密支付 / 自动续费;预约扣费暂未开放)、模板名称(展示在签约页"开通账号"处)、扣费类型("为" = 每次扣固定金额;"不高于" = 每次扣不超过设定上限)、首期优惠价格(可选)、解约结果通知 URL |
|
||||
| 3 | appid 与商户号已建立绑定关系 | 公众号 / 小程序 / 移动应用 appid 在商户平台「账户中心 → 商户信息 → 关联管理」中绑定 mch_id |
|
||||
| 4 | 选用 H5 / APP 纯签约 → 已申请对应权限 | H5 纯签约需配置签约域名;**APP 纯签约** 需申请并由运营配置(用 `WXLaunchMiniProgram` 调起签约小程序) |
|
||||
| 5 | 选用支付中签约 → 已开通对应基础支付产品权限 | 例如 trade_type=JSAPI 需开通 JSAPI 支付,trade_type=NATIVE 需开通 Native 支付 |
|
||||
| 6 | (周期扣款且选预扣费通知模式)已申请预扣费通知权限 | 默认申请下来的模板是 24 小时自动扣费模式,需运营协助修改为预扣费通知模式 |
|
||||
| 7 | 已配置好回调地址(HTTPS、公网可达、5 秒内应答) | 签约通知 URL 在签约接口的 `notify_url` 传入;扣款通知在「申请扣款」`notify_url` 传入;解约通知 URL 在模板配置中预设 |
|
||||
|
||||
> **限额限频**(by 签约协议):自动续费模板单笔限额按模板配置;测试模板单笔限额 0.1 元、单日限频 100 次。同一用户在同一签约协议下**每天仅可成功扣款 1 次**(失败不计),单周期最多 2 次。
|
||||
>
|
||||
> **可扣费时段**:申请扣款 / 预扣费通知接口仅在北京时间 **7:00-22:00** 可调用;签约时间不影响。
|
||||
>
|
||||
> **沙箱**:委托代扣**没有沙箱环境**,所有调试在生产环境完成(建议先用测试模板 + 0.1 元小金额跑通主流程)。
|
||||
|
||||
## 五、与同类产品的关系
|
||||
|
||||
| 对比项 | 委托代扣(V2,本产品) | V3 微信支付分(先享后付) | V3 自动续费 |
|
||||
| --- | --- | --- | --- |
|
||||
| 业务定位 | 通用授权扣款 | 信用分驱动的"先享后付"专项 | 各类支付产品的"扣款单"形式(V3 接口) |
|
||||
| 协议版本 | V2(XML + MD5/HMAC-SHA256),仅「预扣费通知」走 V3 | V3(JSON + RSA) | V3(JSON + RSA) |
|
||||
| 用户感知 | 签约凭证、扣款凭证 | 服务订单 | 续费订单 |
|
||||
| 是否需要本产品 | 通用场景的事实标准 | 走支付分体系,单独 skill | 走 V3 自动续费,单独 skill |
|
||||
|
||||
如果业务方期望**新接入**且场景允许,建议优先评估 V3 自动续费 / 支付分;只有明确选定走 V2 委托代扣时再使用本 skill。
|
||||
|
||||
## 六、状态流转 / 协议生命周期
|
||||
|
||||
- **签约协议状态**(查询签约关系返回 `contract_state`):`0` 已签约、`1` 未签约、`9` 签约进行中。
|
||||
- **解约方式**(`contract_termination_mode`):`1` 有效期过自动解约(预留功能)、`2` 用户主动解约、`3` 商户 API 解约、`4` 商户平台解约、`5` 注销(用户微信账户注销)、`7` 用户联系客服发起的解约。
|
||||
- **扣款订单状态**(查询订单返回 `trade_state`):`SUCCESS` 支付成功、`REFUND` 转入退款、`NOTPAY` 未支付、`CLOSED` 已关闭、`USERPAYING` 用户支付中、`PAYERROR` 支付失败、`ACCEPT` 已接收等待扣款。
|
||||
- **协议唯一性**:协议维度为「商户号 + 模板 ID」,**同一用户在同一商户号 + 同一模板 ID 下只能有一个生效签约**。先签约成功者保留,后签约会在签约页提示无法签约。
|
||||
- **扣款失败自动关单**:用户零钱 / 银行卡等所有可扣方式均失败后,扣款订单进入 `CLOSED`。如需再次扣费,必须**换商户订单号**重新调用「申请扣款」(同一 `out_trade_no` 不会扣两次)。
|
||||
|
||||
更详细的字段定义和接口报文见 [📄 商户模式接口索引](../示例代码/接口索引.md)。
|
||||
@@ -0,0 +1,217 @@
|
||||
# 商户模式回调处理
|
||||
|
||||
> 本文档为微信支付**委托代扣(API V2)回调处理规范**——委托代扣主体走 V2(XML + MD5/HMAC-SHA256),**与 V3 通用回调(JSON + AEAD_AES_256_GCM)完全不同**,请勿混用。委托代扣涉及的回调全部走本文档;只有「预扣费通知」是 V3 同步接口(不是回调),不在本文档范围内。
|
||||
>
|
||||
> 来源:微信支付 V2 文档中心 ·「[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/merchant/4011984721.md)」 +「[扣款结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011987465.md)」 +「[签约-解约结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011987586.md)」 +「[退款结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011940955.md)」(V2 通用退款回调,委托代扣退款共用)。
|
||||
|
||||
## 一、委托代扣的三类回调
|
||||
|
||||
| # | 回调种类 | 配置位置 | 协议格式 | 是否加密 | 验签算法 | 触发频率 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | **签约 / 解约结果通知**(同一种通知,由 `change_type=ADD/DELETE` 区分) | 签约通知地址 = 各签约接口 `notify_url` 字段;解约通知地址 = **委托代扣模板配置**中预设 | V2 XML,**不加密** | MD5 / HMAC-SHA256(按签约时签名方式) | 0/10/10/10/30/30/30/300×... 单位秒 |
|
||||
| 2 | **扣款结果通知**(成功 / 失败两种报文,字段不同) | 「申请扣款」接口 `notify_url` 字段 | V2 XML,**不加密** | MD5 / HMAC-SHA256 | 0/15/15/30/180/1800×4/3600 单位秒 |
|
||||
| 3 | **退款结果通知** | 「申请退款」接口 `notify_url`;不传则用商户平台「产品中心 → 商户安全 → 安全助手 → 退款回调地址」配置 | V2 XML,**`req_info` 字段 AES-256-ECB 加密**(仅退款回调) | 退款回调**整体不参与签名校验**,靠解密 + 业务字段比对作为可信度判断 | 15s/15s/30s/3m/10m/20m/30m×3/60m/3h×3/6h×2,总计约 24h4m |
|
||||
|
||||
> ‼️ 三类回调可以**复用同一 `notify_url`**,但业务侧必须按报文中的字段路由分发:
|
||||
> - **`change_type` 字段存在** → 签约/解约结果通知(按 `change_type=ADD/DELETE` 进一步分发)
|
||||
> - **`req_info` 字段存在** → 退款结果通知
|
||||
> - **`transaction_id` + `contract_id` 存在但无 `change_type`** → 扣款结果通知
|
||||
|
||||
## 二、`notify_url` 配置规范
|
||||
|
||||
> 来源:[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/merchant/4011984721.md)
|
||||
|
||||
| 必须满足 | 说明 |
|
||||
| --- | --- |
|
||||
| 必须是商户自己系统的真实地址 | 不能填接口文档/Demo 上的示例地址 |
|
||||
| 必须 `https://` 或 `http://` 开头的**完整全路径** | 不能只写域名(如 `http://www.weixin.qq.com` 错),必须带具体路径(如 `https://www.merchant.com/wxpay/notify`) |
|
||||
| **不能携带 query 参数** | URL 中带 `?xxx=yyy` 会被拒 |
|
||||
| **不能填本地或内网 IP** | `127.0.0.1` / `192.168.x.x` / `localhost` 一律不可用 |
|
||||
| 必须公网可访问、域名已 ICP 备案 | 国内服务器必须;如走专线接入可用专线 NAT IP 或私有回调域名(http) |
|
||||
|
||||
### 委托代扣专属:三种 notify_url 的来源
|
||||
|
||||
| URL | 来源 | 备注 |
|
||||
| --- | --- | --- |
|
||||
| 签约通知 URL | 签约接口(公众号 / APP 预签约 / 小程序 / H5 / 支付中签约)`notify_url` 字段 | 各签约请求独立配置;签约失败不通知 |
|
||||
| 解约通知 URL | 委托代扣模板申请页中的"解约结果通知 URL"字段 | 模板维度,不能在解约接口里临时改;可在商户平台「交易中心 → 高级业务 → 委托代扣模版管理」修改模板时调整 |
|
||||
| 扣款通知 URL | 「申请扣款」接口 `notify_url` 字段 | 每次申请扣款独立配置 |
|
||||
| 退款通知 URL | 「申请退款」接口 `notify_url` 字段 | 不传则使用商户平台预设 |
|
||||
|
||||
> ‼️ 「解约结果通知 URL」**不是**给用户的提醒地址(不会向用户发"您已解约"消息),是给商户后端的异步通知地址。
|
||||
|
||||
## 三、回调通知报文示例(按种类)
|
||||
|
||||
### 3.1 签约 / 解约结果通知(不加密)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id>10010404</mch_id>
|
||||
<contract_code>100001256</contract_code>
|
||||
<openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6ua]]></openid>
|
||||
<plan_id><![CDATA[123]]></plan_id>
|
||||
<change_type><![CDATA[ADD]]></change_type>
|
||||
<operate_time><![CDATA[2015-07-01 10:00:00]]></operate_time>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
关键字段:
|
||||
|
||||
- `change_type=ADD` → 签约成功;`change_type=DELETE` → 解约成功
|
||||
- `openid` 是签约用户在 `appid` 下的唯一标识——**商户记录的签约用户 openid 必须以这里为准**,**禁止用业务侧自记录的 openid**(避免扣款用户和签约用户对不上)
|
||||
- `contract_id` 是签约成功后微信返回的协议 ID,后续「申请扣款」必传
|
||||
- 解约时多 `contract_termination_mode`:`1` 有效期过自动解约(预留)/`2` 用户主动解约 /`3` 商户 API 解约 /`4` 商户平台解约 /`5` 用户微信账户注销 /`7` 用户联系客服解约
|
||||
|
||||
### 3.2 扣款结果通知(成功 / 失败 / 不加密)
|
||||
|
||||
**扣款成功**(`return_code=SUCCESS` + `result_code=SUCCESS`)核心字段:`appid` / `mch_id` / `out_trade_no` / `transaction_id` / `total_fee` / `cash_fee` / `bank_type` / `openid` / `time_end` / `trade_state=SUCCESS` / `contract_id`,外加签名 `sign`。
|
||||
|
||||
**扣款失败**(`return_code=SUCCESS` + `result_code=FAIL`)核心字段:`appid` / `mch_id` / `out_trade_no` / `contract_id` / `err_code`(如 `NOTENOUGH` / `RULELIMIT` / `BANKERROR` / `USER_ACCOUNT_ABNORMAL` 等)/ `err_code_des`,外加签名 `sign`。
|
||||
|
||||
> ‼️ 「申请扣款」接口返回 `result_code=SUCCESS` 仅代表受理成功,**最终扣款成功 / 失败必须等扣款回调或调「查询订单」确认**。
|
||||
|
||||
**SYSTEMERROR 兜底流程**:扣款回调中 `err_code=SYSTEMERROR` 时,先调「查询订单」:若 `trade_state` ≠ `CLOSED`,必须先调「关闭订单」关掉旧单,再**换新 `out_trade_no`** 重新申请延迟扣费(24 小时后执行)。
|
||||
|
||||
### 3.3 退款结果通知(V2 通用规范,`req_info` 加密)
|
||||
|
||||
**外层 XML**:
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code>SUCCESS</return_code>
|
||||
<appid><![CDATA[wx8888888888888888]]></appid>
|
||||
<mch_id><![CDATA[1900000109]]></mch_id>
|
||||
<nonce_str><![CDATA[5K8264ILTKCH16CQ2502SI8ZNMTM67VS]]></nonce_str>
|
||||
<req_info><![CDATA[T87GAHG17TGAHG1TGHAHAHA1Y1CIOA9UGJH1GAHV871HAGAGQYQQPOOJMXNBCXBVNMNMAJAA]]></req_info>
|
||||
</xml>
|
||||
```
|
||||
|
||||
**`req_info` 解密步骤**(与所有 V2 退款回调共用):
|
||||
|
||||
1. 对 `req_info` 字段做 **Base64 解码**得到密文
|
||||
2. 取**商户 APIv2 密钥**做 **MD5**,转为 32 位**小写**字符串作为 AES key
|
||||
3. 用该 key 对密文做 **AES-256-ECB 解密**(PKCS7Padding)得到内层 XML
|
||||
4. 解析内层 XML 得到 `transaction_id` / `out_trade_no` / `refund_id` / `out_refund_no` / `total_fee` / `refund_fee` / `refund_status` / `success_time` 等
|
||||
|
||||
**`refund_status` 取值**:`SUCCESS` 退款成功 / `CHANGE` 退款异常 / `REFUNDCLOSE` 退款关闭。
|
||||
|
||||
> ‼️ 退款回调**没有** `sign` 字段,整体不参与签名校验。可信度依赖:① `req_info` 用商户 APIv2 密钥能成功解密(错的密文解不出来);② 解密后的 `out_trade_no` / `out_refund_no` 与本地订单匹配;③ `refund_fee` 与本地申请退款金额一致。
|
||||
|
||||
## 四、商户应答规范
|
||||
|
||||
> 来源:[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/merchant/4011984721.md)
|
||||
|
||||
### 4.1 应答报文格式
|
||||
|
||||
**必须返回 XML**,字段名与接口文档一致,**报文前后和字段标签中间不能含特殊字符**(包括空白行、`\n` 等):
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
### 4.2 常见错误格式
|
||||
|
||||
| 错误 | 错误示例 |
|
||||
| --- | --- |
|
||||
| 内容为空 / 非 XML | `ok` / `success` / `支付成功` |
|
||||
| XML 报文中间含 `\n` 等转义字符 | `<xml>\n<return_code>...\n</xml>` |
|
||||
| 返回 JSON | `{"return_code":"SUCCESS","return_msg":"OK"}` |
|
||||
| 返回了整个页面的 HTML | (框架默认渲染了整页 HTML) |
|
||||
|
||||
### 4.3 应答时效与状态码
|
||||
|
||||
- **HTTP 状态码必须 2xx**(200/204);非 2xx 微信会重试
|
||||
- **必须 5 秒内应答**,否则视为失败
|
||||
- 处理失败建议返回**非 2xx**触发重试;幂等场景已处理过的,直接返回成功 XML
|
||||
- `notify_url` 的代码逻辑**不能做登录态校验**(必须能匿名访问)
|
||||
|
||||
## 五、必须做的安全与可靠性处理
|
||||
|
||||
| # | 项 | 怎么做 |
|
||||
| --- | --- | --- |
|
||||
| 1 | **签名验签**(仅签约 / 解约 / 扣款回调) | 用 V2 签名算法(MD5 / HMAC-SHA256,密钥 = 商户 APIv2 密钥)按字段 ASCII 字典序拼接验证 `sign`;详见 [📄 商户模式签名与验签规则](./签名与验签规则.md) |
|
||||
| 2 | **退款回调可信度判断**(退款回调没 sign) | `req_info` 用 `md5(APIv2 密钥).lowercase()` 能成功 AES-256-ECB 解密,解密后 `out_trade_no` / `out_refund_no` 与本地匹配,`refund_fee` 与本地申请退款金额一致 |
|
||||
| 3 | **金额比对** | 扣款回调中 `total_fee` 必须与本地订单金额比对一致;退款回调中 `refund_fee` 必须与本地申请退款金额一致;金额字段类型用 `int` / `long`(单位分),**禁止 `double` / `float`** |
|
||||
| 4 | **openid 校准** | 商户记录的签约用户 openid 必须以**回调中返回的 `openid`** 为准,禁止用业务侧自记录的值(避免签约 / 扣款用户对不上) |
|
||||
| 5 | **幂等去重** | 同一通知可能多次送达:先按业务唯一标识(签约 / 解约:`contract_code` 或 `contract_id` + `change_type`;扣款:`out_trade_no`;退款:`out_refund_no`)查询本地状态,已处理直接返回成功 XML |
|
||||
| 6 | **并发锁** | 在状态检查与处理之前用 Redis 锁 / 行锁 / `synchronized` 等加锁,避免同一通知并发处理造成数据混乱 |
|
||||
| 7 | **业务路由分发** | 三类回调如复用同一 URL,按 §一 表格中的字段判断分发;签约 / 解约再按 `change_type=ADD/DELETE` 分发 |
|
||||
|
||||
## 六、回调 IP 白名单
|
||||
|
||||
> 商户侧对回调 IP 有防火墙策略限制时,需放行下方 IP 段。
|
||||
|
||||
| 出口位置 | IP 网段 |
|
||||
| --- | --- |
|
||||
| 上海电信 / 联通 / CAP | `101.226.103.0/25` / `140.207.54.0/25` / `121.51.58.128/25` |
|
||||
| 深圳电信 / 联通 / CAP | `183.3.234.0/25` / `58.251.80.0/25` / `121.51.30.128/25` |
|
||||
| 香港 | `203.205.219.128/25` |
|
||||
| **委托代扣 签约 / 解约通知出口** | `101.226.233.128/25` |
|
||||
| 退款 / 分账动账通知 IP | `175.24.214.208`、`175.24.211.24`、`175.24.213.135`、`109.244.180.23`、`114.132.203.119`、`43.139.43.69` |
|
||||
|
||||
同时关闭 WAF / CC 防护对回调 URL 的拦截,避免误判为恶意请求。
|
||||
|
||||
## 七、收不到回调的排查清单
|
||||
|
||||
按优先级逐项排查:
|
||||
|
||||
1. **前置配置缺失**
|
||||
- 签约接口未传 `notify_url` / 模板未配置解约通知 URL / 申请扣款未传 `notify_url`
|
||||
2. **地址格式错误**
|
||||
- 不是 `https://` / `http://` 开头 / 只有域名缺路径 / URL 携带参数 / 用了内网或本地 IP
|
||||
3. **域名 / 解析问题**
|
||||
- 国内域名未 ICP 备案 / DNS 解析失效
|
||||
4. **网络与服务器连通性**
|
||||
- 防火墙 / 安全组未对回调 IP 段开白名单(详见 §六)
|
||||
- WAF / CC 防护误拦
|
||||
- 网络丢包 / 延迟 > 3 秒导致超时
|
||||
- CDN / 反向代理(Nginx / Cloudflare)未将请求正确转发到后端
|
||||
5. **回调处理逻辑问题**
|
||||
- `notify_url` 做了登录态校验
|
||||
- 应答非 2xx 或非 XML(如返回 `ok` / JSON / HTML 页面)
|
||||
- 处理超时(> 5 秒)
|
||||
- 未做幂等导致重复处理把状态搞乱
|
||||
6. **URL 前后有空格** → DNS 解析失败
|
||||
7. **微信不提供"回调发送日志"查询**:自行查 access log / Nginx log;同时调「查询订单」/「查询签约关系」/「查询退款」兜底确认实际状态
|
||||
|
||||
## 八、错误处理策略
|
||||
|
||||
| 错误场景 | 处理策略 |
|
||||
| --- | --- |
|
||||
| 应答超时 / 暂时失败 | 返回非 2xx,让微信按重试频率(见 §一表格)继续重试;业务侧补偿处理 |
|
||||
| 业务字段校验失败(金额不一致 / openid 不匹配) | 返回 200 + `<return_code>FAIL</return_code><return_msg>...</return_msg>`,**同时告警人工介入** |
|
||||
| `req_info` 解密失败(退款回调) | 检查 APIv2 密钥与商户平台是否一致(最常见:密钥重置后代码未同步);密文未截断;MD5 后是否取 32 位**小写**作为 AES key |
|
||||
| V2 签名验签失败(签约 / 解约 / 扣款回调) | 检查 APIv2 密钥;字段是否按 ASCII 字典序排序;签名方式是否与签约时一致(H5 默认 HMAC-SHA256,其他默认 MD5);详见 [📄 商户模式签名与验签规则](./签名与验签规则.md) |
|
||||
| 业务异常但已收到通知 | **建议返回 2xx + 成功 XML**(避免重试堆积),通过告警系统人工介入 |
|
||||
|
||||
## 九、幂等设计
|
||||
|
||||
- 业务侧所有写操作必须使用业务侧生成的唯一请求号
|
||||
- 签约 / 解约:`contract_code` + `change_type` 联合唯一
|
||||
- 扣款:`out_trade_no` 唯一
|
||||
- 退款:`out_refund_no` 唯一
|
||||
- 相同业务号 + 相同事件类型重复请求**不得创建重复资源、不得重复入账 / 出账**
|
||||
- 建议格式:`{业务前缀}_{日期}_{序号}`,例如 `pap_pay_20260429_000001`
|
||||
|
||||
## 十、回调与查单的双保险
|
||||
|
||||
> 来源参考:[支付回调和查单实现指引](https://pay.weixin.qq.com/doc/v2/merchant/4011984682.md)
|
||||
|
||||
委托代扣建议**回调 + 主动查单双保险**:
|
||||
|
||||
- 申请扣款提交后**立即在本地登记 `out_trade_no` 状态为"扣款中"**
|
||||
- 收到扣款回调后按 §五 处理 + 同步状态
|
||||
- **同时**起一个定时任务(如每 30 秒)扫描超过预期时长仍未收到回调的扣款单,调「查询订单」主动确认
|
||||
- 退款 / 签约 / 解约同理:「查询退款」/「查询签约关系」兜底
|
||||
|
||||
## 十一、请求域名
|
||||
|
||||
- 主域名:`https://api.mch.weixin.qq.com`
|
||||
- 备域名:`https://api2.mch.weixin.qq.com`([跨城冗灾方案](https://pay.weixin.qq.com/doc/v2/merchant/4011984887.md))
|
||||
@@ -0,0 +1,134 @@
|
||||
# 商户模式开发参数与业务规则
|
||||
|
||||
> 来源:微信支付 V2 文档中心 · 委托代扣([协议规则](https://pay.weixin.qq.com/doc/v2/merchant/4011986811.md) / [接口规则](https://pay.weixin.qq.com/doc/v2/merchant/4011986748.md) / [接入流程](https://pay.weixin.qq.com/doc/v2/merchant/4011986709.md) / [周期扣费](https://pay.weixin.qq.com/doc/v2/merchant/4011986682.md))。本文档汇总接入开发期需要确认的所有参数、密钥、模板、字段命名/取值规范以及业务规则。
|
||||
|
||||
## 一、接入参数清单(开发前必须备齐)
|
||||
|
||||
| # | 参数 | 用途 | 获取位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `mch_id`(商户号) | 所有委托代扣接口的身份标识 | 商户平台首页右上角 / 邮件 |
|
||||
| 2 | `appid`(公众号 / 小程序 / 移动应用 ID) | 所有接口必传;APP 用开放平台 appid(与公众号 appid 不同);小程序用小程序 appid;H5 / 公众号纯签约用公众号 appid | 公众平台 / 开放平台 |
|
||||
| 3 | **APIv2 密钥**(32 字符) | V2 接口签名密钥(MD5 / HMAC-SHA256),**与申请退款的 `secapi` 接口、签约接口共用同一把** | 商户平台 → 账户中心 → API 安全 → APIv2 密钥 → 设置 |
|
||||
| 4 | **APIv3 密钥**(32 字节) | **仅「预扣费通知」接口需要**(V3 JSON + RSA),同时也是 V3 回调通知 AES 解密密钥 | 商户平台 → 账户中心 → API 安全 → APIv3 密钥 → 设置 |
|
||||
| 5 | **商户 API 证书**(`apiclient_cert.p12` / `apiclient_cert.pem` + `apiclient_key.pem`) | 「申请退款」`/secapi/pay/refund` 必须双向证书;预扣费通知 V3 签名也需要私钥 | 商户平台 → 账户中心 → API 安全 → 商户 API 证书 → 申请 |
|
||||
| 6 | **微信支付公钥** 或 **微信支付平台证书** | 验签预扣费通知 V3 响应、V3 回调通知 | 商户平台 → 账户中心 → API 安全;推荐用「微信支付公钥」(`PUB_KEY_ID_xxxxxx`),平台证书需定期轮换 |
|
||||
| 7 | **委托代扣模板 ID**(`plan_id`) | 所有签约 / 解约 / 查询签约关系接口必传 | 商户平台 → 交易中心 → 高级业务 → 委托代扣模版管理 |
|
||||
| 8 | **签约协议号**(`contract_code`) | 商户自定义,**同一 mch_id 下唯一**;用于 `plan_id+contract_code` 模式解约 / 查询 | 业务方按"业务前缀_日期_序号"自行生成 |
|
||||
| 9 | **委托代扣协议 ID**(`contract_id`) | 签约成功后微信返回,用于「申请扣款」「`contract_id` 模式解约 / 查询」 | 签约成功回调 / 查询签约关系返回 |
|
||||
| 10 | **`notify_url`**(多个) | 签约通知 URL:在签约接口 `notify_url` 字段传入;扣款通知 URL:在「申请扣款」`notify_url` 字段传入;解约通知 URL:在模板配置中预设 | 由商户自行准备 HTTPS 公网地址 |
|
||||
| 11 | **支付中签约通知 URL**(`contract_notify_url`) | **仅支付中签约接口需要**,与支付通知 URL(`notify_url`)独立 | 在 `pay/contractorder` 接口 `contract_notify_url` 字段传入 |
|
||||
| 12 | **`out_trade_no`**(商户订单号) | 「申请扣款」「支付中签约」必传,**同一 mch_id 下唯一** | 业务方自行生成 |
|
||||
| 13 | **`out_refund_no`**(商户退款单号) | 「申请退款」必传,同一 mch_id 下唯一;退款重试**严禁更换单号** | 业务方自行生成 |
|
||||
|
||||
## 二、APIv2 接入规则(协议层)
|
||||
|
||||
| 项 | 取值 |
|
||||
| --- | --- |
|
||||
| 传输方式 | HTTPS |
|
||||
| 提交方式 | POST(公众号 / H5 纯签约入口为 GET) |
|
||||
| 数据格式 | XML,根节点 `<xml>...</xml>`;预扣费通知例外,是 V3 JSON |
|
||||
| 字符编码 | **UTF-8 子集**:仅支持 1-3 字节编码字符;**不支持 4 字节字符**(emoji 等) |
|
||||
| 签名算法 | MD5 / HMAC-SHA256(默认 MD5;H5 纯签约预签约默认 HMAC-SHA256) |
|
||||
| 是否需要证书 | 「申请退款」需双向证书;其余 V2 接口不需要 |
|
||||
| 判断逻辑 | **先判断协议字段** `return_code` → **再判断业务返回** `result_code` → **最后判断交易状态** `trade_state` |
|
||||
|
||||
> ‼️ **一单一支付**:必须严格按"一单一支付"原则——在未得到支付系统**明确回复**之前不要换单,避免重复支付/重复扣款。
|
||||
|
||||
## 三、字段传参规范(产品特有 / 易踩坑)
|
||||
|
||||
### 3.1 字符编码 / 命名
|
||||
|
||||
- **`contract_display_account`**(用户账户展示名称):用于签约页"开通账号"展示;**禁止传微信昵称**(含 emoji),UTF-8 子集只支持 1-3 字节字符。
|
||||
- **`request_serial`**(请求序列号):int64,**禁止以 0 开头**,纯数字;序列号只用于排序、不作查询条件;同一商户下需唯一。
|
||||
- **`timestamp`**(时间戳):10 位 UNIX 秒;**长度必须 >= 9 位**(< 9 位会被拒)。
|
||||
- **`contract_code`** vs **`contract_id`**:前者是商户自定义的协议号,后者是签约成功后微信返回的 ID;申请扣款只能传 `contract_id`,解约 / 查询签约关系两者皆可。
|
||||
- **`out_trade_no`**:32 字符内,可含字母、数字;同一 mch_id 下唯一;**换 mch_id 同名 `out_trade_no` 会被认为是新单**。
|
||||
|
||||
### 3.2 URL 编码(最易踩坑)
|
||||
|
||||
- 所有签约入口(公众号 / APP 预签约 / 小程序 / H5 / 支付中签约)都要求 **`notify_url` 在 URL 中传输前做一次 URL Encode**;但**签名时使用 encode 之前的原值**。
|
||||
- encode 后的转义符(如 `%2F`、`%3A`)要求**大写**。
|
||||
- ‼️ **不要双重 encode**——encode 一次即可。
|
||||
|
||||
### 3.3 金额
|
||||
|
||||
- 单位为**分**,整数;`refund_fee` <= `total_fee`,单笔订单部分退款次数 <= 50。
|
||||
- 实际扣款金额以**「申请扣款」`total_fee`**为准,**不是**模板预设金额:
|
||||
- 模板"扣费类型 = 为"(固定金额)→ 必须按模板金额传
|
||||
- 模板"扣费类型 = 不高于"(非固定)→ 可在限额内自定义
|
||||
|
||||
### 3.4 协议号 / 模板 ID 唯一性
|
||||
|
||||
- 协议维度:**mch_id + plan_id + 微信号** 唯一。同一用户在同一模板下只能有一个生效签约(解约可重新获得名额)。
|
||||
- 同一商户号下不同 plan_id 可与同一用户分别签约一次。
|
||||
- 一个商户号可申请的模板数**无上限**。
|
||||
- 不同签约方式(如 APP 纯签约、H5 纯签约)的 `plan_id` 可以共用。
|
||||
|
||||
### 3.5 协议关系与业务字段
|
||||
|
||||
- **APP 签约**:必须使用**开放平台 appid**(与公众号 appid 不同)。
|
||||
- **支付中签约**:`contract_appid` 必须 = `appid`,`contract_mchid` 必须 = `mch_id`,否则报"签约 contract_appid 与下单 appid 不匹配"。
|
||||
- **签约用户的 openid 以微信侧回调为准**:商户记录的签约用户 openid 必须以「签约/解约结果通知」中回调的 `openid` 为准,**禁止使用业务侧自记录的 openid**,避免扣款用户和签约用户对不上。
|
||||
|
||||
## 四、扣费时段与频率约束
|
||||
|
||||
| 约束项 | 取值 |
|
||||
| --- | --- |
|
||||
| 「申请扣款」/ 「预扣费通知」可调用时段 | 北京时间每天 **7:00 - 22:00**(强制;签约时间不影响) |
|
||||
| 「申请扣款」请求频率 | **150 QPS**(正常请求) |
|
||||
| 同一签约协议每日成功扣款次数 | **1 次**(失败不计) |
|
||||
| 同一签约协议单周期成功扣款次数 | **2 次** |
|
||||
| 自动续费模板单笔限额 | 按模板申请时的金额配置 |
|
||||
| 测试模板单笔限额 / 单日限频 | 0.1 元 / 100 次 |
|
||||
| `out_trade_no` 重复扣款 | 同 mch_id + 同 `out_trade_no` 仅扣 1 次;换 mch_id 后同名单号会再扣 |
|
||||
|
||||
## 五、首次签约的特殊扣款规则
|
||||
|
||||
如果用户为**首次签约**(含解约后重新签约),从用户**签约成功时间**开始算:
|
||||
|
||||
- **12 小时内**调用「申请扣款」 → **立即执行,无延迟**;预扣费通知模式下也无需先调「预扣费通知」
|
||||
- 超过 12 小时后调用「申请扣款」 → 按通知模式(24h / 预扣费通知)的标准规则延迟扣费
|
||||
|
||||
## 六、可选模块清单(按需启用)
|
||||
|
||||
下列模块仅当业务实际涉及时才需要:
|
||||
|
||||
- **周期扣款(预扣费通知模式)** → 必须先调「预扣费通知」V3 接口;模板需运营修改为预扣费通知模式
|
||||
- **周期扣款(24 小时延迟扣费模式)** → 直接调「申请扣款」即可,受理后 24 小时内勿重复扣款
|
||||
- **支付中签约** → 必须开通对应基础支付产品权限(如 trade_type=JSAPI 需开通 JSAPI 支付)
|
||||
- **退款** → 需「申请退款」`/secapi/pay/refund`,必须双向证书;订单超过 1 年不能退款
|
||||
- **对账下载** → 「下载交易账单」`/pay/downloadbill`,账单生成时间为次日 9 点,建议 10 点后下载;可选 GZIP 压缩
|
||||
- **首期优惠** → 申请模板时设置"优惠价格 < 标准价格"
|
||||
- **APP 鸿蒙系统适配** → 必须使用 WXLaunchMiniProgram(OpenBusinessWebview 不支持鸿蒙)
|
||||
|
||||
## 七、回调地址 / IP 白名单
|
||||
|
||||
> 详细的回调机制(解密 / 验签 / 幂等 / 路由)见 [📄 商户模式回调处理](./回调处理.md)。
|
||||
|
||||
- 回调地址要求:**HTTPS + 域名已 ICP 备案 + 公网可访问**;**不允许内网 / `127.0.0.1` / `localhost` / 携带 query 参数**。
|
||||
- 5 秒内必须返回 HTTP 2XX;逻辑里不能做登录态校验。
|
||||
- 微信支付不提供"回调发送日志"查询,需自行查 access log;回调可能重复送达,必须按业务唯一标识 + `event_type` 去重 + 加锁。
|
||||
- 委托代扣**没有 IP 白名单**对接入侧的限制;商户侧若有防火墙策略限制回调 IP,需放行通用 IP 段(详见 [📄 商户模式回调处理](./回调处理.md) "回调 IP 白名单" 章节)。
|
||||
|
||||
## 八、签名 / 验签算法
|
||||
|
||||
> 详细签名步骤、Authorization 头格式、4/5/3 行签名串、调起支付签名等见 [📄 商户模式签名与验签规则](./签名与验签规则.md)。
|
||||
|
||||
简要要点:
|
||||
|
||||
- V2 主体接口(签约 / 申请扣款 / 解约 / 查询 / 退款 / 关单 / 账单 / 回调)→ MD5 / HMAC-SHA256,密钥 = APIv2 密钥
|
||||
- V3「预扣费通知」 → SHA256-RSA2048,私钥 = 商户 API 证书私钥
|
||||
- V3 回调验签密钥 = 微信支付公钥 / 平台证书
|
||||
- **支付中签约的"调起支付"步骤**走对应基础支付产品的调起规则(如 JSAPI 是 4 行签名串 + `prepay_id=` 前缀)
|
||||
- 「申请退款」需要 `apiclient_cert.p12` 双向证书
|
||||
|
||||
## 九、其他业务规则
|
||||
|
||||
- **解约**:用户可直接解约,无需商户同意;解约后可继续以纯签约或支付中签约方式重新签约(注意签约单号不能重复)。
|
||||
- **续费协议提前续费**:必须在协议**最后一期(到期当月)**发起续期,不能在中间月份提前操作。
|
||||
- **首月优惠**:申请模板时配置后,签约页自动展示"首月优惠";商户侧无需自行判断。
|
||||
- **首期扣费金额可以与后续不一致**:每期扣款金额在模板限额内即可。
|
||||
- **扣款失败用户无感知**(不会发通知给用户);可扣周期内可换单重试,周期结束需重新调「预扣费通知」。
|
||||
- **扣款失败自动关单**:所有可扣方式失败后订单变 `CLOSED`,再次扣费必须**换 `out_trade_no`** 重新调「申请扣款」。
|
||||
- **回调结果为 `err_code=SYSTEMERROR` 的扣款单**:先查询订单,若 `trade_state` ≠ `CLOSED`,需先关单,再换单号重新申请延迟扣费(24 小时后执行)。
|
||||
- **微信纯签约现支持鸿蒙**:仅 WXLaunchMiniProgram 调起方式支持,OpenBusinessWebview 不支持。
|
||||
@@ -0,0 +1,78 @@
|
||||
# 商户模式接入质量检查
|
||||
|
||||
## 角色设定:金融支付系统技术专家
|
||||
|
||||
> ‼️ **本节角色、铁律和问题雷达是质检的全部驱动力,必须内化后再审代码。**
|
||||
|
||||
你是金融支付系统技术专家,全栈工程师出身,亲手写过从前端收银台到后端交易引擎的全链路代码。你主导过千万级用户规模的国民级支付系统架构设计,从零搭建过高并发交易平台。你熟悉主流支付平台的接入规范与安全体系,对 API 签名验签机制、异步回调通知处理、资金流对账有丰富的实战经验。你对代码质量有极强的直觉,尤其对资金链路上的异常处理缺失高度警觉。
|
||||
|
||||
你对支付系统的要求极高:接口交互必须有完善的异常处理和兜底方案,资金操作必须可追溯、可对账,所有外部输入必须经过校验才能进入业务逻辑。
|
||||
|
||||
## 铁律
|
||||
|
||||
**铁律一:高可用(99.9999%)**
|
||||
系统可用性要求 99.9999%(六个 9),即每一百万次请求中最多允许一次失败。支付链路上不允许存在单点故障,每一个外部调用都必须有超时、重试和降级方案。
|
||||
检查直觉:调用微信支付 API 超时了,代码会自动重试还是直接报错?重试的时候会不会导致重复下单?微信的支付回调一直没来,系统有没有定时去主动查询订单状态?用户快速点了两次支付按钮,会不会创建两笔订单?
|
||||
|
||||
**铁律二:资金安全(一分钱都不能错)**
|
||||
金额计算必须使用整数(单位:分),杜绝浮点精度丢失。每一笔资金变动(支付、退款、分账)都必须有据可查,系统必须在次日通过账单对账主动发现差异。
|
||||
检查直觉:金额字段的类型是 int/long 还是 double/float?用户申请退款时,代码有没有累加历史退款金额并校验是否超过订单总额?系统有没有每天自动拉取微信账单和本地订单做比对?
|
||||
|
||||
**铁律三:零信任(不信任任何未经验证的外部数据)**
|
||||
微信的回调通知、前端传入的参数、缓存中的数据,在进入业务逻辑前必须经过验证,未验证的输入一律视为不可信。
|
||||
检查直觉:收到支付回调后,代码是先验签还是直接解析 body 处理业务?下单接口的金额是从后端数据库查的还是直接用前端传过来的值?回调通知中的支付金额有没有和本地订单金额做比对?私钥是通过环境变量加载的还是硬编码在代码里?
|
||||
|
||||
## 检查方法
|
||||
|
||||
1. **扫代码** — 快速扫描代码,按问题雷达定位高风险区域
|
||||
2. **追链路** — 沿资金流完整走一遍:签约 → (周期扣款则)预扣费通知/24h 等待 → 申请扣款 → 扣款回调 → 查询订单兜底 → 退款 → 对账,任何断点都是事故点
|
||||
3. **做预演** — 对每个关键节点问"如果这里故障了/超时了/被攻击了/来了两次,会怎样?"
|
||||
|
||||
**输出要求**:发现问题必须给出修复方向,不能只说"有风险";必须基于代码事实,不基于猜测;结果按 🔴🟡🟠 分级,致命问题置顶。
|
||||
|
||||
## 问题雷达
|
||||
|
||||
> **来源**:通用安全雷达(固定 4 项)+ 产品专属雷达(**重点从「开发指引」与「常见问题」提炼**,其他文档作为补充)。
|
||||
>
|
||||
> 以下仅列举常见的高风险问题,**不要只检查列出的项**。检查时应反向运用铁律:逐条铁律审视代码,发现未列出的同类问题。
|
||||
|
||||
### 通用安全雷达(所有产品必查)
|
||||
|
||||
> 4 项**独立判定**,每项必须给出"通过 / 未实现 / 不涉及"三选一的明确结论,**禁止合并多项为一条**。具体检查方法见 [签名与验签规则](./签名与验签规则.md)。
|
||||
|
||||
| # | 检查项 | 检查锚点 | 未实现的判定特征 | 默认级别 |
|
||||
| --- | ------ | -------- | ---------------- | -------- |
|
||||
| 1 | **HTTP 响应验签** | 发起请求并处理响应的代码(OkHttp `execute()` / HttpClient `send()` 等) | 收到 2XX 响应后直接解析返回数据,中间无任何验签调用 | 🔴 致命 |
|
||||
| 2 | **回调通知验签** | 处理回调通知的代码(含 `event_type` / `resource_type` / `encrypt-resource` 等字段) | 收到通知后**先解密或解析业务数据**,验签缺失或在解密之后 | 🔴 致命 |
|
||||
| 3 | **幂等去重 + 并发锁** | 回调处理流程的入口 | 既无按"业务唯一标识 + `event_type`"的去重查询,也无加锁逻辑(Redis 锁 / 行锁 / `synchronized` 等) | 🔴 致命 |
|
||||
| 4 | **探测流量未做特殊跳过** | 验签代码分支 | 对签名值含 `WECHATPAY/SIGNTEST/` 前缀的请求做了特殊跳过/早返回 | 🟠 可选 |
|
||||
|
||||
### 产品专属雷达
|
||||
|
||||
> ‼️ 委托代扣**主要走 V2(XML + MD5/HMAC-SHA256)**,**唯一例外**是「预扣费通知」走 V3(JSON + RSA)+ V3 回调通知走 AEAD_AES_256_GCM。审代码时必须**先确认协议版本**,不要套错算法。
|
||||
|
||||
| # | 检查项 | 检查锚点 | 未实现的判定特征 | 默认级别 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 5 | **V2 签名密钥来源** | V2 接口签名调用 | APIv2 密钥**硬编码**在代码里,未从环境变量 / 配置中心 / KMS 加载;或与商户平台 APIv2 密钥不一致 | 🔴 致命 |
|
||||
| 6 | **V2 字符编码限制(UTF-8 子集)** | `contract_display_account` / `body` / `attach` 等字符串字段赋值处 | 字段直接传了用户微信昵称(含 emoji 4 字节字符),未做"非 1-3 字节字符过滤"或长度截断 | 🔴 致命 |
|
||||
| 7 | **`notify_url` 编码** | 签约接口 URL 拼接代码 | URL 中的 `notify_url` **未做一次 URL Encode** 或做了**双重 encode**;或 encode 后转义符未大写 | 🔴 致命 |
|
||||
| 8 | **签名时使用 encode 前的原值** | 签名串拼接逻辑 | 签名时使用了 encode 后的 `notify_url`(与发送的不一致),或字段顺序未按 ASCII 字典序 | 🔴 致命 |
|
||||
| 9 | **扣款时段前置校验** | 「申请扣款」/「预扣费通知」调用入口 | 调用前**未校验北京时间是否在 7:00-22:00**,22:00 后定时任务批量扣款会触发拒绝;或扣款失败时未识别"非可扣费时段"原因 | 🟡 推荐 |
|
||||
| 10 | **`out_trade_no` 唯一性 + 不复用** | 「申请扣款」`out_trade_no` 生成逻辑 | 重试场景使用了相同 `out_trade_no`(同 mch_id 同单号只会扣一次,旧单失败后必须**换新号**重新申请扣款);或换新号时**未先查询/关闭旧单** | 🔴 致命 |
|
||||
| 11 | **扣款 / 签约 / 解约回调路由分发** | 回调入口的 `change_type` / 报文结构判断 | 三类回调(扣款结果通知 / 签约-解约结果通知)共用同一 URL 时未按 `change_type=ADD/DELETE` 或报文字段路由分发,导致签约回调被扣款逻辑处理 | 🔴 致命 |
|
||||
| 12 | **回调中的 openid 校准** | 处理签约 / 解约回调的代码 | 商户记录的签约用户 openid 直接用业务侧自记录的值,未以**回调中返回的 `openid`** 为准(导致扣款时用错用户的协议) | 🔴 致命 |
|
||||
| 13 | **签约成功幂等性** | 签约成功回调处理 | 同一 `contract_code` 多次签约成功通知未做幂等去重,导致重复创建协议 | 🔴 致命 |
|
||||
| 14 | **扣款回调金额校验** | 扣款回调处理代码 | 回调中的 `total_fee` 未与本地订单金额比对,直接信任并入账 | 🔴 致命 |
|
||||
| 15 | **扣款失败 SYSTEMERROR 兜底流程** | 扣款回调失败分支 | 回调结果 `err_code=SYSTEMERROR` 时,**未先查询订单**确认状态,**未按"非 CLOSED → 关单 → 换号重新申请延迟扣费"流程操作** | 🔴 致命 |
|
||||
| 16 | **查询订单兜底** | 申请扣款失败 / 回调长时间未到的处理逻辑 | 申请扣款返回 `ORDER_ACCEPTED` / `SYSTEMERROR` 时,未调用「查询订单」确认实际状态就直接换号重试,导致重复扣款 | 🔴 致命 |
|
||||
| 17 | **扣款失败自动关单识别** | 查询订单 `trade_state` 处理 | 未识别 `trade_state=CLOSED`(自动关单状态),仍以原 `out_trade_no` 重试扣款 | 🔴 致命 |
|
||||
| 18 | **预扣费通知(V3)签名 / 密钥独立** | 预扣费通知接口调用代码 | 误用 V2 APIv2 密钥 + MD5 签名调 V3 接口;或路径错填为 `/v3/papay/contracts/{contract_id}/notify` 之外的 URL | 🔴 致命 |
|
||||
| 19 | **预扣费通知 24 小时内首签免发** | 预扣费通知调用入口 | 未识别"首次签约 12 小时内申请扣款立即执行"规则,仍强制先调预扣费通知,导致用户重复收到提醒 | 🟡 推荐 |
|
||||
| 20 | **协议唯一性冲突处理** | 签约调用入口 | 用户已签约同 mch_id+plan_id 时,签约失败错误 `CONTRACT_CODE DUPLICATION` / "用户已签约"未先调「查询签约关系」确认状态后再决定走签约还是直接调用扣款 | 🟡 推荐 |
|
||||
| 21 | **支付中签约失败兜底**(仅支付中签约场景) | `pay/contractorder` 返回 `contract_result_code` 处理 | `result_code=SUCCESS` 但 `contract_result_code=FAIL` 时未独立处理"支付成功但签约失败"分支(只能完成此次支付,无法后续代扣) | 🔴 致命 |
|
||||
| 22 | **APP 调起签约 SDK 选型**(仅 APP 签约场景) | APP 签约 SDK 接入代码 | 新申请的模板仍接入 `OpenBusinessWebview`(已停止新申请),或鸿蒙系统未使用 `WXLaunchMiniProgram` | 🟡 推荐 |
|
||||
| 23 | **重复退款 / 单号复用**(仅退款场景) | 「申请退款」`out_refund_no` 生成 | 退款重试**更换了** `out_refund_no`(必须复用原单号);或未累加历史退款金额校验是否超过订单总额 | 🔴 致命 |
|
||||
| 24 | **退款证书 mTLS**(仅退款场景) | `/secapi/pay/refund` 调用代码 | HTTPS 客户端未加载 `apiclient_cert.p12` 双向证书,直接报 401 / `CERT_ERROR` | 🔴 致命 |
|
||||
| 25 | **每日对账自动化**(仅有扣款的商户必查) | 定时任务 / 跑批代码 | 未实现每日 10 点后自动调「下载交易账单」+ 与本地订单逐笔比对 + 差异告警;或对账中未识别 `PAP` 交易类型为委托代扣 | 🟡 推荐 |
|
||||
| 26 | **金额单位 / 数据类型** | 金额字段定义和计算 | 金额字段使用 `double` / `float`(应为 `int` / `long`,单位分) | 🔴 致命 |
|
||||
| 27 | **APIv2 密钥与 APIv3 密钥不混用** | 两类签名调用代码 | V3 预扣费通知误用 APIv2 密钥;V2 签约 / 扣款误用 APIv3 密钥 | 🔴 致命 |
|
||||
@@ -0,0 +1,269 @@
|
||||
# 商户模式签名与验签规则
|
||||
|
||||
> 委托代扣是 **API V2 主体 + 单接口 V3 例外**(参见 [SKILL.md](../../../SKILL.md) 全局规则 §3)。本文以 **V2 签名(MD5 / HMAC-SHA256)** 为主,V3 仅作为「预扣费通知」一个接口的例外说明。
|
||||
>
|
||||
> 来源:[V2 协议规则](https://pay.weixin.qq.com/doc/v2/merchant/4011986581.md) / [V2 安全规范-签名算法](https://pay.weixin.qq.com/doc/v2/merchant/4011985891.md) / [V2 在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md)
|
||||
>
|
||||
> ⚠️ **常见误解**:委托代扣不是 V3 RSA 签名体系,**绝大多数接口请使用 V2 MD5/HMAC-SHA256**。把 V3 的 5 行 RSA 签名套到委托代扣 V2 接口(签约 / 申请扣款 / 解约 / 查询 / 退款 / 关单 / 账单 / V2 异步回调)→ 100% 401 SIGN_ERROR。
|
||||
|
||||
## 一、协议版本与签名算法对应关系
|
||||
|
||||
| 接口分组 | 协议版本 | 数据格式 | 签名算法 | 签名密钥 | 是否需双向证书 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 签约(公众号 / APP / 小程序 / H5 纯签约 / 支付中签约) | **V2** | XML / URL Query | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 申请扣款 `/pay/pappayapply` | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 解约 `/papay/deletecontract` | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 查询签约关系 / 查询订单 | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 关闭订单 `/pay/closeorder` | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 下载交易账单 `/pay/downloadbill` | **V2** | XML 请求 / 文本响应 | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 申请退款 `/secapi/pay/refund` | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | **是**(apiclient_cert.p12) |
|
||||
| 查询退款 `/pay/refundquery` | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | 否 |
|
||||
| 签约 / 解约 / 扣款 / 退款 异步回调 | **V2** | XML | **MD5 或 HMAC-SHA256** | APIv2 密钥 | — |
|
||||
| **预扣费通知** `/v3/papay/contracts/{contract_id}/notify` | **V3** | JSON | **SHA256-RSA2048** | 商户 API 证书私钥 | 否(验签需微信支付公钥) |
|
||||
|
||||
> **签名方式默认值**:除「H5 纯签约预签约接口」默认 `HMAC-SHA256` 外,其余 V2 接口默认 `MD5`;签约 / 申请扣款等支持通过 `sign_type` 字段显式指定。**收到响应/回调时按报文里的 `sign_type` 还原签名方式校验**,不要写死。
|
||||
|
||||
## 二、V2 请求签名(主体规范)
|
||||
|
||||
### 2.1 签名生成步骤
|
||||
|
||||
> 摘自官方 [V2 签名算法](https://pay.weixin.qq.com/doc/v2/merchant/4011985891.md),建议直接对照官方 [在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md) 复核。
|
||||
|
||||
**第一步**:所有发送或接收到的数据为集合 M,将 M 内**非空**参数值按参数名 ASCII 字典序(key1 < key2 < …)排序,使用 `key=value` 拼接成字符串 stringA。
|
||||
|
||||
**第二步**:在 stringA 末尾拼接 `&key=<APIv2密钥>` 得到 stringSignTemp,对其做 MD5 或 HMAC-SHA256 运算,结果**全部转换为大写**,得到 sign。
|
||||
|
||||
**第三步**:将 sign 作为 `<sign>` 字段放入 XML 请求体一并发出。
|
||||
|
||||
### 2.2 必须遵守的 6 条规则
|
||||
|
||||
1. **ASCII 字典序**:参数名按 ASCII 从小到大排序,**不是字母序、不是字段定义顺序**。
|
||||
2. **空值不参与签名**:参数值为空字符串、`null`、未传字段一律**不进入签名串**。注意:传了空字符串和不传是同义;只要值是空就跳过。
|
||||
3. **`sign` 字段不参与签名**:响应/回调里收到的 `sign` 是结果,不能再放进签名串。
|
||||
4. **参数名区分大小写**:`mch_id` ≠ `Mch_Id`;字段拼写以官方文档为准。
|
||||
5. **使用原值签名,不做 URL Encode**:尤其是 `notify_url`,签名时使用 encode **之前**的原值;URL Encode 只发生在 URL 拼接传输环节。
|
||||
6. **支持扩展字段**:响应/回调可能新增字段,验签时**必须把新字段一起带进签名串**,否则未来某天突然报"验签失败"。
|
||||
|
||||
### 2.3 字符编码
|
||||
|
||||
- 强制 **UTF-8**,且**仅支持 1-3 字节编码字符**(Unicode BMP 子集)。
|
||||
- **不支持 4 字节字符**(emoji、部分罕用汉字、辅助平面字符)。
|
||||
- 高频踩雷:`contract_display_account`(签约展示账户名)、`body`(商品描述)传了带 emoji 的微信昵称 → 直接被网关拒绝。
|
||||
|
||||
### 2.4 完整示例(来自官方文档)
|
||||
|
||||
假设要发送的参数:
|
||||
|
||||
```
|
||||
appid: wxd930ea5d5a258f4f
|
||||
mch_id: 10000100
|
||||
device_info: 1000
|
||||
body: test
|
||||
nonce_str: ibuaiVcKdpRxkhJA
|
||||
```
|
||||
|
||||
**第一步:按 ASCII 字典序拼接(非空字段)**:
|
||||
|
||||
```
|
||||
stringA = "appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"
|
||||
```
|
||||
|
||||
**第二步:拼 APIv2 密钥并签名**:
|
||||
|
||||
```
|
||||
# 假设 APIv2 密钥 = 192006250b4c09247ec02edce69f6a2d
|
||||
stringSignTemp = stringA + "&key=192006250b4c09247ec02edce69f6a2d"
|
||||
|
||||
# MD5
|
||||
sign = MD5(stringSignTemp).toUpperCase()
|
||||
= "9A0A8659F005D6984697E2CA0A9CF3B7"
|
||||
|
||||
# 或 HMAC-SHA256(部分语言 hmac 输出二进制,需自行转十六进制再大写)
|
||||
sign = hash_hmac("sha256", stringSignTemp, key).toUpperCase()
|
||||
= "6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6"
|
||||
```
|
||||
|
||||
**第三步:组装最终 XML**:
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wxd930ea5d5a258f4f</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<device_info>1000</device_info>
|
||||
<body>test</body>
|
||||
<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
|
||||
<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> 走 HMAC-SHA256 时,请求体里**必须额外加 `<sign_type>HMAC-SHA256</sign_type>`** 并参与签名(`sign_type` 也是普通字段,按 ASCII 字典序入串)。
|
||||
|
||||
### 2.5 nonce_str / timestamp 要求
|
||||
|
||||
- `nonce_str`:32 字符以内,建议字母 + 数字随机串;用于防重放,每次请求重新生成。
|
||||
- `timestamp`(如有):10 位 UNIX 秒;服务器与微信侧时差大于约 5 分钟会被拒;**长度必须 ≥ 9 位**(< 9 位被网关拒)。
|
||||
- `request_serial`(部分签约接口必传):int64,**禁止以 0 开头**,仅用排序不用作查询条件。
|
||||
|
||||
## 三、V2 响应 / 回调验签(与请求签名同一算法)
|
||||
|
||||
委托代扣 V2 异步回调(签约结果 / 解约结果 / 扣款结果 / 退款结果)和 V2 请求的同步响应都走 **同一套 MD5 / HMAC-SHA256 算法**,只是验签方向反过来:
|
||||
|
||||
1. 解析 XML,取出 `<sign>` 字段,**剩余字段全部入签名串**(含微信新增的扩展字段,含 `sign_type`)。
|
||||
2. 按本文 §2.1 步骤重算 sign(密钥仍是商户 APIv2 密钥)。
|
||||
3. **大小写不敏感比对**计算结果与报文中的 `sign` 是否相等;不等即视为伪造/被篡改,丢弃报文。
|
||||
4. **退款回调例外**:退款结果通知报文中的 `req_info` 字段是 **AES-256-ECB 加密的 base64 字符串**,需用 **APIv2 密钥的 MD5(小写 32 位)** 作为 AES key 解密后才能拿到业务字段;解密后的内容仍是 XML,但**不再参与 V2 sign 验签**(外层报文本身不带 `sign`)。
|
||||
|
||||
### 3.1 静态扫描判定(接入质检用)
|
||||
|
||||
| 场景 | 第一步 定位 | 第二步 检查 | 判为"未实现" |
|
||||
| --- | --- | --- | --- |
|
||||
| **V2 同步响应验签** | 找发 V2 接口请求并解析响应 XML 的代码 | `return_code=SUCCESS` 分支中是否对响应再做一次 V2 sign 校验 | 收到 SUCCESS 直接读业务字段,无任何 sign 重算逻辑 |
|
||||
| **V2 异步回调验签** | 找处理签约 / 扣款 / 解约回调的 controller / handler | 处理业务前是否对回调 XML 重算 V2 sign 比对 | 直接读字段、写库、回 SUCCESS,没有 sign 校验 |
|
||||
| **退款回调解密** | 找处理 `/refund-notify` 回调的代码 | 是否用 `MD5(APIv2密钥)` 解密 `req_info` | 跳过解密直接 base64 → 读 XML 解析;或用 APIv3 密钥解密 |
|
||||
|
||||
⚠️ 自研封装方法(如 `parseXmlAndVerify`)需要打开看内部是否有真验签,不要被命名骗了。
|
||||
|
||||
## 四、V2 调起客户端支付签名(仅支付中签约 / 走 V2 调起的场景)
|
||||
|
||||
> 仅当业务涉及「支付中签约」或基于委托代扣 V2 下单后调起 JSAPI / APP / 小程序 / H5 客户端支付时适用。**纯签约接口走的是 URL/H5 跳转或 SDK 调起,不涉及客户端 paySign**。
|
||||
|
||||
V2 调起支付签名 **算法与本文 §2.1 完全相同**(MD5 / HMAC-SHA256 + APIv2 密钥),只是参与签名的字段集合按客户端类型不同。
|
||||
|
||||
### 4.1 各客户端调起字段与签名输入
|
||||
|
||||
| 客户端 | 服务端下发给前端的字段 | 参与 paySign 的字段(仍按 ASCII 字典序 + APIv2 密钥结尾) |
|
||||
| --- | --- | --- |
|
||||
| **JSAPI**(公众号) | `appId` / `timeStamp` / `nonceStr` / `package=prepay_id=xxx` / `signType` / `paySign` | `appId`、`nonceStr`、`package`、`signType`、`timeStamp` |
|
||||
| **小程序** | 同 JSAPI(`appId` 是小程序 appid) | 同 JSAPI |
|
||||
| **APP** | `appid` / `partnerid` / `prepayid` / `package=Sign=WXPay` / `noncestr` / `timestamp` / `sign` | `appid`、`noncestr`、`package`、`partnerid`、`prepayid`、`timestamp` |
|
||||
| **H5** | 不需要 paySign,直接跳 `mweb_url` 即可 | — |
|
||||
|
||||
> ⚠️ 特别注意 JSAPI / 小程序的字段是 **驼峰**(`appId` / `timeStamp` / `nonceStr`),APP 是 **全小写**(`appid` / `noncestr` / `timestamp`)。**这是字段名差异,不是大小写不敏感**——字典序排序结果完全不同。
|
||||
|
||||
### 4.2 JSAPI 示例
|
||||
|
||||
服务端下单返回 `prepay_id=wx201410272009395522657a690389285100` 后,构造调起参数:
|
||||
|
||||
```
|
||||
stringA = "appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx201410272009395522657a690389285100&signType=MD5&timeStamp=1414561699"
|
||||
stringSignTemp = stringA + "&key=192006250b4c09247ec02edce69f6a2d"
|
||||
paySign = MD5(stringSignTemp).toUpperCase()
|
||||
```
|
||||
|
||||
前端 `WeixinJSBridge.invoke('getBrandWCPayRequest', {...})` 入参:
|
||||
|
||||
```js
|
||||
{
|
||||
appId: 'wxd678efh567hg6787',
|
||||
timeStamp: '1414561699',
|
||||
nonceStr: '5K8264ILTKCH16CQ2502SI8ZNMTM67VS',
|
||||
package: 'prepay_id=wx201410272009395522657a690389285100',
|
||||
signType: 'MD5',
|
||||
paySign: '<上一步算出>'
|
||||
}
|
||||
```
|
||||
|
||||
## 五、V3 例外:预扣费通知 `/v3/papay/contracts/{contract_id}/notify`
|
||||
|
||||
> ⚠️ 整个委托代扣里**仅此一个**接口走 V3。其余接口请回到 §二、§三、§四。
|
||||
|
||||
### 5.1 V3 请求签名(5 行格式)
|
||||
|
||||
```
|
||||
HTTP请求方法\n
|
||||
URL\n
|
||||
请求时间戳\n
|
||||
请求随机串\n
|
||||
请求报文主体\n
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
1. 严格 5 行,每行末尾必须有 `\n`(含最后一行)。
|
||||
2. URL 是**去掉域名的绝对路径**(不含 `https://api.mch.weixin.qq.com`),含已替换的 `{contract_id}`。
|
||||
3. 签名时用 **商户 API 证书私钥** 做 SHA256 with RSA。
|
||||
4. 时间戳为系统当前 UNIX 秒。
|
||||
5. 签名时的请求体与实际发送的请求体**字节级一致**。
|
||||
|
||||
完整示例:
|
||||
|
||||
```
|
||||
POST\n
|
||||
/v3/papay/contracts/Wx15463511252015071056489715/notify\n
|
||||
1554208460\n
|
||||
593BEC0C930BF1AFEB40B4A08C8FB242\n
|
||||
{"mchid":"1230000109","appid":"wxd678efh567hg6787","deduct_duration":{"count":1,"unit":"DAY"},"estimated_amount":{"amount":1,"currency":"CNY"}}\n
|
||||
```
|
||||
|
||||
### 5.2 V3 Authorization 头
|
||||
|
||||
```
|
||||
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1230000109",nonce_str="<随机串>",signature="<签名值>",timestamp="<时间戳>",serial_no="<商户API证书序列号>"
|
||||
```
|
||||
|
||||
| 字段 | 取值 |
|
||||
| --- | --- |
|
||||
| `mchid` | 直连商户号 |
|
||||
| `nonce_str` / `timestamp` | 与签名串中字节级一致 |
|
||||
| `signature` | §5.1 算出的签名值 |
|
||||
| `serial_no` | **商户 API 证书序列号**(不是平台证书序列号) |
|
||||
|
||||
### 5.3 V3 响应验签(3 行格式)
|
||||
|
||||
预扣费通知接口正常返回 HTTP 204 无 body,理论无需验签;但若返回 5XX/4XX 含 body,则需验签:
|
||||
|
||||
1. 取 4 个签名头:`Wechatpay-Timestamp` / `Wechatpay-Nonce` / `Wechatpay-Signature` / `Wechatpay-Serial`。
|
||||
2. 构造验签串(**3 行**,每行 `\n` 结尾):
|
||||
|
||||
```
|
||||
应答时间戳\n
|
||||
应答随机串\n
|
||||
应答报文主体\n
|
||||
```
|
||||
|
||||
3. 使用 **微信支付公钥**(推荐,`PUB_KEY_ID_xxxxxx`)或 **微信支付平台证书** 做 SHA256 with RSA 验证。
|
||||
4. 校验 `Wechatpay-Serial` 与本地持有的微信支付公钥 ID / 平台证书序列号匹配。
|
||||
|
||||
## 六、幂等与并发控制
|
||||
|
||||
V2 / V3 回调都可能多次到达,业务**必须能正确处理重复**。
|
||||
|
||||
**推荐做法**:① 收到后先按业务唯一标识查询是否已处理 → ② 未处理则进入业务流程(**前置加锁**)→ ③ 已处理则直接返回成功。
|
||||
|
||||
| 回调类型 | 业务唯一标识(幂等键) |
|
||||
| --- | --- |
|
||||
| 签约 / 解约结果通知 | `contract_id`(或 `plan_id` + `contract_code`) |
|
||||
| 扣款结果通知 | `out_trade_no`(或 `transaction_id`) |
|
||||
| 退款结果通知 | `out_refund_no`(或 `refund_id`) |
|
||||
|
||||
**并发锁选型**:Redis 分布式锁(`SETNX` / `RedisLock`)、数据库行锁(`SELECT ... FOR UPDATE`)、`synchronized` / `ReentrantLock` 等。锁粒度建议 = 业务唯一标识。
|
||||
|
||||
**静态扫描判定**:回调处理代码中**既无去重查询逻辑也无加锁逻辑** → 判定未实现。
|
||||
|
||||
## 七、探测流量处理
|
||||
|
||||
微信支付会定期发送探测流量验证商户系统的连通性和验签逻辑:
|
||||
|
||||
- V3 探测请求的 `Wechatpay-Signature` 以 `WECHATPAY/SIGNTEST/` 为前缀。
|
||||
- 商户系统**不应做特殊跳过**,应当作正常响应/回调进行验签处理(V2 探测同样按正常 sign 校验流程走)。
|
||||
- 排查时可通过该前缀快速识别探测流量。
|
||||
- 若对探测流量返回错误,微信支付可能判定回调地址不可用。
|
||||
|
||||
## 八、401 / SIGN_ERROR 自查清单
|
||||
|
||||
按顺序逐项确认(无需提供私钥文件):
|
||||
|
||||
1. **协议版本对了吗?** —— V2 接口请用 MD5/HMAC-SHA256 + APIv2 密钥;只有「预扣费通知」一个接口走 V3。**别把 5 行 RSA 签名套到 V2 接口上**。
|
||||
2. **密钥用对了吗?** —— V2 用 **APIv2 密钥**(32 字符),V3 用 **商户 API 证书私钥**;两者完全不同。检查商户平台 → 账户中心 → API 安全 → APIv2 / APIv3 密钥与代码中一致;密钥重置后**新旧并存 15 天**,期间未同步代码 = 验签失败。
|
||||
3. **签名串顺序**:是否按**参数名 ASCII 字典序**排序,不是字段定义顺序、不是字母序。
|
||||
4. **空值字段是否漏跳**:值为空 / null / 未传字段不入串;**`sign` 字段本身也不入串**。
|
||||
5. **大小写**:参数名区分大小写;MD5 / HMAC-SHA256 结果**全部转大写**。
|
||||
6. **`notify_url` 编码**:签名用 encode 前的原值;URL 中传输用 encode 后的值;encode 转义符(`%2F` `%3A`)必须**大写**;不要双重 encode。
|
||||
7. **`sign_type` 一致**:用了 HMAC-SHA256 时务必在请求体加 `<sign_type>HMAC-SHA256</sign_type>` 并参与签名。
|
||||
8. **响应/回调验签**:收到响应/回调后必须按报文里的 `sign_type` 重算并比对,不要直接相信 `return_code=SUCCESS`;新字段也要带进签名串。
|
||||
9. **退款回调解密**:`req_info` 用 **MD5(APIv2 密钥) 小写 32 位** 作为 AES-256-ECB 的 key,**不是 APIv2 密钥本身**,**不是 APIv3 密钥**。
|
||||
10. **支付中签约调起**:JSAPI / 小程序字段名是**驼峰**(`appId` / `timeStamp` / `nonceStr`),APP 是**全小写**(`appid` / `timestamp` / `noncestr`),不要混。
|
||||
11. **字符编码**:UTF-8 1-3 字节子集,不要传 emoji / 4 字节字符(典型踩雷:`contract_display_account` 直接传微信昵称)。
|
||||
12. **request_serial / timestamp**:纯数字,**禁止以 0 开头**,长度 ≥ 9 位。
|
||||
13. **V3 例外(仅预扣费通知)**:5 行 `\n` 含末行;URL 去域名且 `{contract_id}` 已替换;`serial_no` 是**商户 API 证书序列号**(不是平台证书序列号)。
|
||||
14. 仍排查不出 → 用 [V2 在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md) 把签名原串和最终 sign 一一比对,定位是字段顺序、大小写、密钥、还是字段缺失/多余的问题。
|
||||
@@ -0,0 +1,102 @@
|
||||
# APP 纯签约(直连商户)
|
||||
|
||||
> **来源**:[APP纯签约](https://pay.weixin.qq.com/doc/v2/merchant/4011986804.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:HMAC-SHA256 / MD5(默认 MD5)
|
||||
> **是否需要证书**:否
|
||||
|
||||
外部 APP 拉起微信客户端发起签约前,需先后台调用预签约接口完成预签约,获取 `pre_entrustweb_id`,再拉起微信客户端,完成签约,返回 APP。
|
||||
|
||||
> 该业务接口需要单独申请,详情查看:[委托代扣-App&H5纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AJ8AyQbhAD4CN41Af5nZSQvqQrvcM)
|
||||
|
||||
---
|
||||
|
||||
## 步骤 1:预签约接口
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/preentrustweb` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
| 签名方式 | HMAC-SHA256 / MD5(默认 MD5) |
|
||||
|
||||
### 请求示例(XML)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<mch_id>1200009811</mch_id>
|
||||
<plan_id>12535</plan_id>
|
||||
<contract_code>100000</contract_code>
|
||||
<request_serial>1000</request_serial>
|
||||
<contract_display_account>微信代扣</contract_display_account>
|
||||
<notify_url>https://weixin.qq.com</notify_url>
|
||||
<version>1.0</version>
|
||||
<sign_type>MD5</sign_type>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
<timestamp>1414488825</timestamp>
|
||||
<return_app>Y</return_app>
|
||||
</xml>
|
||||
```
|
||||
|
||||
### 响应示例(XML)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<mch_id><![CDATA[10000098]]></mch_id>
|
||||
<miniprogram_username><![CDATA[gh_xxxxxxxxxxxxx]]></miniprogram_username>
|
||||
<miniprogram_path><![CDATA[pages/index/index?sign_scene=app&domain_type=cn&pre_entrustweb_id=xxxxxxxxxx]]></miniprogram_path>
|
||||
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
|
||||
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
|
||||
<pre_entrustweb_id><![CDATA[5778aadY9nltAsZzXixCkFIGYnV2V]]></pre_entrustweb_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> `miniprogram_username` 与 `miniprogram_path` 仅以下两种情况会返回:
|
||||
> 1. 模板 ID 为 2025-09-23 及之后申请的委托代扣模板
|
||||
> 2. 模板 ID 为 2025-09-23 之前申请且申请了 WXLaunchMiniProgram 权限
|
||||
>
|
||||
> 若返回这两个字段,可使用 LaunchMiniProgram SDK 进行签约(推荐,详见 [APP调起签约](./APP调起签约.md));若未返回,则只能通过 OpenBusinessWebview SDK 签约(见步骤 2)。
|
||||
|
||||
---
|
||||
|
||||
## 步骤 2:签约接口(客户端 OpenSDK 调用)
|
||||
|
||||
> ‼️ OpenBusinessWebview 仅支持 2025-09-22 之前申请过权限的商户在存量模板使用。**新增模板请用 WXLaunchMiniProgram**(见 [APP调起签约](./APP调起签约.md))。
|
||||
|
||||
预签约 ID(`pre_entrustweb_id`)从步骤 1 的返回参数获取。
|
||||
|
||||
### iOS(来源:官方文档原文)
|
||||
|
||||
```objectivec
|
||||
WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init];
|
||||
req.businessType =12; //固定值
|
||||
NSMutableDictionary *queryInfoDic = [NSMutableDictionary dictionary];
|
||||
[queryInfoDic setObject:"5778aadY9nltAsZzXixCkFIGYnV2V" forKey:"pre_entrustweb_id"];
|
||||
req.queryInfoDic = queryInfoDic;
|
||||
[WxApi sendReq:req];
|
||||
```
|
||||
|
||||
### Android(来源:官方文档原文)
|
||||
|
||||
```java
|
||||
WXOpenBusinessWebview.Req req = new WXOpenBusinessWebview.Req();
|
||||
req.businessType = 12;//固定值
|
||||
HashMap queryInfo = new HashMap<>();
|
||||
queryInfo.put("pre_entrustweb_id","5778aadY9nltAsZzXixCkFIGYnVV1");
|
||||
req.queryInfo = queryInfo;
|
||||
api.sendReq(req);
|
||||
```
|
||||
|
||||
### 返回参数
|
||||
|
||||
`WXOpenBusinessWebview.Resp`,**返回参数内容无需关注**。如果签约成功,商户系统会收到 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011986804.md
|
||||
@@ -0,0 +1,64 @@
|
||||
# APP 调起签约(直连商户)
|
||||
|
||||
> **来源**:[APP调起签约 - WXLaunchMiniProgram](https://pay.weixin.qq.com/doc/v2/merchant/4015996790.md)
|
||||
> **接口名称**:`WXLaunchMiniProgram`
|
||||
> **接口类型**:客户端 OpenSDK 调用(无服务端 HTTP 接口)
|
||||
|
||||
商户在完成"委托代扣 APP 预签约"后,可在移动端 APP 集成 OpenSDK 调起微信,请求用户签约。
|
||||
|
||||
## 接入前注意
|
||||
|
||||
1. 该能力依赖微信 [Open SDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Pay/Vendor_Service_Center.html),需在微信开放平台申请开通移动应用的微信支付能力
|
||||
2. 商户在使用 WXLaunchMiniProgram 调起 APP 签约前,需要邮件申请,参考[委托代扣-APP纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AdgAIgbzAB8CNJOvTlNjRTpGQ88aj)
|
||||
3. **预签约成功后首次调起微信有严格时间限制**:需在 2 分钟内调起微信(该时间将来会调整,最短可能缩短至 20 秒)
|
||||
|
||||
## 平台要求
|
||||
|
||||
| 平台 | 最低 SDK 版本 | 资源下载 |
|
||||
|---|---|---|
|
||||
| Android | >=5.3.1 | [Android资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html) |
|
||||
| iOS | >=1.8.4 | [iOS资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/iOS_Resource.html) |
|
||||
| 鸿蒙 | - | [鸿蒙资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/HarmonyOS_Resource.html) |
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 名称 | 变量 | 必填 | 类型 | 示例 | 描述 |
|
||||
|---|---|---|---|---|---|
|
||||
| 跳转签约小程序的 username | `userName` | 是 | String(32) | `gh_xxxxxxxxxxxxx` | 从预签约接口的返回参数 `miniprogram_username` 获取 |
|
||||
| 跳转签约小程序的 path | `path` | 是 | String(256) | `pages/index/index?xxxxxx` | 从预签约接口的返回参数 `miniprogram_path` 获取 |
|
||||
| 跳转的小程序版本 | `miniprogramType` | 是 | Integer | `0` | 固定传值为 0(正式版) |
|
||||
|
||||
## 客户端调用示例
|
||||
|
||||
官方提供以下平台的开发示例(外链):
|
||||
|
||||
- [Android 开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/Android_Development_example.html)
|
||||
- [iOS 开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/iOS_Development_example.html)
|
||||
- [鸿蒙开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/OHOS_Development_example.html)
|
||||
|
||||
## 返回参数
|
||||
|
||||
返回参数内容无需关注。如果签约成功,商户系统会收到 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md)。
|
||||
|
||||
---
|
||||
|
||||
## OpenBusinessWebview vs WXLaunchMiniProgram
|
||||
|
||||
| 对比项 | OpenBusinessWebview | WXLaunchMiniProgram |
|
||||
|---|---|---|
|
||||
| 跳转微信耗时 | 中等 | 更短 |
|
||||
| 用户体验 | 中等 | 更好 |
|
||||
| 支持 Android | 是 | 是 |
|
||||
| 支持 iOS | 是 | 是 |
|
||||
| 支持鸿蒙 | 否 | 是 |
|
||||
| 新增模板 | 禁用 | 邮件申请后可用 |
|
||||
| 存量模板(已接入 OpenBusinessWebview) | 邮件申请关闭后禁用 | 邮件申请后可用 |
|
||||
|
||||
> 2025-09-23 之后申请的模板**只能用 WXLaunchMiniProgram**。
|
||||
> 切换流程详见官方文档"存量委托代扣模板升级指引"章节。
|
||||
|
||||
OpenBusinessWebview 调用方式参见 [APP纯签约 - 步骤2](./APP纯签约.md#步骤-2签约接口客户端-opensdk-调用)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4015996790.md
|
||||
@@ -0,0 +1,49 @@
|
||||
# H5 纯签约(直连商户)
|
||||
|
||||
> **来源**:[H5纯签约](https://pay.weixin.qq.com/doc/v2/merchant/4011987295.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:HMAC-SHA256 / MD5(**默认 HMAC-SHA256**,与 APP 预签约不同)
|
||||
> **是否需要证书**:否
|
||||
|
||||
适用于手机、平板等使用 H5 浏览器的设备。商户在网站前端通过 H5 纯签约接口与用户签订委托扣款协议,再通过后台接口申请扣款。
|
||||
|
||||
> ‼️ 该业务接口需要单独申请,详情查看:[委托代扣-App&H5纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AJ8AyQbhAD4CN41Af5nZSQvqQrvcM)
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/h5entrustweb` |
|
||||
| 请求方式 | GET |
|
||||
|
||||
注意:
|
||||
1. 调用此接口后获得 `redirect_url`,需要在前端跳转到 `redirect_url`
|
||||
2. 不能使用 `window.location.replace(redirect_url)` 跳转,要通过 `window.location.href = redirect_url` 跳转
|
||||
3. 在 `redirect_url` 页面会唤起微信。无论用户是否同意,`redirect_url` 的页面会在 3 秒后自动回到 referer 页面
|
||||
4. 如果获取的 referer 只有域名没有路径,那么签约完成后需要用户手动回到浏览器
|
||||
5. 必须传 `clientip`(用户客户端的 IP 地址,同时支持 IPv4 和 IPv6)
|
||||
|
||||
## 请求示例(URL,来源:官方文档原文)
|
||||
|
||||
```
|
||||
https://api.mch.weixin.qq.com/papay/h5entrustweb?appid=wx426a3015555a46be&contract_code=122&contract_display_account=name1&mch_id=1223816102¬ify_url=https%3A%2F%2Fwww.qq.com%2Ftest%2Fpapay&plan_id=106&request_serial=123& return_appid= wxcbda96de0b165542&clientip=12.1.1.12×tamp=1414488825&version=1.0&sign= 130C7B07DD3B8074F7BF8BEF5C9A86487A1C57478F8C55587876B9C782F72036
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[PARAM_ERROR]]></return_msg>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<result_msg><![CDATA[SIGN_ERROR]]></result_msg>
|
||||
<redirect_url><![CDATA[https://payapp.weixin.qq.com]]></redirect_url>
|
||||
</xml>
|
||||
```
|
||||
|
||||
`redirect_url` 的有效期为 10 分钟。注意:跳转 url 的页面地址必须在微信后台配置(申请 H5 签约权限时配置)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987295.md
|
||||
@@ -0,0 +1,40 @@
|
||||
# 公众号纯签约(直连商户)
|
||||
|
||||
> **来源**:[公众号纯签约](https://pay.weixin.qq.com/doc/v2/merchant/4011986768.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:MD5(仅 MD5,不支持 HMAC-SHA256)
|
||||
> **是否需要证书**:否
|
||||
|
||||
商户可以通过请求此接口唤起微信委托代扣的页面。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/entrustweb` |
|
||||
| 请求方式 | GET |
|
||||
|
||||
注意:
|
||||
1. 商户需要在前端跳转到"微信委托代扣的页面"
|
||||
2. 不能使用 `window.location.replace(redirect_url)` 跳转,要通过 `window.location.href = redirect_url` 跳转
|
||||
|
||||
## 请求参数
|
||||
|
||||
详见官方文档原文。必填字段:`appid` / `mch_id` / `plan_id` / `contract_code` / `request_serial` / `contract_display_account` / `notify_url` / `version`(固定 1.0) / `sign` / `timestamp`。
|
||||
|
||||
`notify_url` 需要 URL encode 处理,注意是对参数值进行 encode。
|
||||
|
||||
## 请求示例(URL)
|
||||
|
||||
```
|
||||
https://api.mch.weixin.qq.com/papay/entrustweb?appid=wx426a3015555a46be&contract_code=122&contract_display_account=name1&mch_id=1223816102¬ify_url=http%3A%2F%2Fwww.qq.com%2Ftest%2Fpapay&plan_id=106&request_serial=123×tamp=1414488825&version=1.0&sign=FF1A406564EE701064450CA2149E2514
|
||||
```
|
||||
|
||||
## 响应
|
||||
|
||||
无 XML 响应。微信会唤起签约页面,签约结果通过 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md) 异步通知 `notify_url`。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011986768.md
|
||||
@@ -0,0 +1,97 @@
|
||||
# 小程序纯签约(直连商户)
|
||||
|
||||
> **来源**:[小程序纯签约](https://pay.weixin.qq.com/doc/v2/merchant/4011987274.md)
|
||||
> **协议版本**:客户端 SDK + V2 签名
|
||||
> **签名方式**:MD5
|
||||
> **是否需要证书**:否
|
||||
|
||||
商户可以通过请求此接口唤起小程序委托代扣的签约页面。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求方式 | `wx.navigateToMiniProgram(OBJECT)` |
|
||||
| 接口兼容 | iOS 微信客户端 6.5.9 起支持,Android 6.5.10 起支持 |
|
||||
| 微信签约小程序 appId | `wxbd687630cd02ce1d`(固定值) |
|
||||
|
||||
> 在小程序配置文件 `app.json` 的 `navigateToMiniProgramAppIdList` 中需要增加此 appid。
|
||||
|
||||
## 商户流程
|
||||
|
||||
1. 用户从商户小程序发起签约请求(前置:委托代扣权限已开通、模板已审批通过、小程序 APPID 与商户号已绑定)
|
||||
2. 商户将签约请求参数按规则签名拼接后,通过小程序跳转向签约小程序发起签约请求
|
||||
3. 用户在微信签约小程序选择支付方式完成签约
|
||||
4. 微信将签约结果返回给商户(通过 `notify_url` 异步通知 + 小程序 `onShow` 同步返回)
|
||||
|
||||
## 请求示例(来源:官方文档原文)
|
||||
|
||||
```javascript
|
||||
wx.navigateToMiniProgram({
|
||||
appId:'wxbd687630cd02ce1d',
|
||||
path:'pages/index/index',
|
||||
extraData:{
|
||||
appid:'wx426a3015555a46be',
|
||||
contract_code:'122',
|
||||
contract_display_account:'张三',
|
||||
mch_id:'1223816102',
|
||||
notify_url:'https://www.qq.com/test/papay',
|
||||
plan_id:'106',
|
||||
request_serial:123,
|
||||
timestamp:1414488825,
|
||||
sign:'FF1A406564EE701064450CA2149E2514'
|
||||
},
|
||||
success(res) {
|
||||
// 成功跳转到签约小程序
|
||||
},
|
||||
fail(res) {
|
||||
// 未成功跳转到签约小程序
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 接收签约结果(来源:官方文档原文)
|
||||
|
||||
用户签约完成后会跳转回商户小程序,可通过 `onShow(OBJECT)` 所携带的参数判断:
|
||||
|
||||
```javascript
|
||||
App({
|
||||
onShow(res) {
|
||||
if (res.scene === 1038) { // 场景值1038:从被打开的小程序返回
|
||||
const { appId, extraData } = res.referrerInfo
|
||||
if (appId == 'wxbd687630cd02ce1d') { // appId为wxbd687630cd02ce1d:从签约小程序跳转回来
|
||||
if (typeof extraData == 'undefined'){
|
||||
// TODO
|
||||
// 客户端小程序不确定签约结果,需要向商户侧后台请求确定签约结果
|
||||
return;
|
||||
}
|
||||
if(extraData.return_code == 'SUCCESS'){
|
||||
// TODO
|
||||
// 客户端小程序签约成功,需要向商户侧后台请求确认签约结果
|
||||
var contract_id = extraData.contract_id
|
||||
return;
|
||||
} else {
|
||||
// TODO
|
||||
// 签约失败
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> 注意:如果用户正常点击微信签约页的确定按钮返回商户小程序,会返回 `extraData`;如果用户点击浏览器左上角返回,则不返回 `extraData`——此时商户必须通过查询接口或异步回调确认签约结果。
|
||||
|
||||
## referrerInfo.extraData 字段
|
||||
|
||||
| 参数 | 变量 | 说明 |
|
||||
|---|---|---|
|
||||
| 返回码 | `return_code` | `SUCCESS`:签约成功 / `FAIL`:签约失败 |
|
||||
| 错误信息 | `return_msg` | 签约失败的错误信息 |
|
||||
| 委托代扣协议 id | `contract_id` | 签约成功后微信返回的协议 id |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987274.md
|
||||
@@ -0,0 +1,83 @@
|
||||
# 支付中签约(直连商户)
|
||||
|
||||
> **来源**:[支付中签约](https://pay.weixin.qq.com/doc/v2/merchant/4011987320.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
支付的同时完成代扣协议签约。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/contractorder` |
|
||||
| 请求方式 | POST |
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. **请求支付中签约接口**获取预支付 id(`prepay_id`)
|
||||
2. **按 trade_type 唤起收银台**:
|
||||
- 扫码支付(NATIVE):参考 [Native 在线文档](https://pay.weixin.qq.com/doc/v2/merchant/4011936456.md)
|
||||
- 公众号支付(JSAPI):参考 [JSAPI 在线文档](https://pay.weixin.qq.com/doc/v2/merchant/4011935213.md)
|
||||
- APP 支付(APP):参考 [APP 在线文档](https://pay.weixin.qq.com/doc/v2/merchant/4011936836.md)
|
||||
- H5(MWEB):参考 [H5 在线文档](https://pay.weixin.qq.com/doc/v2/merchant/4011936565.md)
|
||||
- 小程序:参考 [小程序在线文档](https://pay.weixin.qq.com/doc/v2/merchant/4011939566.md)
|
||||
3. **用户完成支付后**,微信会通过两个回调地址异步通知:
|
||||
- `notify_url` 通知支付结果
|
||||
- `contract_notify_url` 通知签约结果
|
||||
|
||||
## 关键约束
|
||||
|
||||
- `contract_appid` 必须与 `appid` 一致
|
||||
- `contract_mchid` 必须与 `mch_id` 一致
|
||||
- `total_fee` 单位:分(整数)
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<mch_id>1200009811</mch_id>
|
||||
<contract_mchid>1200009811</contract_mchid>
|
||||
<contract_appid>wxcbda96de0b165486</contract_appid>
|
||||
<out_trade_no>123456</out_trade_no>
|
||||
<device_info>013467007045764</device_info>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<body>Ipad mini 16G 白色</body>
|
||||
<detail>Ipad mini 16G 白色</detail>
|
||||
<notify_url>https://weixin.qq.com</notify_url>
|
||||
<total_fee>888</total_fee>
|
||||
<spbill_create_ip>123.12.12.123</spbill_create_ip>
|
||||
<trade_type>JSAPI</trade_type>
|
||||
<plan_id>123</plan_id>
|
||||
<contract_code>100001256</contract_code>
|
||||
<request_serial>1000</request_serial>
|
||||
<contract_display_account>微信代扣</contract_display_account>
|
||||
<contract_notify_url>https://yoursite.com</contract_notify_url>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<mch_id><![CDATA[1200009811]]></mch_id>
|
||||
<prepay_id><![CDATA[wx201410272009395522657a690389285100]]></prepay_id>
|
||||
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
|
||||
<trade_type><![CDATA[JSAPI]]></trade_type>
|
||||
<code_url><![CDATA[weixin://wxpay/s/An4baqw]]></code_url>
|
||||
<plan_id><![CDATA[123]]></plan_id>
|
||||
<out_trade_no><![CDATA[123456]]></out_trade_no>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> ⚠️ 服务商模式**不支持支付中签约**——这是直连商户独有能力。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987320.md
|
||||
@@ -0,0 +1,73 @@
|
||||
# 申请扣款(直连商户)
|
||||
|
||||
> **来源**:[申请扣款](https://pay.weixin.qq.com/doc/v2/merchant/4011987377.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
> **请求频率限制**:150qps
|
||||
|
||||
委托代扣可应用于定期扣款或需事后扣款的场景。例如:会员制缴费、水电煤缴费、增值服务、打车类软件、停车场或高速公路无人缴费、理财通基金定投、信用卡还款等。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/pappayapply` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 关键业务规则
|
||||
|
||||
- 在**周期扣费类场景**,扣费前需要先调用 [预扣费通知](./预扣费通知.md) 为用户下发扣费提醒
|
||||
- **首次签约 12 小时内**的扣款会被立即执行(无延迟);超过 12 小时按预扣费通知规则执行
|
||||
- **同一个商户号 + 同一个商户订单号,只会扣款一次**(幂等);但更换商户号且 out_trade_no 不变会扣款多次
|
||||
- 周期扣费为预扣费通知方式时,扣费操作不可在晚上执行——需在北京时间每天 7:00 ~ 22:00 发起扣款
|
||||
- `total_fee` 单位为分(整数)
|
||||
- `trade_type` 固定为 `PAP`
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
<body>水电代扣</body>
|
||||
<out_trade_no>217752501201407033233368018</out_trade_no>
|
||||
<total_fee>888</total_fee>
|
||||
<spbill_create_ip>8.8.8.8</spbill_create_ip>
|
||||
<notify_url>http://yoursite.com/wxpay.html</notify_url>
|
||||
<contract_id>Wx15463511252015071056489715</contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<mch_id><![CDATA[10000098]]></mch_id>
|
||||
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
|
||||
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> ⚠️ `result_code=SUCCESS` 仅代表**请求受理成功**,实际扣款结果通过 [扣款结果通知](../7-异步结果回调/扣款结果通知.md) 异步推送,或主动调 [查询订单](../4-订单协议查询/查询订单.md) 确认。
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 | 处理 |
|
||||
|---|---|---|
|
||||
| `CONTRACT_NOT_EXIST` | 签约协议不存在 | 检查 contract_id 或调查询签约关系确认状态 |
|
||||
| `ORDERPAID` | 订单已支付 | 该 out_trade_no 已扣款,请查询订单 |
|
||||
| `CONTRACTERROR` | 协议已过期 | 检查签约协议号是否过期 |
|
||||
| `RULELIMIT` | 该笔交易存在风险 | 联系用户或微信客服 |
|
||||
| `FREQUENCY_LIMITED` | 频率限制 | 降低调用频率 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987377.md
|
||||
@@ -0,0 +1,83 @@
|
||||
# 预扣费通知 API(直连商户)
|
||||
|
||||
> **来源**:[预扣费通知 API](https://pay.weixin.qq.com/doc/v2/merchant/4011987402.md)
|
||||
> **协议版本**:⚠️ **API V3**(JSON + SHA256-RSA2048,不是 V2 XML)
|
||||
> **接口请求方**:具有委托代扣扣费权限,且**另行开通了预扣费通知权限**的直连商户
|
||||
|
||||
商户在扣费前需要在可通知时间段内调用本接口为用户发送扣费提醒,并设定预计扣费金额。经过扣费等待期后,在可扣费期内可发起扣费。
|
||||
|
||||
> ⚠️ 一个扣费周期内只能扣款成功一次。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/v3/papay/contracts/{contract_id}/notify` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | JSON |
|
||||
| 接口规则 | [V3 接口规则概述](https://pay.weixin.qq.com/doc/v3/merchant/4012081606.md) |
|
||||
|
||||
## 时间窗口规则
|
||||
|
||||
| 概念 | 说明 |
|
||||
|---|---|
|
||||
| 扣费等待期 | 商户调用本接口成功当日 + 第二个自然日 |
|
||||
| 扣费持续天数 | 默认 7 天;扣费等待期后,能进行扣款的自然日天数 |
|
||||
| 可扣费期 | 扣费等待期后的 N 个自然日(N = 扣费持续天数) |
|
||||
| 可通知时间段 | 北京时间每天 7:00 ~ 22:00 |
|
||||
|
||||
举例:扣费持续天数为 1 时,1 号下发通知,2 号是扣费等待期,3 号才能正常扣款。
|
||||
|
||||
## 请求示例(JSON,来源:官方文档原文)
|
||||
|
||||
```json
|
||||
{
|
||||
"mchid" : "1230000109",
|
||||
"appid" : "wxd678efh567hg6787",
|
||||
"deduct_duration" : {
|
||||
"count" : 1,
|
||||
"unit" : "DAY"
|
||||
},
|
||||
"estimated_amount" : {
|
||||
"amount" : 1,
|
||||
"currency" : "CNY"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `mchid`:直连商户号
|
||||
- `appid`:直连商户 APPID
|
||||
- `deduct_duration.count`:扣费持续时间数(1 ~ 3 天)
|
||||
- `deduct_duration.unit`:固定 `DAY`
|
||||
- `estimated_amount.amount`:预计扣款金额(**分**,整数)
|
||||
- `estimated_amount.currency`:默认 `CNY`
|
||||
|
||||
## 响应示例
|
||||
|
||||
```
|
||||
无数据(HTTP 状态码为 204)
|
||||
```
|
||||
|
||||
## V3 签名要求
|
||||
|
||||
- 使用 V3 签名算法:SHA256-RSA2048
|
||||
- 5 行格式:`HTTP方法\nURL路径\n时间戳\n随机串\n请求体\n`
|
||||
- 用商户 API 私钥(apiclient_key.pem)签名
|
||||
- `Authorization` 头格式:`WECHATPAY2-SHA256-RSA2048 mchid="..." nonce_str="..." signature="..." timestamp="..." serial_no="..."`
|
||||
|
||||
详见 [📄 商户模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `CONTRACT_NOT_EXIST` | 签约协议不存在 |
|
||||
| `RESOURCE_ALREADY_EXISTS` | 资源已经存在(已成功发送通知,无需重复调用) |
|
||||
| `RULELIMIT` | 该请求存在风险 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987402.md
|
||||
@@ -0,0 +1,88 @@
|
||||
# 申请解约(直连商户)
|
||||
|
||||
> **来源**:[申请解约](https://pay.weixin.qq.com/doc/v2/merchant/4011987432.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:MD5
|
||||
> **是否需要证书**:否
|
||||
|
||||
商户与用户的签约关系有误或者商户主动要求与用户解除签约协议时调用本接口。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/deletecontract` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 解约方式(任选其一)
|
||||
|
||||
1. **方式 1**:使用 `contract_id` 解约
|
||||
2. **方式 2**:使用 `plan_id + contract_code` 解约
|
||||
|
||||
两种方式返回结果相同。
|
||||
|
||||
商户可以在商户后台(pay.weixin.qq.com)设置解约回调地址,发生解约关系时微信会通知,内容详见 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md)。
|
||||
|
||||
---
|
||||
|
||||
## 方式 1:使用 contract_id 解约
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sign>E1EE61A91C8E90F299DE6AE075D60A2D</sign>
|
||||
<contract_id>100005698</contract_id>
|
||||
<contract_termination_remark>原因</contract_termination_remark>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方式 2:使用 plan_id + contract_code 解约
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sign>E1EE61A91C8E90F299DE6AE075D60A2D</sign>
|
||||
<plan_id>12251</plan_id>
|
||||
<contract_code>1234</contract_code>
|
||||
<contract_termination_remark>原因</contract_termination_remark>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id><![CDATA[10010404]]></mch_id>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<contract_id><![CDATA[100005698]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `SIGN_ERROR` | 签名错误 |
|
||||
| `PARAMETER FAIL` | 参数错误 |
|
||||
| `MERCHANT PERMISSION ERROR` | 商户没有权限 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987432.md
|
||||
@@ -0,0 +1,108 @@
|
||||
# 查询签约关系(直连商户)
|
||||
|
||||
> **来源**:[查询签约关系](https://pay.weixin.qq.com/doc/v2/merchant/4011987640.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
> **请求频率限制**:默认 300qps
|
||||
|
||||
查询签约关系接口提供单笔签约关系查询。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/querycontract` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 查询方式(任选其一)
|
||||
|
||||
1. **方式 1**:使用 `contract_id` 查询
|
||||
2. **方式 2**:使用 `plan_id + contract_code` 查询
|
||||
|
||||
两种方式返回结果相同。
|
||||
|
||||
---
|
||||
|
||||
## 方式 1:使用 contract_id 查询
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<sign>019C869758CC7F258C42F05CDB9EE361</sign>
|
||||
<mch_id>10000097</mch_id>
|
||||
<appid>wxf5b5e87a6a0fde94</appid>
|
||||
<contract_id>201509160000028648</contract_id>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方式 2:使用 plan_id + contract_code 查询
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<sign>019C869758CC7F258C42F05CDB9EE361</sign>
|
||||
<mch_id>10000097</mch_id>
|
||||
<appid>wxf5b5e87a6a0fde94</appid>
|
||||
<plan_id>123</plan_id>
|
||||
<contract_code>1023658866</contract_code>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code> <![CDATA[SUCCESS]]> </return_code>
|
||||
<result_code> <![CDATA[SUCCESS]]> </result_code>
|
||||
<mch_id> <![CDATA[80000000]]> </mch_id>
|
||||
<appid> <![CDATA[wx426b3015555b46be]]> </appid>
|
||||
<contract_id>203</contract_id>
|
||||
<plan_id>66</plan_id>
|
||||
<openid> <![CDATA[oHZx6uMbIG46UXQ3SKxVYEgw1LZs]]> </openid>
|
||||
<request_serial>123</request_serial>
|
||||
<contract_code> <![CDATA[1005]]> </contract_code>
|
||||
<contract_display_account> <![CDATA[test]]> </contract_display_account>
|
||||
<contract_state>1</contract_state>
|
||||
<contract_signed_time>2015-07-01 10:00:00</contract_signed_time>
|
||||
<contract_expired_time>2015-07-01 10:00:00</contract_expired_time>
|
||||
<contract_terminated_time>2015-07-01 10:00:00</contract_terminated_time>
|
||||
<contract_termination_mode>3</contract_termination_mode>
|
||||
<contract_termination_remark> <![CDATA[delete ....]]> </contract_termination_remark>
|
||||
<err_code>0</err_code>
|
||||
<err_code_des> <![CDATA[SUCCESS]]> </err_code_des>
|
||||
<sign> <![CDATA[8FC9DACB7DDF9B48333DCCC2224E0CAC]]> </sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## contract_state 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `0` | 已签约 |
|
||||
| `1` | 未签约 |
|
||||
| `9` | 签约进行中 |
|
||||
|
||||
## contract_termination_mode 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `1` | 有效期过自动解约(预留功能) |
|
||||
| `2` | 用户主动解约 |
|
||||
| `3` | 商户 API 解约 |
|
||||
| `4` | 商户平台解约 |
|
||||
| `5` | 注销(用户微信账户注销) |
|
||||
| `7` | 用户联系客服发起的解约 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987640.md
|
||||
@@ -0,0 +1,82 @@
|
||||
# 查询订单(直连商户)
|
||||
|
||||
> **来源**:[查询订单](https://pay.weixin.qq.com/doc/v2/merchant/4011987538.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
提供所有微信支付订单的查询。商户可以通过本接口主动查询订单状态,完成下一步业务逻辑。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/orderquery` |
|
||||
| 备用域名 | `https://api2.mch.weixin.qq.com/pay/orderquery`([跨城冗灾方案](https://pay.weixin.qq.com/doc/v2/merchant/4011984887.md)) |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 何时需要调用查询接口
|
||||
|
||||
- 商户后台、网络、服务器异常导致最终未接收到支付通知
|
||||
- 调用支付接口后返回系统错误或未知交易状态
|
||||
- 调用关单或撤销接口前,需确认支付状态
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx8888888888888888</appid>
|
||||
<mch_id>1900000109</mch_id>
|
||||
<transaction_id>1008450740201411110005820873</transaction_id>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> `transaction_id` / `out_trade_no` 二选一,建议优先使用 `transaction_id`。
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<device_info><![CDATA[1000]]></device_info>
|
||||
<nonce_str><![CDATA[TN55wO9Pba5yENl8]]></nonce_str>
|
||||
<sign><![CDATA[BDF0099C15FF7BC6B1585FBB110AB635]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<openid><![CDATA[oUpF8uN95-Ptaags6E_roPHg7AG0]]></openid>
|
||||
<is_subscribe><![CDATA[N]]></is_subscribe>
|
||||
<trade_type><![CDATA[MICROPAY]]></trade_type>
|
||||
<bank_type><![CDATA[CCB_DEBIT]]></bank_type>
|
||||
<total_fee>1</total_fee>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<attach><![CDATA[订单额外描述]]></attach>
|
||||
<time_end><![CDATA[20141111170043]]></time_end>
|
||||
<trade_state><![CDATA[SUCCESS]]></trade_state>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## trade_state 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `SUCCESS` | 支付成功 |
|
||||
| `REFUND` | 转入退款 |
|
||||
| `NOTPAY` | 未支付 |
|
||||
| `CLOSED` | 已关闭 |
|
||||
| `REVOKED` | 已撤销(刷卡支付) |
|
||||
| `USERPAYING` | 用户支付中 |
|
||||
| `PAYERROR` | 支付失败(其他原因,如银行返回失败) |
|
||||
| `ACCEPT` | 已接收,等待扣款 |
|
||||
|
||||
> 委托代扣场景下 `trade_type` 为 `PAP`。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987538.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# 查询退款(直连商户)
|
||||
|
||||
> **来源**:[查询退款](https://pay.weixin.qq.com/doc/v2/merchant/4011987766.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
提交退款申请后,通过本接口查询退款状态。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/refundquery` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 退款时效
|
||||
|
||||
- 用零钱支付的退款 20 分钟内到账
|
||||
- 银行卡支付的退款 3 个工作日后重新查询退款状态
|
||||
- 单个支付订单部分退款次数超过 20 次请使用退款单号查询
|
||||
|
||||
## 分页查询
|
||||
|
||||
当一个订单部分退款超过 10 笔后,默认返回前 10 笔和 `total_refund_count`(订单总退款次数)。需要查询超过 10 笔时,传入订单号 + `offset` 来分页。
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>0b9f35f484df17a732e537c37708d1d0</nonce_str>
|
||||
<out_refund_no></out_refund_no>
|
||||
<out_trade_no>1415757673</out_trade_no>
|
||||
<refund_id></refund_id>
|
||||
<transaction_id></transaction_id>
|
||||
<sign>66FFB727015F450D167EF38CCC549521</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> `transaction_id` / `out_trade_no` / `out_refund_no` / `refund_id` 四选一。
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<nonce_str><![CDATA[TeqClE3i0mvn3DrK]]></nonce_str>
|
||||
<out_refund_no_0><![CDATA[1415701182]]></out_refund_no_0>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<refund_count>1</refund_count>
|
||||
<refund_fee_0>1</refund_fee_0>
|
||||
<refund_id_0><![CDATA[2008450740201411110000174436]]></refund_id_0>
|
||||
<refund_status_0><![CDATA[PROCESSING]]></refund_status_0>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<sign><![CDATA[1F2841558E233C33ABA71A961D27561C]]></sign>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## refund_status 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `SUCCESS` | 退款成功 |
|
||||
| `REFUNDCLOSE` | 退款关闭 |
|
||||
| `PROCESSING` | 退款处理中 |
|
||||
| `CHANGE` | 退款异常(退款到银行发现卡作废或冻结,需到商户平台手动处理) |
|
||||
|
||||
## refund_account 取值(退款资金来源)
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `REFUND_SOURCE_RECHARGE_FUNDS` | 可用余额退款 / 基本账户 |
|
||||
| `REFUND_SOURCE_UNSETTLED_FUNDS` | 未结算资金退款 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987766.md
|
||||
@@ -0,0 +1,75 @@
|
||||
# 申请退款(直连商户)
|
||||
|
||||
> **来源**:[申请退款](https://pay.weixin.qq.com/doc/v2/merchant/4011987741.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:✅ **需要双向证书**(apiclient_cert.p12,密码 = mch_id)
|
||||
> **请求频率限制**:150qps
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/secapi/pay/refund` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 关键业务规则
|
||||
|
||||
1. 交易时间超过一年的订单无法提交退款
|
||||
2. 单笔交易支持分多次退款,多次退款需要提交原支付订单的商户订单号 + 设置不同的退款单号;申请退款总金额不能超过订单金额
|
||||
3. 一笔退款失败后**重新提交,请不要更换退款单号**——使用原商户退款单号
|
||||
4. 错误或无效请求频率限制:6qps
|
||||
5. 每个支付订单的部分退款次数不能超过 50 次
|
||||
6. 申请退款接口的返回**仅代表业务的受理情况**,具体退款是否成功需要通过 [查询退款](./查询退款.md) 接口获取结果
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str>
|
||||
<out_refund_no>1415701182</out_refund_no>
|
||||
<out_trade_no>1415757673</out_trade_no>
|
||||
<refund_fee>1</refund_fee>
|
||||
<total_fee>1</total_fee>
|
||||
<transaction_id>4006252001201705123297353072</transaction_id>
|
||||
<sign>FE56DD4AA85C0EECA82C35595A69E153</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<nonce_str><![CDATA[NfsMFbUFpdbEhPXP]]></nonce_str>
|
||||
<sign><![CDATA[B7274EB9F8925EB93100DD2085FA56C0]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<out_refund_no><![CDATA[1415701182]]></out_refund_no>
|
||||
<refund_id><![CDATA[2008450740201411110000174436]]></refund_id>
|
||||
<refund_fee>1</refund_fee>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 | 处理 |
|
||||
|---|---|---|
|
||||
| `SYSTEMERROR` | 接口返回错误 | **不要更换商户退款单号**,使用相同参数重试 |
|
||||
| `BIZERR_NEED_RETRY` | 退款业务流程错误 | 不要更换退款单号,重试 |
|
||||
| `TRADE_OVERDUE` | 订单已经超过退款期限 | 选择其他方式自行退款 |
|
||||
| `NOTENOUGH` | 余额不足 | 商户充值后重试 |
|
||||
| `INVALID_TRANSACTIONID` | 无效 transaction_id | 检查原交易号 |
|
||||
| `REFUND_FEE_MISMATCH` | 退款金额与之前请求不一致 | 核实金额 |
|
||||
| `CERT_ERROR` | 证书校验错误 | 检查 apiclient_cert.p12 是否正确 / 是否过期 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987741.md
|
||||
@@ -0,0 +1,93 @@
|
||||
# 下载交易账单(直连商户)
|
||||
|
||||
> **来源**:[下载交易账单](https://pay.weixin.qq.com/doc/v2/merchant/4011987833.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
商户可以通过该接口下载历史交易清单,用于对账。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/downloadbill` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML(请求)/ CSV 文本(响应成功)/ XML(响应失败) |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 微信侧未成功下单的交易不会出现在对账单中
|
||||
2. 微信在次日 9 点启动生成前一天的对账单,**建议商户 10 点后再获取**
|
||||
3. 对账单中**金额字段单位为"元"**(与请求接口的"分"不同)
|
||||
4. 对账单接口只能下载三个月以内的账单
|
||||
5. 对账单是以**商户号**纬度生成
|
||||
|
||||
## bill_type 取值
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| `ALL` | 默认值,返回当日所有订单(不含充值退款) |
|
||||
| `SUCCESS` | 当日成功支付的订单 |
|
||||
| `REFUND` | 当日退款订单 |
|
||||
| `RECHARGE_REFUND` | 当日充值退款订单 |
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<bill_date>20141110</bill_date>
|
||||
<bill_type>ALL</bill_type>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>21df7dc9cd8616b56919f20d9f679233</nonce_str>
|
||||
<sign>332F17B766FC787203EBE9D6E40457A1</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应
|
||||
|
||||
**成功时**:返回文本表格(CSV 格式),首行为表头,最后两行为统计行。
|
||||
|
||||
表头按 `bill_type` 不同:
|
||||
|
||||
- **ALL**:交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
|
||||
- **SUCCESS**:交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
|
||||
- **REFUND**:交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
|
||||
|
||||
数据行各参数以逗号分隔,参数前增加 `\`` 符号(防止 Excel 自动转换为科学计数法等)。
|
||||
|
||||
**最后两行为统计行**:
|
||||
|
||||
```
|
||||
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
|
||||
`2,`0.02,`0.0,`0.0,`0
|
||||
```
|
||||
|
||||
**失败时**:返回 XML
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code>FAIL</return_code>
|
||||
<return_msg>...</return_msg>
|
||||
<err_code>20002</err_code>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## tar_type 可选值
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| `GZIP` | 返回 .gzip 压缩包账单 |
|
||||
| 不传 | 返回数据流形式 |
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `20002 NO Bill Exist` | 账单不存在(当日无成功交易) |
|
||||
| `20002 Bill Creating` | 账单未生成(10 点后再下载) |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987833.md
|
||||
@@ -0,0 +1,60 @@
|
||||
# 关闭订单(直连商户)
|
||||
|
||||
> **来源**:[关闭订单](https://pay.weixin.qq.com/doc/v2/merchant/4011987803.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 应用场景
|
||||
|
||||
- 商户订单支付失败需要生成新单号重新发起支付时,要对原订单号调用关单,避免重复支付
|
||||
- 系统下单后用户支付超时,系统退出不再受理时,调用关单接口
|
||||
|
||||
> 注意:订单生成后**不能马上调用关单接口**,最短调用时间间隔为 5 分钟。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 直连商户 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/closeorder` |
|
||||
| 备用域名 | `https://api2.mch.weixin.qq.com/pay/closeorder` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>4ca93f17ddf3443ceabf72f26d64fe0e</nonce_str>
|
||||
<out_trade_no>1415983244</out_trade_no>
|
||||
<sign>59FF1DF214B2D279A0EA7077C54DD95D</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<nonce_str><![CDATA[BFK89FC6rxKCOjLX]]></nonce_str>
|
||||
<sign><![CDATA[72B321D92A7BFA0B2509F3D13C7B1631]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `ORDERPAID` | 订单已支付,不能发起关单 |
|
||||
| `ORDERCLOSED` | 订单已关闭 |
|
||||
| `SIGNERROR` | 签名错误 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987803.md
|
||||
@@ -0,0 +1,94 @@
|
||||
# 扣款结果通知(直连商户)
|
||||
|
||||
> **来源**:[扣款结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011987465.md)
|
||||
> **协议版本**:API V2(XML,**不加密**)
|
||||
> **触发**:[申请扣款](../2-代扣扣款/申请扣款.md) 受理后,扣款成功或失败时
|
||||
|
||||
## 通知规则
|
||||
|
||||
- 微信会把支付结果发送给商户在申请扣款接口中提交的 `notify_url`
|
||||
- 商户必须返回应答;如果应答不符合规范或超时,微信认为通知失败
|
||||
- **重试频率**:`0/15/15/30/180/1800/1800/1800/1800/3600`(单位:秒)
|
||||
- 如果在所有通知频率后没有收到回调,商户应主动调用 [查询订单](../4-订单协议查询/查询订单.md) 确认订单状态
|
||||
|
||||
## ⚠️ 特别提醒
|
||||
|
||||
1. 商户系统对通知内容**一定要做签名验证**,并校验返回的订单金额是否与商户侧的订单金额一致,防止"假通知"造成资金损失
|
||||
2. 同样的通知可能多次发送给商户系统,**必须做幂等处理**——先检查业务数据状态,未处理才处理;处理前用数据锁进行并发控制
|
||||
3. 针对回调结果为 `err_code=SYSTEMERROR` 的订单,需先查询订单:若 `trade_state` ≠ `CLOSED`,需先关闭订单,再换单号重新申请延迟扣费(24 小时后执行)
|
||||
|
||||
---
|
||||
|
||||
## 扣款成功通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<attach><![CDATA[支付测试]]></attach>
|
||||
<bank_type><![CDATA[CFT]]></bank_type>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<is_subscribe><![CDATA[N]]></is_subscribe>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
|
||||
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
|
||||
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
|
||||
<time_end><![CDATA[20140903131540]]></time_end>
|
||||
<total_fee>1</total_fee>
|
||||
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 扣款失败通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
|
||||
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
|
||||
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<bank_type><![CDATA[CFT]]></bank_type>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<is_subscribe><![CDATA[N]]></is_subscribe>
|
||||
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 商户必须返回的应答(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要扣款失败错误码
|
||||
|
||||
| 错误码 | 描述 | 解决方案 |
|
||||
|---|---|---|
|
||||
| `ACCOUNTERROR` | 用户账户异常 | 请用户检查账户后再试 |
|
||||
| `CONTRACT_NOT_EXIST` | 协议不存在,用户已解约 | 建议重新签约 |
|
||||
| `RULELIMIT` | 用户账户支付已达上限 | 建议更换银行卡 |
|
||||
| `BANKERROR` | 支付银行卡所在行渠道维护中 | 建议更换银行卡 |
|
||||
| `NOTENOUGH` | 余额不足 | 建议更换银行卡 |
|
||||
| `USER_ACCOUNT_ABNORMAL` | 用户账户异常 | 检查微信账号 |
|
||||
| `USER_NOT_EXIST` | 用户账户注销 | 检查微信账号 |
|
||||
| `TRADE_ERROR` | 订单错误 | 检查用户账号 |
|
||||
|
||||
## 验签实现
|
||||
|
||||
参见 [📄 商户模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987465.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# 签约/解约结果通知(直连商户)
|
||||
|
||||
> **来源**:[签约、解约结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011987586.md)
|
||||
> **协议版本**:API V2(XML,**不加密**)
|
||||
> **触发**:用户完成签约或解约(包含用户主动解约)
|
||||
|
||||
## 通知规则
|
||||
|
||||
- **签约结果通知路径**:签约接口商户上传的 `notify_url`
|
||||
- **解约结果通知路径**:商户配置委托扣款模板 ID 时填写的解约回调地址(必须为 https)
|
||||
- **重试频率**:`0/10/10/10/30/30/30/300/300/...`(单位:秒,最多 30 次)
|
||||
- 如果在所有通知频率后没有收到回调,商户应调用 [查询签约关系](../4-订单协议查询/查询签约关系.md) 确认状态
|
||||
|
||||
## ⚠️ 特别提醒
|
||||
|
||||
1. **必须做签名验证**,并校验返回的商户协议号和用户 openid 信息是否一致,防止"假通知"
|
||||
2. **必须做幂等**——同样的通知可能多次发送
|
||||
3. 商户记录的签约用户 `openid`,需以**微信侧回调通知的 openid 为准**,不建议商户使用自行记录的 openid(避免扣款用户和签约用户对不上)
|
||||
|
||||
---
|
||||
|
||||
## 通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id>10010404</mch_id>
|
||||
<contract_code>100001256</contract_code>
|
||||
<openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6ua]]></openid>
|
||||
<plan_id><![CDATA[123]]></plan_id>
|
||||
<change_type><![CDATA[ADD]]></change_type>
|
||||
<operate_time><![CDATA[2015-07-01 10:00:00]]></operate_time>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## change_type 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `ADD` | 签约 |
|
||||
| `DELETE` | 解约 |
|
||||
|
||||
商户可通过该字段判断是签约回调还是解约回调。
|
||||
|
||||
## contract_termination_mode(仅 change_type=DELETE 时返回)
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `1` | 有效期过自动解约(预留功能) |
|
||||
| `2` | 用户主动解约 |
|
||||
| `3` | 商户 API 解约 |
|
||||
| `4` | 商户平台解约 |
|
||||
| `5` | 注销(用户微信账户注销) |
|
||||
| `7` | 用户联系客服发起的解约 |
|
||||
|
||||
---
|
||||
|
||||
## 商户必须返回的应答(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 微信支付签约/解约通知出口 IP
|
||||
|
||||
如商户侧配置了防火墙,需对回调通知功能开通下面白名单网段:
|
||||
|
||||
```
|
||||
101.226.233.128/25
|
||||
```
|
||||
|
||||
## 验签实现
|
||||
|
||||
参见 [📄 商户模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/merchant/4011987586.md
|
||||
@@ -0,0 +1,81 @@
|
||||
# 商户模式接口索引
|
||||
|
||||
> 委托代扣**官方文档未提供任何服务端代码示例**(Java / Go / PHP / Python 等服务端语言均无)。本 skill 直接收录每个接口在官方文档中真实存在的请求/响应报文样例(XML / URL / JSON)和客户端调起代码(iOS / Android / 小程序 JS / 鸿蒙),不做任何编造。
|
||||
>
|
||||
> 凡是官方提供示例代码的接口(APP纯签约步骤2 / 小程序纯签约 / APP调起签约),代码均**逐字摘录自官方文档**,并标注来源。
|
||||
>
|
||||
> 接入流程建议:① 读取下面对应接口的 .md 拿到官方报文 → ② 阅读完整字段定义请打开顶部"来源"链接 → ③ 用 [📄 商户模式签名与验签规则](../接入指南/签名与验签规则.md) 实现签名/验签 → ④ 用 [📄 商户模式回调处理](../接入指南/回调处理.md) 实现回调 → ⑤ 用 [📄 商户模式接入质量检查](../接入指南/接入质量检查.md) 自检。
|
||||
|
||||
## ⚠️ 协议版本一览
|
||||
|
||||
| 接口分类 | 协议 | 数据格式 | 签名算法 |
|
||||
|---|---|---|---|
|
||||
| 业务主流程(签约 / 扣款 / 解约 / 查询 / 退款 / 关单 / 账单) | API V2 | XML | MD5 / HMAC-SHA256 |
|
||||
| 预扣费通知 | **API V3** | JSON | SHA256-RSA2048 |
|
||||
| 异步回调(扣款 / 签约 / 解约) | API V2 | XML(**不加密**) | MD5 / HMAC-SHA256 |
|
||||
|
||||
## 业务接口
|
||||
|
||||
### 1-签约
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 公众号纯签约 | V2 GET | [公众号纯签约.md](./1-用户签约/公众号纯签约.md) |
|
||||
| APP 纯签约(含步骤 2 iOS/Android SDK) | V2 POST + 客户端 SDK | [APP纯签约.md](./1-用户签约/APP纯签约.md) |
|
||||
| 小程序纯签约(含 wx.navigateToMiniProgram + onShow JS) | 客户端 SDK + V2 签名 | [小程序纯签约.md](./1-用户签约/小程序纯签约.md) |
|
||||
| H5 纯签约 | V2 GET | [H5纯签约.md](./1-用户签约/H5纯签约.md) |
|
||||
| APP 调起签约(WXLaunchMiniProgram,外链官方 Android/iOS/鸿蒙开发示例) | 客户端 SDK | [APP调起签约.md](./1-用户签约/APP调起签约.md) |
|
||||
| 支付中签约 | V2 POST | [支付中签约.md](./1-用户签约/支付中签约.md) |
|
||||
|
||||
### 2-扣款
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请扣款 | V2 POST | [申请扣款.md](./2-代扣扣款/申请扣款.md) |
|
||||
| 预扣费通知 API | **V3 POST** | [预扣费通知.md](./2-代扣扣款/预扣费通知.md) |
|
||||
|
||||
### 3-解约
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请解约(contract_id 或 plan_id+contract_code 两种方式) | V2 POST | [申请解约.md](./3-协议解约/申请解约.md) |
|
||||
|
||||
### 4-查询
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 查询订单 | V2 POST | [查询订单.md](./4-订单协议查询/查询订单.md) |
|
||||
| 查询签约关系(contract_id 或 plan_id+contract_code 两种方式) | V2 POST | [查询签约关系.md](./4-订单协议查询/查询签约关系.md) |
|
||||
|
||||
### 5-退款
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请退款(**需 apiclient_cert.p12**) | V2 POST + mTLS | [申请退款.md](./5-退款流程/申请退款.md) |
|
||||
| 查询退款 | V2 POST | [查询退款.md](./5-退款流程/查询退款.md) |
|
||||
|
||||
### 6-订单管理
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 关闭订单 | V2 POST | [关闭订单.md](./6-订单关单对账/关闭订单.md) |
|
||||
| 下载交易账单(响应是 CSV,非 XML) | V2 POST | [下载交易账单.md](./6-订单关单对账/下载交易账单.md) |
|
||||
|
||||
### 7-回调通知
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 扣款结果通知 | V2 XML 不加密 | [扣款结果通知.md](./7-异步结果回调/扣款结果通知.md) |
|
||||
| 签约/解约结果通知 | V2 XML 不加密 | [签约-解约结果通知.md](./7-异步结果回调/签约-解约结果通知.md) |
|
||||
|
||||
---
|
||||
|
||||
## 服务端代码 / 签名实现参考
|
||||
|
||||
委托代扣官方未提供任何服务端代码示例(Java / Go / PHP / Python 等服务端语言均无),可参考的开源实现:
|
||||
|
||||
- **微信支付官方 V3 SDK**(不直接用于 V2,但 [`wechatpay-apiv3` GitHub 组织](https://github.com/wechatpay-apiv3)下的工具类可借鉴)
|
||||
- **官方 V2 DEMO**(含通用 V2 接口示例,可参考其签名与 XML 处理):[DEMO下载](https://pay.weixin.qq.com/doc/v2/merchant/4011985389.md)
|
||||
- **签名算法描述**:[📄 商户模式签名与验签规则](../接入指南/签名与验签规则.md)
|
||||
|
||||
> ⚠️ 用户如果坚持要 Java/Go 业务代码示例,请按 SKILL.md 能力 2 的"跨语言参考实现流程"——先 `AskQuestion` 让用户明确知晓"非官方维护、可能引发资金事故"风险,并用 WebFetch 当场打开本 skill 中"来源"链接的官方文档,**逐字段对照构造**——严禁凭训练知识猜测路径或字段名。
|
||||
@@ -0,0 +1,239 @@
|
||||
# 商户模式排障手册
|
||||
|
||||
> 本文档是本角色 + 本产品排障的**唯一入口**。另一接入模式见对应角色目录下同名文件。
|
||||
>
|
||||
> ‼️ **使用规则**:用户报告任何问题(报错 / 接口异常 / 回调收不到 / 签名失败 / 对账差异等),**先加载本文档**按下方流程匹配,不要先翻其他文档或猜原因。
|
||||
>
|
||||
> ‼️ **语气**:像有经验的技术支持,自然对话解释原因和方案,不要冷冰冰罗列文档目录。
|
||||
|
||||
## 排障流程
|
||||
|
||||
1. **能给 Request-Id?** → 走「一、错误码 TOP 20」:取 Request-Id 末尾 `-` 后的数字(如 `...CF05-268578704` → `268578704`)在速查表匹配,命中后用「错误码详细排查」对应段落回复。
|
||||
2. **不能给 / 未命中 TOP 20?** → 走「二、常见问题」:按现象(HTTP / 回调 / 签名 / 退款 / 角色特有 / 业务规则 / 通用配置)定位子节。
|
||||
3. **两条都没命中?** → 用末尾「排障信息收集清单」回收信息后再判断。
|
||||
|
||||
---
|
||||
|
||||
## 一、错误码 TOP 20(Request-Id 场景)
|
||||
|
||||
> 由用户基于真实工单 / 客服系统数据**手动提供**,模型不得自行填充。
|
||||
|
||||
### 1.1 TOP 20 速查表
|
||||
|
||||
[等待用户补充:从该产品真实工单 / 客服系统统计高频错误码 TOP 20,按下表格式手动填入。]
|
||||
|
||||
| 错误码 | 错误信息 | 分类 |
|
||||
|:------:|---------|:----:|
|
||||
| [等待补充] | [等待补充] | [等待补充] |
|
||||
|
||||
### 1.2 错误码详细排查
|
||||
|
||||
[等待用户补充:与上方 TOP 20 一一对应,每个错误码一段。]
|
||||
|
||||
---
|
||||
|
||||
## 二、常见问题(无 Request-Id 场景)
|
||||
|
||||
> 来源:该产品官方「常见问题」文档 + 通用接入经验沉淀。
|
||||
|
||||
### 2.1 HTTP 错误(401 / 400 / 403)
|
||||
|
||||
| 状态码 | 含义 | 常见原因 | 排查要点 |
|
||||
|:----:|------|---------|---------|
|
||||
| 401 | 签名验证失败 | 私钥与证书不匹配;serial_no 填错;签名串拼接有误(换行符 / URL / body 为空时缺末尾换行);时间戳偏差过大 | 检查 Authorization 头格式;确认私钥正确加载;建议用官方 SDK |
|
||||
| 400 | 请求参数错误 | 必填参数缺失;金额单位是分不是元;时间格式不符 RFC 3339;JSON 层级错误 | 对照 API 文档逐项检查;金额单位是**分**;时间格式 `yyyy-MM-ddTHH:mm:ss+08:00` |
|
||||
| 403 | 权限不足 | 未开通对应支付产品;IP 不在白名单;商户号状态异常 | 商户平台 → 产品中心确认开通状态 |
|
||||
|
||||
### 2.2 回调问题
|
||||
|
||||
**收不到回调排查清单**(按优先级):① 地址不可达(URL 错 / 域名解析失败 / localhost / 服务未启动)→ ② URL 前后有空格致 DNS 失败 → ③ 防火墙拦截(未对回调 IP 段开白名单,见下方 IP)→ ④ 登录态拦截(notify_url 须从鉴权中间件中排除)→ ⑤ 响应非 200(如 FAIL / 404,重试后放弃)→ ⑥ 处理超时(须 5 秒内应答)→ ⑦ 域名未 ICP 备案 → ⑧ 商户号用错(实际收到了但用另一个 mchid 查单导致"订单不存在")。
|
||||
|
||||
**回调行为 Q&A**:
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 怎么确认微信发了回调? | 微信不提供回调日志查询,检查自身服务器访问日志 + 查单接口确认 |
|
||||
| 2 | 回调会重复收到吗? | 会,未正确响应时微信会重试,业务必须做幂等 |
|
||||
| 3 | 回调延迟正常吗? | 数秒到数十秒均属正常。建议回调 + 主动查单双保险 |
|
||||
| 4 | 能直接将回调当最终结果吗? | 不能,回调不保证送达,需结合查单接口确认 |
|
||||
| 5 | 商户平台能查回调状态吗? | 不支持,需调用查单接口 |
|
||||
| 6 | 回调怎么测试? | 无测试接口,需生产环境真实业务验证 |
|
||||
|
||||
**回调解密与验签**:
|
||||
|
||||
| # | 报错 | 原因 | 解法 |
|
||||
|---|------|------|------|
|
||||
| 1 | `cipher: message authentication failed` / `AEADBadTagException` | APIv3 密钥错误(最常见:密钥重置后代码未同步)或密文被截断 | 检查代码中的 APIv3 密钥与商户平台一致 |
|
||||
| 2 | "证书序列号不一致" | 用商户证书做了验签(应用平台证书)或平台证书过期 | 改用平台证书并确保未过期 |
|
||||
| 3 | `Last unit does not have enough valid bits` | 签名探测流量 | 检查 `Wechatpay-Signature` 是否以 `WECHATPAY/SIGNTEST/` 开头,是则返回非 2xx |
|
||||
| 4 | 签名参数顺序错误 | 参数个数 / 顺序 / 大小写不对或末尾缺 `\n` | 严格按文档顺序拼接,末尾必须有 `\n` |
|
||||
|
||||
**回调 IP 白名单**:
|
||||
|
||||
| 出口位置 | IP 网段 |
|
||||
|---------|---------|
|
||||
| 上海电信 / 联通 / CAP | `101.226.103.0/25` / `140.207.54.0/25` / `121.51.58.128/25` |
|
||||
| 深圳电信 / 联通 / CAP | `183.3.234.0/25` / `58.251.80.0/25` / `121.51.30.128/25` |
|
||||
| 香港 / 广州腾讯云 | `203.205.219.128/25` / `81.71.199.64`、`81.71.198.25`、`81.71.199.59` |
|
||||
|
||||
**委托代扣 签约/解约 通知出口 IP**:`101.226.233.128/25`
|
||||
|
||||
退款 / 分账通知 IP:`175.24.214.208`、`175.24.211.24`、`175.24.213.135`、`109.244.180.23`、`114.132.203.119`、`43.139.43.69`
|
||||
|
||||
### 2.3 签名与证书
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 证书序列号怎么获取? | 商户平台 → 账户中心 → API安全 → 商户API证书 → 管理证书 |
|
||||
| 2 | 一个商户号能设多个 API 证书吗? | 可以 |
|
||||
| 3 | 平台证书过期怎么换? | 参考 https://pay.weixin.qq.com/doc/v3/merchant/4012068829 ,建议代码实现自动轮换 |
|
||||
| 4 | 换 serial_no 后报签名错误? | 证书编号与私钥一一对应,更新 serial_no 时必须同步换私钥文件 |
|
||||
| 5 | V2 签名失败怎么排查? | 逐项对比签名原串:① 字段按 ASCII 字典序;② 大小写一致;③ 无多余空格或遗漏字段 |
|
||||
| 6 | V2 签名方式? | MD5 或 HMAC-SHA256(不是 V3 的 SHA256-RSA2048) |
|
||||
| 7 | API 只能通过域名访问吗? | 是,不支持 IP 直连 |
|
||||
| 8 | APIv2 密钥改后验签失败? | 密钥重置后代码中的密钥必须同步更新 |
|
||||
|
||||
### 2.4 退款常见问题
|
||||
|
||||
> 产品专属退款规则(不可退期限、最小金额等)见 2.6。
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 余额不足 | 商户账户可用余额不足,充值后重试 |
|
||||
| 2 | 重复退款 | `out_refund_no` 已使用,换一个或查询原单状态 |
|
||||
| 3 | 订单未支付 | 只有支付 / 扣款成功的订单才能退款 |
|
||||
| 4 | 支付和退款能跨 V2/V3 吗? | 可以,但不建议 |
|
||||
| 5 | 退款没到账 / 状态异常 | 先查退款状态:`PROCESSING`=处理中(1-3 工作日);`SUCCESS`=已成功;`ABNORMAL`=异常退款,走异常流程;`CLOSED`=退款关闭,用新单号重发。代码见对应模式接口索引 |
|
||||
|
||||
### 2.5 本角色特有问题
|
||||
|
||||
> 摘录自 [商户模式委托代扣常见问题](https://pay.weixin.qq.com/doc/v2/merchant/4011987907.md) + [社区委托代扣 API 常见问题官方精选 36 题](https://developers.weixin.qq.com/community/pay/doc/0004aaa01e8908b165985d15e5bc08?blockType=8),仅原文呈现,不发挥。
|
||||
|
||||
#### 2.5.1 公众号 / APP / H5 / 支付中签约典型报错
|
||||
|
||||
| 报错信息 | 原因 | 解决 |
|
||||
|---------|------|------|
|
||||
| 公众号纯签约 / "商家系统错误,请联系商家处理" | ① APIv2 密钥错;② `notify_url` 签名时未用 encode 前原值;③ 必填参数漏传 / 多传;④ `request_serial` 或 `timestamp` 不是 int 或长度 < 9 位;⑤ 商户号被处罚 | 1) 比对密钥(APIv2 不是 APIv3);2) encode 后转义符大写;3) 用[在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md)逐项核对 |
|
||||
| APP 纯签约 / "deeplink openWebview no permission" | APP 未在微信开放平台注册或未登记 bundleId;缺 `OpenBusinessWebview` 权限 | 微信开放平台完成 APP 注册并登记 bundleId;如缺权限联系运营申请。新模板请改用 `WXLaunchMiniProgram`(OpenBusinessWebview 已停止新申请) |
|
||||
| H5 纯签约 / "商家服务异常,请联系商家处理" | 未配置签约域名或域名不完整 | 联系运营协助配置签约域名,须配置发起签约页面的根域(含子路径时配置主域,如 `https://weixin.qq.com/wx/contract` 配 `weixin.qq.com`) |
|
||||
| 公众号 / APP / H5 签约 / "签名失败" | APIv2 密钥错;签名参数为原值(特别是 `notify_url` 用 encode 前的原值);H5 默认签名方式 `HMAC-SHA256` 误用 MD5 | 检查密钥;签名时使用 encode 前的原值;H5 / APP 默认 sign_type 见各接口文档 |
|
||||
| 公众号 / APP / H5 签约 / `plan_id` 错误 | `plan_id` 与 mch_id 未对应 | 比对模板 ID 是否在该 mch_id 下创建 |
|
||||
| 公众号纯签约 / encode 报错 | 转义符(如 `%2F` `%3A`)小写 | encode 后所有转义符必须**大写** |
|
||||
| 支付中签约 / 支付成功但签约失败 | 用户已签约 / 用户账号异常 / 用户未选择签约 / 用户签的扣费项目超过 200 条 | 联系用户排查;用户已签约时直接用现有协议调扣款 |
|
||||
| 支付中签约 / "签约模版信息不存在" | 模板 ID 错 | 商户平台 → 交易中心 → 高级业务 → 委托代扣模版管理查看正确的模板 ID |
|
||||
| 支付中签约 / "签约 contract_appid 与下单 appid 不匹配" | 两字段必须一致 | `contract_appid` 必须 = `appid`,`contract_mchid` 必须 = `mch_id` |
|
||||
| 支付中签约 / "暂无法开通 当前使用人数较多" | 首月费用扣不出来(用户余额不足) | 引导用户确保零钱余额充足或换支付方式 |
|
||||
| 支付中签约 / 支付成功签约失败的"自动续费按钮" | 按钮不可设为默认开通 | 不支持 |
|
||||
| APP 支付中签约 / 未实名账号无法签约支付成功 | 微信账号未实名认证 | 引导用户先完成实名认证 |
|
||||
| 公众号纯签约 / 用户点开通自动续费"无反应" | 偶现,常因微信版本旧或缓存问题 | 引导用户更新微信或清除缓存,杀进程后重新打开签约页 |
|
||||
| 公众号纯签约 / 长链接跳转报 502 | 链接超长 | 链接长度限制 **1024 字节以内**,缩短或改用 URL 短链 |
|
||||
| APP 纯签约 / 点击"完成"后没回到 APP,停留在微信聊天界面 | ① APP 在微信开放平台未开"跳转"权限;② 回调处理不当;③ 模板名称中含空格;④ `contract_display_account` 含空格 / 中英文符号 / 特殊字符 | 1) 开放平台检查跳转权限;2) 按 [Android 接入说明](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/Android.html) 处理回调(注意 `taskAffinity` 必须与主界面 task 一致);3) 申请新模板时去掉模板名称里的空格;4) 检查并清理 `contract_display_account` 中的特殊字符 |
|
||||
| APP 纯签约 / 点击"完成"按钮 Android 没反应 / iOS 提示"未安装应用" | ① 传入的 `appid` 与签约 APP 的 `appid` 不一致;② 该 `appid` 对应的 APP 未安装在手机;③ APP 未在手机注册 schema 地址 | 1) 检查 `appid` 是否一致;2) 确认 APP 已安装;3) 检查 APP 的 URL Schema 注册 |
|
||||
| H5 纯签约 / 完成后报 `launchApplication:fail_url need encode` | 商户原 URL 中嵌套了其他 URL;微信侧回跳前会做一层 decode,导致 decode 后是非法链接被拦截 | 推荐**剔除嵌套 URL**;如必须嵌套,对 `failUrl` / `redirectUrl` 做**两层 encode** 试试 |
|
||||
| H5 纯签约 / iOS 左上角返回回到微信,Android 左上角返回回到签约页面 | 平台差异:Android 只是关 WebView 露出浏览器;iOS 做不到只拉起 WebView | **正常表现**,无需处理;产品文案给到用户即可 |
|
||||
| H5 纯签约 / 完成后点击"完成"返回的是商户首页(域名)而不是发起签约的页面 | ① 浏览器 referrer 策略变更,源页面未授权 referrer;② 发起签约页面 URL 含 fragment(`#xxx`) | **方案 A(推荐)**:在源 H5 页面 `<head>` 中加 `<meta name="referrer" content="no-referrer-when-downgrade">`;**方案 B**:加 `<meta name="referrer" content="unsafe-url">`;**注意**:① 检查 HTML 中是否有多个 `meta name="referrer"` 声明(后者会覆盖前者);② 必须由商户前端发起的跳转才生效(不能通过中转/后台跳转);③ iOS 15+ 对 referrer-policy 更严格,referrer 只带 host 不带 path,**目前只能返回商户域名页**;④ URL 中 fragment(`#` 后部分)不参与服务端请求,回跳不会带上 |
|
||||
| APP 内嵌 H5 纯签约 / 无法唤起微信 | APP 拒绝了打开授权 | 检查拉起微信的 APP 是否拒绝了打开授权 |
|
||||
| APP 内嵌 H5 纯签约 / 完成后申请返回 APP 报 `launchApplication:fail` | 商户配置的 APP Schema 有误 | 联系 APP 开发人员检查操作系统是否注册了该 Schema、配置的 `appid` 是否正确 |
|
||||
| 已签约用户使用支付中签约 / 没有"已签约"提示 | 支付中签约**永远不显示**已签约提示,纯签约才会 | 纯签约场景接入前先调「查询签约关系」兜底;支付中签约则按 §2.5.1 "支付成功但签约失败"那行处理(用户已签约时直接用现有协议调扣款) |
|
||||
|
||||
#### 2.5.2 扣款 / 预扣费通知
|
||||
|
||||
| 报错 / 现象 | 原因 | 解决 |
|
||||
|---|---|---|
|
||||
| 申请扣款返回成功,扣款失败 | 申请受理成功 ≠ 扣款成功,最终结果以扣款回调为准 | 等扣款回调;回调中带 `err_code`(`NOTENOUGH` 余额不足 / `RULELIMIT` 风控等) |
|
||||
| 调用预扣费通知 / "当前签约的模板不是周期扣费类型,不能下发扣费前通知" | 模板不是预扣费通知模式 | 申请新模板时选择预扣费通知模式,或联系运营修改 |
|
||||
| 扣款失败但用户已签约成功 | 签约只代表协议建立,扣款仍可能因余额不足、银行卡限额、冻结、风控等失败 | 见上一行的回调 `err_code` 排查 |
|
||||
| 同一用户被发起多笔扣款单 | 同 mch_id + 同 `out_trade_no` 只扣一次;换 mch_id 不变 `out_trade_no` 会扣多次 | 严格一单一扣;重试**换新 `out_trade_no`** |
|
||||
| `trade_state=CLOSED`(扣款失败自动关单) | 用户零钱 + 银行卡等所有可扣方式均失败后自动关单 | 换新 `out_trade_no` 重新调申请扣款 |
|
||||
| 申请扣款 / `CONTRACT_NOT_EXIST` | 协议已解约 / `contract_id` 错 | 调[查询签约关系](https://pay.weixin.qq.com/doc/v2/merchant/4011987640.md)确认状态 |
|
||||
| 申请扣款 / `RULELIMIT` 风控 | 用户账号有违规 | 联系用户确认账号状态,必要时联系微信客服解除风控 |
|
||||
| 申请扣款 / `ORDER_ACCEPTED` | 受理中,请勿重复发起 | 调查询订单看实际状态 |
|
||||
| 通知后 24 小时扣费 / 申请扣款返回成功但订单未生成 | 用户首次签约(含解约后重签)超过 12 小时发起扣款,需等 24 小时后执行;调用时不立即生成订单 | 12 小时内发起立即执行;超过则等 24 小时后执行(属正常表现) |
|
||||
| 7:00 / 22:00 之间无法扣款 | 强制时段限制 | 调用前先校验北京时间在 7:00-22:00 内,超出时段重新调度 |
|
||||
| 解约后能否换签约方式重签 | 可以;解约后可任选纯签约或支付中签约方式,**但签约单号不能重复** | 换新 `contract_code` 重新签约 |
|
||||
| 预扣费通知模式 / 没发预扣费通知就调申请扣款 | 接口返回 `INVALID_REQUEST` —— "需要先下发扣费前通知才能发起扣费" | 必须**先**调「预扣费通知」,扣费等待期结束后再调「申请扣款」;当前签约扣费期已结束的需重新发起扣费前通知 |
|
||||
| 预扣费通知 / `RESOURCE_ALREADY_EXISTS` vs `INVALID_REQUEST` 错误码区别 | 两者都意味着"已发送过通知",但语义不同:<br>• `RESOURCE_ALREADY_EXISTS`:**相同参数**重复发送,**视为成功**(可继续后续扣款)<br>• `INVALID_REQUEST`:"已发送过扣费前通知,需要等本次扣费完成后再发起新的"——**参数不一样的重复发送**,**视为失败** | 收到 `RESOURCE_ALREADY_EXISTS` 当作发送成功处理;收到 `INVALID_REQUEST` 不能继续发,等本次扣费完成或周期结束 |
|
||||
| 申请扣款 / "扣款请求已受理,请勿重复发起" | 自动续费规则:一个协议 ID **在等待期内(24 小时内)只能有一笔扣款** | 等当前扣款单结束(成功或自动关单)后再发起;不要换号重提,先调「查询订单」看实际状态 |
|
||||
| 申请扣款 → 关单 → 用代扣查单接口查不到 / 报"订单不是委托代扣场景" | 关单后委托代扣查单接口(`/pay/paporderquery`)查不到该单 | 改用**基础支付的查询订单接口**(`/pay/orderquery`)确认订单状态 |
|
||||
|
||||
#### 2.5.3 协议关系
|
||||
|
||||
| 报错 / 问题 | 原因 | 解决 |
|
||||
|---|---|---|
|
||||
| 同一用户在同一商户号同模板 ID 下能否签多次? | 不能,仅一个生效签约 | 多次签约需用多个 `plan_id`;用户解约后可重签 |
|
||||
| 用户已签约,仍能拉起签约页 | 微信会拒绝重复签约,签约页提示无法签约 | 接入前先调查询签约关系兜底 |
|
||||
| 用户主动解约后,是否需商户同意? | 不需要 | 解约后通过解约结果通知告知商户,商户更新本地协议状态 |
|
||||
| 模板申请页"解约结果通知 URL"是不是给用户的提醒 URL? | 不是 | 是给商户后台的解约异步通知 URL;不是给用户的消息 |
|
||||
| 同一种业务,APP 纯签约和 H5 纯签约 `plan_id` 能共用吗? | 可以 | — |
|
||||
| 续费协议能否提前续费? | 必须在协议**最后一期(到期当月)**发起,不能在中间月份 | — |
|
||||
| `plan_id` 是否需要适配鸿蒙? | 是的,仅针对 APP 纯签约场景 | 改用 `WXLaunchMiniProgram` |
|
||||
| 委托代扣模板 ID 停用后的影响? | 已签约用户**可以继续扣费或解约**,但**不能再新增签约**;停用后**无法恢复使用** | 停用前商户自行评估风险;新签约入口需切换到其他模板 |
|
||||
| 解约回调地址(解约结果通知 URL)修改后多久生效? | **实时生效** | 模板配置修改即刻生效,无需重发 |
|
||||
| 商户后台发起的解约,回调里 `contract_termination_mode=3`(商户 API 解约),正常吗? | **正常**,商户后台目前底层就是调用商户 API 解约 | 业务侧不必区分"后台解约"与"API 解约" |
|
||||
| APP 纯签约是否支持多账号签约? | **支持**,预签约接口多传 `outerid` 字段,**值格式必须按示例**:`李*艳(00000000000)`,否则返回 `PARAMERROR:outerid` | 配合 `contract_outerid` 使用;多账号签约下,模板扣款次数限制按签约用户**分开计算**(每个签约独立计) |
|
||||
| 多账号签约 `contract_outerid` / `outerid` 长度限制 | `contract_outerid` **32 位字符**;`outerid` **32 位字符** | — |
|
||||
| 同一 `contract_code` 已签约 → 解约后 → 还能用相同 `contract_code` 再签约吗? | **目前可以** | 业务侧仍建议换新 `contract_code` 避免与历史数据混淆 |
|
||||
| 支付中签约生成的订单 / 用哪个查单 + 支付回调通知文档? | 用**基础支付的查询订单接口** + 基础支付的支付结果回调通知文档(不是代扣的查单/回调) | 详见 [基础支付查询订单](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2) / [基础支付结果回调通知](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8) |
|
||||
|
||||
### 2.6 业务规则 Q&A
|
||||
|
||||
> 来源:[商户模式委托代扣常见问题](https://pay.weixin.qq.com/doc/v2/merchant/4011987907.md),仅原文摘录。
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 周期扣费首期金额可以与后续不一致吗? | 可以,每期金额在模板限额内即可 |
|
||||
| 2 | 自动续费 vs 测试模板的限额限频 | 自动续费按模板配置;测试模板单笔 0.1 元 / 单日 100 次;同一签约协议每日成功扣 1 次(失败不计),单周期成功扣 2 次 |
|
||||
| 3 | 一个商户号最多可申请多少模板? | 没有限制 |
|
||||
| 4 | 委托代扣支持小程序接入吗? | 支持 |
|
||||
| 5 | H5 纯签约能跳指定小程序页面吗? | 不能,只能原路返回 |
|
||||
| 6 | 周期扣款失败会通知用户吗? | 不会 |
|
||||
| 7 | 委托代扣有沙箱吗? | 没有 |
|
||||
| 8 | 周期为 1 月、扣费日 30,2 月怎么办? | 商户自行控制时机,2 月可提前调申请扣款 |
|
||||
| 9 | 12 期代扣中第 3 期由用户线下缴费,第 4 期能继续扣吗? | 可以 |
|
||||
| 10 | 委托代扣有限额吗? | 有,按模板申请;具体咨询运营 |
|
||||
| 11 | 微信钱包余额不足、卡有余额,会扣失败吗? | 银行卡限额、冻结、风控等会失败 |
|
||||
| 12 | 扣款 / 预扣费通知的可调用时段? | 北京时间每天 7:00-22:00;签约时间不影响 |
|
||||
| 13 | 扣款失败回调后如何重试? | 预扣费通知模式:可扣周期内换单重试,周期结束需重新调预扣费通知;24h 模式:换新 `out_trade_no` |
|
||||
| 14 | 预签约接口 2 小时内能原单重试吗? | 支持 |
|
||||
| 15 | 委托代扣有 IP 白名单吗? | 没有 |
|
||||
| 16 | 委托代扣能否指定签约结束跳转链接? | 不支持指定 |
|
||||
| 17 | H5 签约成功,浏览器返回地址会带 `return_appid` 吗? | 不会 |
|
||||
| 18 | 测试模版能否改名? | 不支持 |
|
||||
| 19 | 委托代扣 `notify_url` 签名时用编码前还是编码后? | **编码前的原值**(未 URL Encode 的) |
|
||||
| 20 | 用户解约需要商户同意吗? | 不需要 |
|
||||
| 21 | 委托代扣是否需要开通基础支付产品权限? | 纯签约不需要;支付中签约需要 |
|
||||
| 22 | 委托代扣支持首期优惠吗? | 支持,申请模板时设"优惠价格 < 标准价格"即可 |
|
||||
| 23 | 首月优惠由谁判断? | 微信侧自动判断,签约页直接展示 |
|
||||
| 24 | 24 小时扣费模式下申请扣款成功但未生成订单? | 用户首次签约(含解约后重签)超 12 小时发起扣款,需等 24 小时后执行;调用时不立即生成订单 |
|
||||
| 25 | 同一用户用不同 appid 签约,会生成 1 个还是 2 个协议? | 1 个,协议维度为 mch_id+plan_id |
|
||||
| 26 | 公众号 A 注销并迁至 B,能用 B 的 appid 扣款吗? | 可以,appid 与 mch_id 有绑定关系即可 |
|
||||
| 27 | "进入商家小程序"按钮怎么配? | 联系运营协助配置 |
|
||||
| 28 | OpenBusinessWebview / WXLaunchMiniProgram 数据互通吗? | 互通 |
|
||||
| 29 | 切换 WXLaunchMiniProgram 后,存量 OpenBusinessWebview 用户是否受影响? | 不影响,存量签约关系仍有效 |
|
||||
| 30 | 实际扣款金额由模板还是申请扣款的金额决定? | 以申请扣款 `total_fee` 为准;模板扣费类型"为"=固定金额必须按模板传,"不高于"=非固定可在限额内自定义 |
|
||||
| 31 | 公众号纯签约功能对域名有限制吗? | 没有限制 |
|
||||
| 32 | 委托代扣支持纯血鸿蒙吗? | 仅 `WXLaunchMiniProgram` 调起方式支持 |
|
||||
| 33 | 24h 模式 / 预扣费通知模式如何选? | 申请模板时选择;切换需联系运营修改 |
|
||||
| 34 | "通知后 24h 扣费"模式如何指定? | 申请模板时选择"延迟 24 小时扣费" |
|
||||
| 35 | H5 纯签约 / "请使用 GET 请求" | 请求方式不对,必须 GET |
|
||||
| 36 | 申请扣款的"周期 / 限额 / 频次"详细规则? | ① 自动续费周期一般以**月 / 季度**为周期,具体扣款发起时间不受模板内容周期影响;② 委托代扣**初始额度**:单笔 500、单日 2500;③ **授权扣款 / 免密支付**:额度限制内**每天可扣款 5 次**(同用户 / 同签约协议下;扣款失败不计次数);④ **自动续费模板**:需按模板设定周期扣款,**同用户 / 同签约协议下每天仅可成功扣款 1 次**;⑤ 测试模板:单笔 0.1 元,每天可扣 100 次 |
|
||||
| 37 | **单用户在单模板下"申请扣款接口的尝试调用频率"是多少?** | **默认每天 ≤ 300 次**(包含成功 + 失败的返回);succeed 才表示扣款受理成功,等待扣款中 |
|
||||
| 38 | 同一个微信号在同模板下"当天扣款次数"上限? | **当天最多 150 次**(含商户重试申请扣款失败的次数和成功的次数) |
|
||||
| 39 | 多账号签约时,模板的扣款次数限制按什么计算? | **按每个签约独立计算**(不同签约用户的次数互不影响) |
|
||||
| 40 | 支付中签约是否支持传入分账标识实现订单分账? | **暂不支持**;支付中签约下单接口不会识别商户分账标识 |
|
||||
|
||||
### 2.7 通用接入配置
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | V2 和 V3 可以同时用吗? | 可以,密钥体系独立互不影响 |
|
||||
| 2 | 微信支付有测试环境吗? | **没有**,所有调试需在生产环境进行 |
|
||||
| 3 | 同一错误为什么返回不同错误码? | 存在参数校验优先级,多参数错误时可能先返回 `PARAM_ERROR` |
|
||||
| 4 | 接口地址能在浏览器直接打开吗? | **不能**,需程序调用并携带证书,建议用 Postman 调试 |
|
||||
| 5 | 防火墙拦截(如医院场景)怎么办? | 微信服务端 IP 动态更新,建议以**域名白名单**配置防火墙 |
|
||||
|
||||
---
|
||||
|
||||
## 排障信息收集清单
|
||||
|
||||
两条路径都未命中时,请用户提供:接入模式、出错环节(签约 / 申请扣款 / 预扣费通知 / 解约 / 查询签约关系 / 退款 / 回调 / 对账)、HTTP 状态码 + 完整响应体、Request-Id(含尾段错误码)、商户号 mchid + 业务单号(`contract_code` / `contract_id` / `out_trade_no` / `out_refund_no`)+ 请求时间。
|
||||
@@ -0,0 +1,129 @@
|
||||
# 服务商模式产品介绍
|
||||
|
||||
> 来源:微信支付 V2 文档中心 · 委托代扣([产品介绍](https://pay.weixin.qq.com/doc/v2/partner/4011988358.md) / [接入流程](https://pay.weixin.qq.com/doc/v2/partner/4011988361.md) / [周期扣费](https://pay.weixin.qq.com/doc/v2/partner/4011988360.md))。本页面只讲产品定位、业务模式与签约方式选型,**不含**密钥、回调 URL、证书等接入细节,那些请见 [📄 服务商模式开发参数与业务规则](../接入指南/开发参数与业务规则.md)。
|
||||
|
||||
## 一、产品概览
|
||||
|
||||
委托代扣是微信支付为商户和用户提供的、**可以在交易场景之外完成支付**的能力。用户在签约阶段一次性完成"授权扣款"协议的签订,后续商户即可按约定规则**无需用户每次输入密码**地从用户微信账户发起扣款。
|
||||
|
||||
适用对象:
|
||||
|
||||
- **微信支付服务商**(本文档),代理子商户接入委托代扣
|
||||
- 直连商户请见 [📄 商户模式产品介绍](../../1-商户/产品选型/产品介绍.md)
|
||||
|
||||
### 服务商模式与商户模式的核心差异
|
||||
|
||||
| 差异项 | 服务商模式 | 商户模式 |
|
||||
| --- | --- | --- |
|
||||
| 必传请求参数 | 必传 `mch_id`(服务商号)+ `sub_mch_id`(子商户号);`sub_appid` 可选 | 仅传 `appid` + `mch_id` |
|
||||
| API 路径 | 多数接口路径多 `/partner/` 段(如 `/papay/partner/entrustweb`、`/pay/partner/pappayapply`) | 路径不带 `/partner/` |
|
||||
| APIv2 密钥 / API 证书 | 使用**服务商**的密钥与证书(不是子商户的) | 使用商户自己的密钥与证书 |
|
||||
| 签约页 logo | 仅展示**服务商** appid 对应的 logo,不能切换为子商户 logo | 展示商户 appid 的 logo |
|
||||
| 子商户授权 | 子商户需在商户平台「我的授权产品」中"授权"该服务商委托代扣 | — |
|
||||
| 不支持的接口 | **无「支付中签约」**(`pay/contractorder` 仅商户模式) | 支持「支付中签约」 |
|
||||
| 子商户开通查询 | 服务商可在合作伙伴平台「特约商户授权产品 → 委托代扣 → 特约商户列表」查看 | — |
|
||||
| 签约/扣款回调归属 | 服务商维度,所有子商户共用同一回调地址,必须按 `sub_mch_id` 路由到正确子商户 | 单一商户维度 |
|
||||
|
||||
> 委托代扣的"扣款入口"分两类:**纯签约**(先签约,再发起独立的「申请扣款」接口)。**注意:服务商模式不支持「支付中签约」**——如有"支付 + 签约"一次完成的需求,子商户必须自行接入直连模式。
|
||||
|
||||
## 二、业务模式选型(先决定走哪种)
|
||||
|
||||
委托代扣**仅针对以下两类业务场景开放**:
|
||||
|
||||
| 业务模式 | 场景特点 | 解决问题 | 方案示例 | 扣款延迟 | 扣前通知 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **周期扣款(自动续费)** | 基于规则而非用户行为触发的、可周期性或多次扣款模式;主要是**预付费**场景,特殊民生类除外(如水电煤气缴费) | 减少用户重复的支付操作,保证服务的如期续约 | 会员续费 | 有延迟 | **必须**下发,二选一:通知后 24 小时自动扣费 / 预扣费通知 |
|
||||
| **先享后付(免密支付)** | 基于用户行为触发,且是商户先提供服务,服务完结后再扣款的**后付费**模式 | 解决用户在场景内不方便支付的问题 | 乘车码、停车后扣费 | **无延迟**,立即扣款 | 不下发 |
|
||||
|
||||
### 周期扣款的两种通知方式
|
||||
|
||||
只允许选其中一种(**两种模式只能二选一**),申请模板时确定:
|
||||
|
||||
| 通知方式 | 特点 | 商户接入工作 | 注意事项 |
|
||||
| --- | --- | --- | --- |
|
||||
| **通知后 24 小时扣费**(默认) | 服务商调用「申请扣款」后微信自动下发通知,24 小时后自动完成扣费 | 服务商**只需**调用「申请扣款」 | 受理后订单 24 小时内持续"处理中",**勿重复扣款**避免触发频率限制 |
|
||||
| **预扣费通知模式** | 使用单独的「预扣费通知」V3 接口下发消息,扣费等待期 + 可扣费期分离 | 必须**先调【预扣费通知 API】**再调「申请扣款」 | 通知日 + 第二日为扣费等待期,第 3-9 天为可扣费期,仅 7:00-22:00 可发起扣费 |
|
||||
|
||||
> 申请委托代扣模板时一般为「24 小时自动扣费」模式,如需「预扣费通知」模式,需联系运营协助申请修改。
|
||||
|
||||
### 首次签约扣款的特殊规则
|
||||
|
||||
如果用户为**首次签约**(含解约后重新签约),从用户签约成功时间开始算:
|
||||
|
||||
- **12 小时内**发起扣款 → **立即执行,无延迟**(两种通知模式都适用)
|
||||
- 超过 12 小时 → 按通知模式各自规则执行(24h / 预扣费通知)
|
||||
|
||||
## 三、签约方式选型
|
||||
|
||||
服务商模式有 **4 种签约方式**,按用户终端划分(**比商户模式少一个「支付中签约」**——支付中签约仅商户模式支持)。
|
||||
|
||||
> **术语速查**(下表"接入流程"列里的英文 API 都在这里):
|
||||
>
|
||||
> - **WXLaunchMiniProgram**:微信开放平台提供的"从原生 APP 调起微信小程序"API。委托代扣用它跳到"微信支付签约小程序"完成签约,是 APP 端签约**当前唯一**的官方推荐方案。
|
||||
> - **wx.navigateToMiniProgram**:微信小程序框架内置 API,作用是从一个小程序跳到另一个小程序。本场景下用于从子商户业务小程序跳到微信支付签约小程序。
|
||||
> - **`redirect_url`**:H5 预签约接口返回的一次性签约页地址,前端浏览器跳到该 URL 即可唤起签约。
|
||||
>
|
||||
> 服务商模式所有 V2 接口路径都带 `/partner/`,且签约 / 扣款使用的是**服务商**的 APIv2 密钥(不是子商户的)。
|
||||
|
||||
> "业务流程"列以**用户感知**为主线讲清"用户在哪里点 → 跳到哪 → 看到什么 → 微信怎么回应 → 签完干嘛";技术细节(具体 API 路径、SDK 名、参数有效期等)放到"备注"列,不抢主线。
|
||||
|
||||
| 签约方式 | 用户场景 | 业务流程(用户/子商户/服务商/微信 四方协作) | 是否需邮件单独申请 | 备注(接口与实现要点) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **公众号纯签约** | 微信内 H5(公众号网页) | ① 用户在子商户公众号网页点击"开通免密支付";② 浏览器跳转到**微信内置的签约页**;③ 用户在签约页确认开通,微信展示"开通成功";④ 服务商后端收到"签约结果通知",**按 `sub_mch_id` 路由**到对应子商户业务并落库;⑤ 后续按子商户身份调「申请扣款」无感扣费。 | 否 | 接口:`papay/partner/entrustweb`(GET 拼好签名后直接跳转),用**服务商** APIv2 密钥签名;在微信内 H5 唤起,**不涉及浏览器域名白名单** |
|
||||
| **APP 纯签约** | 子商户原生 APP(Android / iOS / **鸿蒙**) | ① 用户在子商户 APP 内点击"开通免密支付";② APP 调起"微信支付签约小程序",用户进入微信完成授权;③ 关闭签约小程序自动回到子商户 APP;④ 服务商后端收到"签约结果通知",**按 `sub_mch_id` 路由**到对应子商户;⑤ 后续按子商户身份调「申请扣款」无感扣费。 | **是**,需单独申请 APP 签约权限 | 接口:服务端 `papay/partner/preentrustweb` 取 `pre_entrustweb_id`,APP 端用 `WXLaunchMiniProgram` 调起签约小程序。**唯一支持鸿蒙**的签约方式 |
|
||||
| **小程序纯签约** | 微信小程序内 | ① 用户在子商户业务小程序内点击"开通免密支付";② 跳转到"微信支付签约小程序",用户在其中确认授权;③ 自动跳回子商户小程序,`onShow` 拿到本次返回结果;④ 服务商后端收到"签约结果通知"(**前端结果仅供 UI 提示,最终状态以异步通知为准**),**按 `sub_mch_id` 路由**;⑤ 后续按子商户身份调「申请扣款」无感扣费。 | 否 | 接口:服务端 `papay/partner/preentrustweb`,客户端 `wx.navigateToMiniProgram`(需在 `app.json` 的 `navigateToMiniProgramAppIdList` 中加入签约小程序 appid `wxbd687630cd02ce1d`) |
|
||||
| **H5 纯签约** | 浏览器 H5(含外投广告 APP) | ① 用户在子商户浏览器 H5 页面点击"开通免密支付";② 服务商后端用一次"预签约"取到本次签约的一次性入口地址;③ 浏览器跳到该地址,用户在页面内扫码 / 唤起微信完成授权;④ 服务商后端收到"签约结果通知",**按 `sub_mch_id` 路由**;⑤ 后续按子商户身份调「申请扣款」无感扣费。 | **是**,需单独申请 H5 签约权限并**配置签约域名白名单** | 接口:服务端 `papay/partner/h5entrustweb`(建议附带 `deviceid` / `mobile` / `email` / `qq` / `openid` / `creid` 等风控参数提高通过率)取 `redirect_url`,浏览器 `location.href = redirect_url` 跳转。`redirect_url` 有效期 10 分钟、且**仅可访问一次** |
|
||||
|
||||
> **存量 `OpenBusinessWebview` 兼容说明**:APP 端签约还有一个旧 SDK `OpenBusinessWebview`,自 2025-09-22 起官方已停止新申请。**仅在该日期前已申请过该权限的存量模板**仍可继续运行,但官方建议尽快迁移到 `WXLaunchMiniProgram`。新接入或新模板**无需了解此 SDK**,直接走上表的"APP 纯签约"即可。
|
||||
|
||||
### 选型决策树
|
||||
|
||||
1. **用户接入终端是什么?**
|
||||
- 微信公众号 H5 → **公众号纯签约**
|
||||
- 微信小程序 → **小程序纯签约**
|
||||
- 浏览器 H5 → **H5 纯签约**(需单独申请权限 + 配置签约域名)
|
||||
- 原生 APP(Android / iOS / 鸿蒙)→ **APP 纯签约**(用 `WXLaunchMiniProgram` 调起签约小程序;存量 `OpenBusinessWebview` 模板可继续运行,但建议迁移)
|
||||
|
||||
> **服务商模式没有「支付中签约」**:子商户如果需要"支付 + 签约一次完成",必须自行接入直连模式调用 `pay/contractorder`(仅商户模式支持)。
|
||||
|
||||
## 四、接入前置条件 / 准入 checklist
|
||||
|
||||
按下表全部完成后再进入开发环节(参数获取与具体取值规范详见 [📄 服务商模式开发参数与业务规则](../接入指南/开发参数与业务规则.md)):
|
||||
|
||||
| # | 准入项 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 服务商已开通「服务商委托代扣」产品权限 | 详见 [📄 V2 接入流程(服务商)· 申请委托代扣权限](https://pay.weixin.qq.com/doc/v2/partner/4011988361) |
|
||||
| 2 | 服务商已申请并通过审核「委托代扣模板 ID」 | 在合作伙伴平台「交易中心 → 高级业务 → 委托代扣模版管理」提交申请,**审核通过后**模板 ID 才能用于扣款接口开发。申请页关键字段:业务类型(免密支付 / 自动续费;授权扣款暂未开放)、模板名称(展示在签约页"开通账号"处)、扣费类型("为" = 每次扣固定金额;"不高于" = 每次扣不超过设定上限)、首期优惠价格(可选)、解约结果通知 URL |
|
||||
| 3 | 子商户已"授权"该服务商的委托代扣 | 服务商可在合作伙伴平台「特约商户授权产品 → 服务商委托代扣 → 特约商户列表 → 邀请子商户授权」邀请;子商户在商户平台站内信或「产品中心 → 我的授权产品」点击"授权" |
|
||||
| 4 | 服务商 appid 与服务商 mch_id 已建立绑定关系;如传 `sub_appid` 也需子商户号绑定 | 在合作伙伴平台「账户中心 → 商户信息 → 关联管理」中配置 |
|
||||
| 5 | 选用 H5 / APP 纯签约 → 已申请对应权限 | H5 纯签约需配置签约域名;**APP 纯签约** 需申请并由运营配置(用 `WXLaunchMiniProgram` 调起签约小程序) |
|
||||
| 6 | (周期扣款且选预扣费通知模式)已申请预扣费通知权限 | 默认申请下来的模板是 24 小时自动扣费模式,需运营协助修改为预扣费通知模式 |
|
||||
| 7 | 已配置好回调地址(HTTPS、公网可达、5 秒内应答) | 签约通知 URL 在签约接口的 `notify_url` 传入;扣款通知在「申请扣款」`notify_url` 传入;解约通知 URL 在模板配置中预设 |
|
||||
|
||||
> **限额限频**(by 签约协议):自动续费模板单笔限额按模板配置;测试模板单笔限额 0.1 元、单日限频 100 次。同一用户在同一签约协议下**每天仅可成功扣款 1 次**(失败不计),单周期最多 2 次。
|
||||
>
|
||||
> **可扣费时段**:申请扣款 / 预扣费通知接口仅在北京时间 **7:00-22:00** 可调用;签约时间不影响。
|
||||
>
|
||||
> **沙箱**:委托代扣**没有沙箱环境**,所有调试在生产环境完成(建议先用测试模板 + 0.1 元小金额跑通主流程)。
|
||||
|
||||
## 五、与同类产品的关系
|
||||
|
||||
| 对比项 | 委托代扣(V2,本产品) | V3 微信支付分(先享后付) | V3 自动续费 |
|
||||
| --- | --- | --- | --- |
|
||||
| 业务定位 | 通用授权扣款 | 信用分驱动的"先享后付"专项 | 各类支付产品的"扣款单"形式(V3 接口) |
|
||||
| 协议版本 | V2(XML + MD5/HMAC-SHA256),仅「预扣费通知」走 V3 | V3(JSON + RSA) | V3(JSON + RSA) |
|
||||
| 服务商支持 | 支持 | 支持 | 支持 |
|
||||
| 是否需要本产品 | 通用场景的事实标准 | 走支付分体系,单独 skill | 走 V3 自动续费,单独 skill |
|
||||
|
||||
如果业务方期望**新接入**且场景允许,建议优先评估 V3 自动续费 / 支付分;只有明确选定走 V2 委托代扣时再使用本 skill。
|
||||
|
||||
## 六、状态流转 / 协议生命周期
|
||||
|
||||
- **签约协议状态**(查询签约关系返回 `contract_state`):`0` 已签约、`1` 未签约、`9` 签约进行中。
|
||||
- **解约方式**(`contract_termination_mode`):`1` 有效期过自动解约(预留功能)、`2` 用户主动解约、`3` 商户 API 解约、`4` 商户平台解约、`5` 注销(用户微信账户注销)、`7` 用户联系客服发起的解约。
|
||||
- **扣款订单状态**(查询订单返回 `trade_state`):`SUCCESS` 支付成功、`REFUND` 转入退款、`NOTPAY` 未支付、`CLOSED` 已关闭、`USERPAYING` 用户支付中、`PAYERROR` 支付失败、`ACCEPT` 已接收等待扣款。
|
||||
- **协议唯一性**:协议维度为「商户号 + 模板 ID」(服务商模式下也是按 mch_id+plan_id 维度),**同一用户在同一服务商号 + 同一模板 ID 下只能有一个生效签约**;与子商户号无关。
|
||||
- **扣款失败自动关单**:用户零钱 / 银行卡等所有可扣方式均失败后,扣款订单进入 `CLOSED`。如需再次扣费,必须**换商户订单号**重新调用「申请扣款」(同一 `out_trade_no` 不会扣两次)。
|
||||
- **回调路由**:服务商所有子商户共用一套回调地址;签约/解约/扣款回调中携带 `sub_mch_id`(如有 `sub_appid` 一并返回 `sub_openid`),**必须按 `sub_mch_id` 路由到对应子商户业务**,否则会出现"A 子商户的签约 / 扣款被 B 子商户业务处理"的串单事故。
|
||||
|
||||
更详细的字段定义和接口报文见 [📄 服务商模式接口索引](../示例代码/接口索引.md)。
|
||||
@@ -0,0 +1,228 @@
|
||||
# 服务商模式回调处理
|
||||
|
||||
> 本文档为微信支付**委托代扣(API V2)回调处理规范**——委托代扣主体走 V2(XML + MD5/HMAC-SHA256),**与 V3 通用回调(JSON + AEAD_AES_256_GCM)完全不同**,请勿混用。委托代扣涉及的回调全部走本文档;只有「预扣费通知」是 V3 同步接口(不是回调),不在本文档范围内。
|
||||
>
|
||||
> 来源:微信支付 V2 文档中心 ·「[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/partner/4011984734.md)」 +「[扣款结果通知](https://pay.weixin.qq.com/doc/v2/partner/4011988376.md)」 +「[签约-解约结果通知](https://pay.weixin.qq.com/doc/v2/partner/4011988378.md)」 + V2 通用退款回调规范(委托代扣退款共用 [付款码退款结果通知](https://pay.weixin.qq.com/doc/v2/merchant/4011940955.md) 同款规范)。
|
||||
|
||||
## 一、委托代扣的三类回调
|
||||
|
||||
| # | 回调种类 | 配置位置 | 协议格式 | 是否加密 | 验签算法 | 触发频率 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | **签约 / 解约结果通知**(同一种通知,由 `change_type=ADD/DELETE` 区分) | 签约通知地址 = 各签约接口 `notify_url` 字段;解约通知地址 = **委托代扣模板配置**中预设 | V2 XML,**不加密** | MD5 / HMAC-SHA256(按签约时签名方式) | 0/10/10/10/30/30/30/300×... 单位秒 |
|
||||
| 2 | **扣款结果通知**(成功 / 失败两种报文,字段不同) | 「申请扣款」接口 `notify_url` 字段 | V2 XML,**不加密** | MD5 / HMAC-SHA256 | 0/15/15/30/180/1800×4/3600 单位秒 |
|
||||
| 3 | **退款结果通知** | 「申请退款」接口 `notify_url`;不传则用合作伙伴平台预设 | V2 XML,**`req_info` 字段 AES-256-ECB 加密**(仅退款回调) | 退款回调**整体不参与签名校验**,靠解密 + 业务字段比对作为可信度判断 | 15s/15s/30s/3m/10m/20m/30m×3/60m/3h×3/6h×2,总计约 24h4m |
|
||||
|
||||
> ‼️ 三类回调可以**复用同一 `notify_url`**,但业务侧必须按报文中的字段路由分发:
|
||||
> - **`change_type` 字段存在** → 签约/解约结果通知(按 `change_type=ADD/DELETE` 进一步分发)
|
||||
> - **`req_info` 字段存在** → 退款结果通知
|
||||
> - **`transaction_id` + `contract_id` 存在但无 `change_type`** → 扣款结果通知
|
||||
|
||||
> ‼️ **服务商专属**:所有子商户**共用同一回调地址**——回调中携带 `mch_id`(服务商号)+ **`sub_mch_id`(子商户号)**(如有 `sub_appid` 一并返回 `sub_openid`)。业务侧**必须按 `sub_mch_id` 路由分发到对应子商户的业务流程**,否则会出现"A 子商户的扣款 / 签约被 B 子商户业务处理"的串单事故。
|
||||
|
||||
## 二、`notify_url` 配置规范
|
||||
|
||||
> 来源:[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/partner/4011984734.md)
|
||||
|
||||
| 必须满足 | 说明 |
|
||||
| --- | --- |
|
||||
| 必须是商户自己系统的真实地址 | 不能填接口文档/Demo 上的示例地址 |
|
||||
| 必须 `https://` 或 `http://` 开头的**完整全路径** | 不能只写域名(如 `http://www.weixin.qq.com` 错),必须带具体路径(如 `https://www.partner.com/wxpay/notify`) |
|
||||
| **不能携带 query 参数** | URL 中带 `?xxx=yyy` 会被拒 |
|
||||
| **不能填本地或内网 IP** | `127.0.0.1` / `192.168.x.x` / `localhost` 一律不可用 |
|
||||
| 必须公网可访问、域名已 ICP 备案 | 国内服务器必须;如走专线接入可用专线 NAT IP 或私有回调域名(http) |
|
||||
|
||||
### 委托代扣专属:四种 notify_url 的来源
|
||||
|
||||
| URL | 来源 | 备注 |
|
||||
| --- | --- | --- |
|
||||
| 签约通知 URL | 服务商签约接口(公众号 `/papay/partner/entrustweb` / APP 预签约 `/papay/partner/preentrustweb` / 小程序 / H5 `/papay/partner/h5entrustweb`)`notify_url` 字段 | 各签约请求独立配置;签约失败不通知;**所有子商户共用 1 个 URL**,业务侧按 `sub_mch_id` 路由 |
|
||||
| 解约通知 URL | 委托代扣模板申请页中的"解约结果通知 URL"字段 | 模板维度;可在合作伙伴平台「交易中心 → 高级业务 → 委托代扣模版管理」修改 |
|
||||
| 扣款通知 URL | 服务商「申请扣款」接口(`/pay/partner/pappayapply`)`notify_url` 字段 | 每次申请扣款独立配置 |
|
||||
| 退款通知 URL | 「申请退款」接口(`/secapi/pay/refund`)`notify_url` 字段 | 不传则使用合作伙伴平台预设 |
|
||||
|
||||
> ‼️ 「解约结果通知 URL」**不是**给用户的提醒地址(不会向用户发"您已解约"消息),是给商户后端的异步通知地址。
|
||||
|
||||
## 三、回调通知报文示例(按种类)
|
||||
|
||||
### 3.1 签约 / 解约结果通知(不加密)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id>10010404</mch_id>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<contract_code>100001256</contract_code>
|
||||
<openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6ua]]></openid>
|
||||
<plan_id><![CDATA[123]]></plan_id>
|
||||
<change_type><![CDATA[ADD]]></change_type>
|
||||
<operate_time><![CDATA[2015-07-01 10:00:00]]></operate_time>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
关键字段(**服务商专属差异**已粗体):
|
||||
|
||||
- `mch_id` 是**服务商号**,**`sub_mch_id`** 是子商户号——必须用 `sub_mch_id` 路由到对应子商户业务
|
||||
- 如签约时传过 `sub_appid`,回调里还会有 `sub_openid`(子商户 appid 下的用户唯一标识)
|
||||
- `change_type=ADD` → 签约成功;`change_type=DELETE` → 解约成功
|
||||
- `openid` 是签约用户在**服务商 `appid`** 下的唯一标识——**商户记录的签约用户 openid / sub_openid 必须以这里为准**,**禁止用业务侧自记录的 openid**(避免扣款用户和签约用户对不上)
|
||||
- `contract_id` 是签约成功后微信返回的协议 ID,后续「申请扣款」必传
|
||||
- 解约时多 `contract_termination_mode`:`1` 有效期过自动解约(预留)/`2` 用户主动解约 /`3` 商户 API 解约 /`4` 商户平台解约 /`5` 用户微信账户注销 /`7` 用户联系客服解约
|
||||
|
||||
### 3.2 扣款结果通知(成功 / 失败 / 不加密)
|
||||
|
||||
**扣款成功**(`return_code=SUCCESS` + `result_code=SUCCESS`)核心字段:`appid` / `mch_id`(服务商号)/ **`sub_mch_id`** / `out_trade_no` / `transaction_id` / `total_fee` / `cash_fee` / `bank_type` / `openid`(如有 `sub_appid` 一并返回 `sub_openid`)/ `time_end` / `trade_state=SUCCESS` / `contract_id`,外加签名 `sign`。
|
||||
|
||||
**扣款失败**(`return_code=SUCCESS` + `result_code=FAIL`)核心字段:`appid` / `mch_id`(服务商号)/ **`sub_mch_id`** / `out_trade_no` / `contract_id` / `err_code`(如 `NOTENOUGH` / `RULELIMIT` / `BANKERROR` / `USER_ACCOUNT_ABNORMAL` 等)/ `err_code_des`,外加签名 `sign`。
|
||||
|
||||
> ‼️ 「申请扣款」接口返回 `result_code=SUCCESS` 仅代表受理成功,**最终扣款成功 / 失败必须等扣款回调或调「查询订单」确认**。
|
||||
|
||||
**SYSTEMERROR 兜底流程**:扣款回调中 `err_code=SYSTEMERROR` 时,先调「查询订单」(请求体带 `sub_mch_id`):若 `trade_state` ≠ `CLOSED`,必须先调「关闭订单」关掉旧单,再**换新 `out_trade_no`** 重新申请延迟扣费(24 小时后执行)。
|
||||
|
||||
### 3.3 退款结果通知(V2 通用规范,`req_info` 加密)
|
||||
|
||||
**外层 XML**:
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code>SUCCESS</return_code>
|
||||
<appid><![CDATA[wx8888888888888888]]></appid>
|
||||
<mch_id><![CDATA[1900000109]]></mch_id>
|
||||
<nonce_str><![CDATA[5K8264ILTKCH16CQ2502SI8ZNMTM67VS]]></nonce_str>
|
||||
<req_info><![CDATA[T87GAHG17TGAHG1TGHAHAHA1Y1CIOA9UGJH1GAHV871HAGAGQYQQPOOJMXNBCXBVNMNMAJAA]]></req_info>
|
||||
</xml>
|
||||
```
|
||||
|
||||
**`req_info` 解密步骤**(与所有 V2 退款回调共用):
|
||||
|
||||
1. 对 `req_info` 字段做 **Base64 解码**得到密文
|
||||
2. 取**服务商 APIv2 密钥**做 **MD5**,转为 32 位**小写**字符串作为 AES key
|
||||
3. 用该 key 对密文做 **AES-256-ECB 解密**(PKCS7Padding)得到内层 XML
|
||||
4. 解析内层 XML 得到 `transaction_id` / `out_trade_no` / `refund_id` / `out_refund_no` / `total_fee` / `refund_fee` / `refund_status` / `success_time` 等
|
||||
|
||||
**`refund_status` 取值**:`SUCCESS` 退款成功 / `CHANGE` 退款异常 / `REFUNDCLOSE` 退款关闭。
|
||||
|
||||
> ‼️ 退款回调**没有** `sign` 字段,整体不参与签名校验。可信度依赖:① `req_info` 用**服务商 APIv2 密钥**能成功解密(错的密文解不出来);② 解密后的 `out_trade_no` / `out_refund_no` 与本地订单匹配;③ `refund_fee` 与本地申请退款金额一致。
|
||||
>
|
||||
> ‼️ **服务商模式注意**:解密用的是**服务商 APIv2 密钥**,不是子商户的;解密后的内层 XML **不一定包含** `sub_mch_id`(不同接口字段不同),路由分发时需结合外层 XML 或本地订单 `out_refund_no` ↔ `sub_mch_id` 的映射。
|
||||
|
||||
## 四、商户应答规范
|
||||
|
||||
> 来源:[回调通知注意事项](https://pay.weixin.qq.com/doc/v2/partner/4011984734.md)
|
||||
|
||||
### 4.1 应答报文格式
|
||||
|
||||
**必须返回 XML**,字段名与接口文档一致,**报文前后和字段标签中间不能含特殊字符**(包括空白行、`\n` 等):
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
### 4.2 常见错误格式
|
||||
|
||||
| 错误 | 错误示例 |
|
||||
| --- | --- |
|
||||
| 内容为空 / 非 XML | `ok` / `success` / `支付成功` |
|
||||
| XML 报文中间含 `\n` 等转义字符 | `<xml>\n<return_code>...\n</xml>` |
|
||||
| 返回 JSON | `{"return_code":"SUCCESS","return_msg":"OK"}` |
|
||||
| 返回了整个页面的 HTML | (框架默认渲染了整页 HTML) |
|
||||
|
||||
### 4.3 应答时效与状态码
|
||||
|
||||
- **HTTP 状态码必须 2xx**(200/204);非 2xx 微信会重试
|
||||
- **必须 5 秒内应答**,否则视为失败
|
||||
- 处理失败建议返回**非 2xx**触发重试;幂等场景已处理过的,直接返回成功 XML
|
||||
- `notify_url` 的代码逻辑**不能做登录态校验**(必须能匿名访问)
|
||||
|
||||
## 五、必须做的安全与可靠性处理
|
||||
|
||||
| # | 项 | 怎么做 |
|
||||
| --- | --- | --- |
|
||||
| 1 | **签名验签**(仅签约 / 解约 / 扣款回调) | 用 V2 签名算法(MD5 / HMAC-SHA256,密钥 = **服务商 APIv2 密钥**)按字段 ASCII 字典序拼接验证 `sign`;详见 [📄 服务商模式签名与验签规则](./签名与验签规则.md) |
|
||||
| 2 | **退款回调可信度判断**(退款回调没 sign) | `req_info` 用 `md5(服务商 APIv2 密钥).lowercase()` 能成功 AES-256-ECB 解密,解密后 `out_trade_no` / `out_refund_no` 与本地匹配,`refund_fee` 与本地申请退款金额一致 |
|
||||
| 3 | **服务商身份正确性** | 所有签名 / 解密都使用**服务商号**对应的密钥,**不是子商户的** |
|
||||
| 4 | **`sub_mch_id` 路由分发**(服务商专属,最关键) | 业务入口先按报文中的 `sub_mch_id` 找到对应子商户业务流程;签约 / 解约回调还要按 `change_type=ADD/DELETE` 二级分发;**严禁未路由直接处理** |
|
||||
| 5 | **金额比对** | 扣款回调中 `total_fee` 必须与本地订单金额比对一致;退款回调中 `refund_fee` 必须与本地申请退款金额一致;金额字段类型用 `int` / `long`(单位分),**禁止 `double` / `float`** |
|
||||
| 6 | **openid / sub_openid 校准** | 商户记录的签约用户 openid 必须以**回调中返回的 `openid`**(如有 `sub_openid` 也以回调为准)为准,禁止用业务侧自记录的值(避免签约 / 扣款用户对不上) |
|
||||
| 7 | **幂等去重** | 同一通知可能多次送达:先按业务唯一标识(签约 / 解约:`contract_code` 或 `contract_id` + `change_type`;扣款:`out_trade_no`;退款:`out_refund_no`)查询本地状态,已处理直接返回成功 XML |
|
||||
| 8 | **并发锁** | 在状态检查与处理之前用 Redis 锁 / 行锁 / `synchronized` 等加锁,避免同一通知并发处理造成数据混乱 |
|
||||
| 9 | **业务路由分发** | 三类回调如复用同一 URL,按 §一 表格中的字段判断分发;签约 / 解约再按 `change_type=ADD/DELETE` 分发 |
|
||||
|
||||
## 六、回调 IP 白名单
|
||||
|
||||
> 商户侧对回调 IP 有防火墙策略限制时,需放行下方 IP 段。
|
||||
|
||||
| 出口位置 | IP 网段 |
|
||||
| --- | --- |
|
||||
| 上海电信 / 联通 / CAP | `101.226.103.0/25` / `140.207.54.0/25` / `121.51.58.128/25` |
|
||||
| 深圳电信 / 联通 / CAP | `183.3.234.0/25` / `58.251.80.0/25` / `121.51.30.128/25` |
|
||||
| 香港 | `203.205.219.128/25` |
|
||||
| **委托代扣 签约 / 解约通知出口** | `101.226.233.128/25` |
|
||||
| 退款 / 分账动账通知 IP | `175.24.214.208`、`175.24.211.24`、`175.24.213.135`、`109.244.180.23`、`114.132.203.119`、`43.139.43.69` |
|
||||
|
||||
同时关闭 WAF / CC 防护对回调 URL 的拦截,避免误判为恶意请求。
|
||||
|
||||
## 七、收不到回调的排查清单
|
||||
|
||||
按优先级逐项排查:
|
||||
|
||||
1. **前置配置缺失**
|
||||
- 签约接口未传 `notify_url` / 模板未配置解约通知 URL / 申请扣款未传 `notify_url`
|
||||
- 子商户未"授权"该服务商的委托代扣
|
||||
2. **地址格式错误**
|
||||
- 不是 `https://` / `http://` 开头 / 只有域名缺路径 / URL 携带参数 / 用了内网或本地 IP
|
||||
3. **域名 / 解析问题**
|
||||
- 国内域名未 ICP 备案 / DNS 解析失效
|
||||
4. **网络与服务器连通性**
|
||||
- 防火墙 / 安全组未对回调 IP 段开白名单(详见 §六)
|
||||
- WAF / CC 防护误拦
|
||||
- 网络丢包 / 延迟 > 3 秒导致超时
|
||||
- CDN / 反向代理(Nginx / Cloudflare)未将请求正确转发到后端
|
||||
5. **回调处理逻辑问题**
|
||||
- `notify_url` 做了登录态校验
|
||||
- 应答非 2xx 或非 XML(如返回 `ok` / JSON / HTML 页面)
|
||||
- 处理超时(> 5 秒)
|
||||
- 未做幂等导致重复处理把状态搞乱
|
||||
- **未按 `sub_mch_id` 路由**:实际收到了,但被路由到错误的子商户业务流程,看起来像"丢回调"
|
||||
6. **URL 前后有空格** → DNS 解析失败
|
||||
7. **微信不提供"回调发送日志"查询**:自行查 access log / Nginx log;同时调「查询订单」/「查询签约关系」/「查询退款」(请求体带 `sub_mch_id`)兜底确认实际状态
|
||||
|
||||
## 八、错误处理策略
|
||||
|
||||
| 错误场景 | 处理策略 |
|
||||
| --- | --- |
|
||||
| 应答超时 / 暂时失败 | 返回非 2xx,让微信按重试频率(见 §一表格)继续重试;业务侧补偿处理 |
|
||||
| 业务字段校验失败(金额不一致 / openid 不匹配 / `sub_mch_id` 不识别) | 返回 200 + `<return_code>FAIL</return_code><return_msg>...</return_msg>`,**同时告警人工介入** |
|
||||
| `req_info` 解密失败(退款回调) | 检查**服务商** APIv2 密钥与合作伙伴平台是否一致(最常见:密钥重置后代码未同步);密文未截断;MD5 后是否取 32 位**小写**作为 AES key |
|
||||
| V2 签名验签失败(签约 / 解约 / 扣款回调) | 检查**服务商** APIv2 密钥;字段是否按 ASCII 字典序排序;签名方式是否与签约时一致(H5 默认 HMAC-SHA256,其他默认 MD5);详见 [📄 服务商模式签名与验签规则](./签名与验签规则.md) |
|
||||
| 业务异常但已收到通知 | **建议返回 2xx + 成功 XML**(避免重试堆积),通过告警系统人工介入 |
|
||||
|
||||
## 九、幂等设计
|
||||
|
||||
- 业务侧所有写操作必须使用业务侧生成的唯一请求号
|
||||
- 签约 / 解约:`contract_code` + `change_type` 联合唯一(**服务商模式**下唯一性维度可叠加 `sub_mch_id`,避免不同子商户使用相同 `contract_code` 时冲突)
|
||||
- 扣款:`out_trade_no` 唯一
|
||||
- 退款:`out_refund_no` 唯一
|
||||
- 相同业务号 + 相同事件类型重复请求**不得创建重复资源、不得重复入账 / 出账**
|
||||
- 建议格式:`{业务前缀}_{sub_mchid}_{日期}_{序号}`,例如 `pap_pay_1900000109_20260429_000001`
|
||||
|
||||
## 十、回调与查单的双保险
|
||||
|
||||
> 来源参考:[支付回调和查单实现指引](https://pay.weixin.qq.com/doc/v2/partner/4011984698.md)
|
||||
|
||||
委托代扣建议**回调 + 主动查单双保险**:
|
||||
|
||||
- 申请扣款提交后**立即在本地登记 `out_trade_no` 状态为"扣款中"**
|
||||
- 收到扣款回调后按 §五 处理 + 同步状态
|
||||
- **同时**起一个定时任务(如每 30 秒)扫描超过预期时长仍未收到回调的扣款单,调「查询订单」(请求体带 `sub_mch_id`)主动确认
|
||||
- 退款 / 签约 / 解约同理:「查询退款」/「查询签约关系」(请求体带 `sub_mch_id`)兜底
|
||||
|
||||
## 十一、请求域名
|
||||
|
||||
- 主域名:`https://api.mch.weixin.qq.com`
|
||||
- 备域名:`https://api2.mch.weixin.qq.com`([跨城冗灾方案](https://pay.weixin.qq.com/doc/v3/partner/4012082567.md))
|
||||
@@ -0,0 +1,144 @@
|
||||
# 服务商模式开发参数与业务规则
|
||||
|
||||
> 来源:微信支付 V2 文档中心 · 委托代扣([协议规则](https://pay.weixin.qq.com/doc/v2/partner/4011988363.md) / [接口规则](https://pay.weixin.qq.com/doc/v2/partner/4011988362.md) / [接入流程](https://pay.weixin.qq.com/doc/v2/partner/4011988361.md) / [周期扣费](https://pay.weixin.qq.com/doc/v2/partner/4011988360.md))。本文档汇总服务商接入开发期需要确认的所有参数、密钥、模板、字段命名/取值规范以及业务规则。
|
||||
|
||||
## 一、接入参数清单(开发前必须备齐)
|
||||
|
||||
| # | 参数 | 用途 | 获取位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `mch_id`(**服务商**号) | 所有委托代扣接口的身份标识;**不是子商户号** | 合作伙伴平台首页 / 邮件 |
|
||||
| 2 | `sub_mch_id`(子商户号) | 服务商所有接口必传 | 合作伙伴平台 → 商户管理 → 特约商户管理 |
|
||||
| 3 | `appid`(**服务商** appid) | 所有接口必传;APP 用开放平台 appid(与公众号 appid 不同);H5 / 公众号纯签约用公众号 appid;签约页只展示**服务商** appid 对应的 logo | 服务商自有的公众平台 / 开放平台 |
|
||||
| 4 | `sub_appid`(子商户 appid) | 可选;**需服务商在合作伙伴平台为子商户绑定**;填后回调中会返回 `sub_openid` | 子商户公众平台 / 开放平台 |
|
||||
| 5 | **APIv2 密钥**(32 字符) | V2 接口签名密钥(MD5 / HMAC-SHA256),**用服务商号的密钥,不是子商户的** | 合作伙伴平台 → 账户中心 → API 安全 → APIv2 密钥 → 设置 |
|
||||
| 6 | **APIv3 密钥**(32 字节) | **仅「预扣费通知」接口需要**(V3 JSON + RSA),同时也是 V3 回调通知 AES 解密密钥 | 合作伙伴平台 → 账户中心 → API 安全 → APIv3 密钥 → 设置 |
|
||||
| 7 | **服务商 API 证书**(`apiclient_cert.p12` / `apiclient_cert.pem` + `apiclient_key.pem`) | 「申请退款」`/secapi/pay/refund` 必须双向证书;预扣费通知 V3 签名也需要私钥;**用服务商的证书** | 合作伙伴平台 → 账户中心 → API 安全 → 商户 API 证书 → 申请 |
|
||||
| 8 | **微信支付公钥** 或 **微信支付平台证书** | 验签预扣费通知 V3 响应、V3 回调通知 | 合作伙伴平台 → 账户中心 → API 安全;推荐用「微信支付公钥」(`PUB_KEY_ID_xxxxxx`) |
|
||||
| 9 | **委托代扣模板 ID**(`plan_id`) | 所有签约 / 解约 / 查询签约关系接口必传 | 合作伙伴平台 → 交易中心 → 高级业务 → 委托代扣模版管理 |
|
||||
| 10 | **签约协议号**(`contract_code`) | 服务商自定义,**同一服务商号下唯一**;用于 `plan_id+contract_code` 模式解约 / 查询 | 业务方按"业务前缀_日期_序号"自行生成 |
|
||||
| 11 | **委托代扣协议 ID**(`contract_id`) | 签约成功后微信返回,用于「申请扣款」「`contract_id` 模式解约 / 查询」 | 签约成功回调 / 查询签约关系返回 |
|
||||
| 12 | **`notify_url`**(多个) | 签约通知 URL:在签约接口 `notify_url` 字段传入;扣款通知 URL:在「申请扣款」`notify_url` 字段传入;解约通知 URL:在模板配置中预设 | 由服务商自行准备 HTTPS 公网地址,**所有子商户共用**,按 `sub_mch_id` 在业务侧路由 |
|
||||
| 13 | **`out_trade_no`**(商户订单号) | 「申请扣款」必传,**同一服务商号下唯一** | 业务方自行生成 |
|
||||
| 14 | **`out_refund_no`**(商户退款单号) | 「申请退款」必传,同一服务商号下唯一;退款重试**严禁更换单号** | 业务方自行生成 |
|
||||
|
||||
## 二、APIv2 接入规则(协议层)
|
||||
|
||||
| 项 | 取值 |
|
||||
| --- | --- |
|
||||
| 传输方式 | HTTPS |
|
||||
| 提交方式 | POST(公众号 / H5 纯签约入口为 GET) |
|
||||
| 数据格式 | XML,根节点 `<xml>...</xml>`;预扣费通知例外,是 V3 JSON |
|
||||
| 字符编码 | **UTF-8 子集**:仅支持 1-3 字节编码字符;**不支持 4 字节字符**(emoji 等) |
|
||||
| 签名算法 | MD5 / HMAC-SHA256(默认 MD5;H5 纯签约预签约默认 HMAC-SHA256) |
|
||||
| 是否需要证书 | 「申请退款」需双向证书;其余 V2 接口不需要 |
|
||||
| 判断逻辑 | **先判断协议字段** `return_code` → **再判断业务返回** `result_code` → **最后判断交易状态** `trade_state` |
|
||||
|
||||
> ‼️ **一单一支付**:必须严格按"一单一支付"原则——在未得到支付系统**明确回复**之前不要换单,避免重复支付/重复扣款。
|
||||
|
||||
## 三、字段传参规范(产品特有 / 易踩坑)
|
||||
|
||||
### 3.1 服务商专属差异
|
||||
|
||||
- **路径多 `/partner/`**:核心签约接口路径为 `/papay/partner/entrustweb` / `/papay/partner/preentrustweb` / `/papay/partner/h5entrustweb` / `/papay/partner/querycontract`;核心扣款接口路径为 `/pay/partner/pappayapply`;预扣费通知 V3 路径为 `/v3/partner-papay/contracts/{contract_id}/notify`。
|
||||
- **解约接口路径不带 `/partner/`**:申请解约 `https://api.mch.weixin.qq.com/papay/deletecontract`(与商户模式同路径,但请求体多 `sub_mch_id`)。
|
||||
- **退款 / 查询订单 / 查询退款 / 关单 / 下载账单**:与商户模式**同 URL**,请求体多 `sub_mch_id`。
|
||||
- **必须使用服务商的密钥与证书**:APIv2 密钥、APIv3 密钥、API 证书都从合作伙伴平台获取,**不是从子商户**。
|
||||
- **服务商模式不支持「支付中签约」**:`pay/contractorder` 仅商户模式可用。
|
||||
- **预扣费通知 V3 字段差异**:商户模式用 `mchid` + `appid`;服务商模式用 `sp_mchid` + `sp_appid` + `sub_mchid` + `sub_appid`(可选)。
|
||||
|
||||
### 3.2 字符编码 / 命名
|
||||
|
||||
- **`contract_display_account`**(用户账户展示名称):用于签约页"开通账号"展示;**禁止传微信昵称**(含 emoji),UTF-8 子集只支持 1-3 字节字符。
|
||||
- **`request_serial`**(请求序列号):int64,**禁止以 0 开头**,纯数字;序列号只用于排序、不作查询条件;同一服务商号下需唯一。
|
||||
- **`timestamp`**(时间戳):10 位 UNIX 秒;**长度必须 >= 9 位**(< 9 位会被拒)。
|
||||
- **`contract_code`** vs **`contract_id`**:前者是商户自定义的协议号,后者是签约成功后微信返回的 ID;申请扣款只能传 `contract_id`,解约 / 查询签约关系两者皆可。
|
||||
|
||||
### 3.3 URL 编码(最易踩坑)
|
||||
|
||||
- 所有签约入口(公众号 / APP 预签约 / 小程序 / H5)都要求 **`notify_url` 在 URL 中传输前做一次 URL Encode**;但**签名时使用 encode 之前的原值**。
|
||||
- encode 后的转义符(如 `%2F`、`%3A`)要求**大写**。
|
||||
- ‼️ **不要双重 encode**——encode 一次即可。
|
||||
|
||||
### 3.4 金额
|
||||
|
||||
- 单位为**分**,整数;`refund_fee` <= `total_fee`,单笔订单部分退款次数 <= 50。
|
||||
- 实际扣款金额以**「申请扣款」`total_fee`**为准,**不是**模板预设金额:
|
||||
- 模板"扣费类型 = 为"(固定金额)→ 必须按模板金额传
|
||||
- 模板"扣费类型 = 不高于"(非固定)→ 可在限额内自定义
|
||||
|
||||
### 3.5 协议号 / 模板 ID 唯一性
|
||||
|
||||
- 协议维度:**服务商号 + plan_id + 微信号** 唯一。同一用户在同一模板下只能有一个生效签约(解约可重新获得名额)。**与子商户号无关**——协议归属在服务商维度。
|
||||
- 同一服务商号下不同 plan_id 可与同一用户分别签约一次。
|
||||
- 一个服务商可申请的模板数**无上限**。
|
||||
- 不同签约方式(如 APP 纯签约、H5 纯签约)的 `plan_id` 可以共用。
|
||||
|
||||
### 3.6 协议关系与业务字段
|
||||
|
||||
- **APP 签约**:必须使用**服务商开放平台 appid**(与公众号 appid 不同)。
|
||||
- **签约用户的 openid 以微信侧回调为准**:服务商记录的签约用户 openid / sub_openid 必须以「签约/解约结果通知」中回调的 `openid` / `sub_openid` 为准,**禁止使用业务侧自记录的 openid**,避免扣款用户和签约用户对不上。
|
||||
|
||||
## 四、扣费时段与频率约束
|
||||
|
||||
| 约束项 | 取值 |
|
||||
| --- | --- |
|
||||
| 「申请扣款」/ 「预扣费通知」可调用时段 | 北京时间每天 **7:00 - 22:00**(强制;签约时间不影响) |
|
||||
| 「申请扣款」请求频率 | **150 QPS**(正常请求) |
|
||||
| 同一签约协议每日成功扣款次数 | **1 次**(失败不计) |
|
||||
| 同一签约协议单周期成功扣款次数 | **2 次** |
|
||||
| 自动续费模板单笔限额 | 按模板申请时的金额配置 |
|
||||
| 测试模板单笔限额 / 单日限频 | 0.1 元 / 100 次 |
|
||||
| `out_trade_no` 重复扣款 | 同 mch_id + 同 `out_trade_no` 仅扣 1 次 |
|
||||
|
||||
## 五、首次签约的特殊扣款规则
|
||||
|
||||
如果用户为**首次签约**(含解约后重新签约),从用户**签约成功时间**开始算:
|
||||
|
||||
- **12 小时内**调用「申请扣款」 → **立即执行,无延迟**;预扣费通知模式下也无需先调「预扣费通知」
|
||||
- 超过 12 小时后调用「申请扣款」 → 按通知模式(24h / 预扣费通知)的标准规则延迟扣费
|
||||
|
||||
## 六、可选模块清单(按需启用)
|
||||
|
||||
下列模块仅当业务实际涉及时才需要:
|
||||
|
||||
- **周期扣款(预扣费通知模式)** → 必须先调「预扣费通知」V3 接口;模板需运营修改为预扣费通知模式
|
||||
- **周期扣款(24 小时延迟扣费模式)** → 直接调「申请扣款」即可,受理后 24 小时内勿重复扣款
|
||||
- **退款** → 需「申请退款」`/secapi/pay/refund`,必须双向证书;订单超过 1 年不能退款
|
||||
- **对账下载** → 「下载交易账单」`/pay/downloadbill`,账单生成时间为次日 9 点,建议 10 点后下载;可选 GZIP 压缩;可指定 `sub_mch_id` 下载子商户账单
|
||||
- **首期优惠** → 申请模板时设置"优惠价格 < 标准价格"
|
||||
- **APP 鸿蒙系统适配** → 必须使用 WXLaunchMiniProgram(OpenBusinessWebview 不支持鸿蒙)
|
||||
- **子商户开通查询** → 在合作伙伴平台「特约商户授权产品 → 服务商委托代扣 → 特约商户列表」查看
|
||||
- **邀请子商户授权** → 同上路径下点击"邀请子商户授权"
|
||||
|
||||
## 七、回调地址 / IP 白名单
|
||||
|
||||
> 详细的回调机制(解密 / 验签 / 幂等 / 路由)见 [📄 服务商模式回调处理](./回调处理.md)。
|
||||
|
||||
- 回调地址要求:**HTTPS + 域名已 ICP 备案 + 公网可访问**;**不允许内网 / `127.0.0.1` / `localhost` / 携带 query 参数**。
|
||||
- 5 秒内必须返回 HTTP 2XX;逻辑里不能做登录态校验。
|
||||
- 所有子商户**共用同一回调地址**——业务侧必须按回调中的 `sub_mch_id` 路由到对应子商户的业务流程,否则会出现"A 子商户的扣款 / 签约被 B 子商户业务处理"的串单事故。
|
||||
- 微信支付不提供"回调发送日志"查询,需自行查 access log;回调可能重复送达,必须按业务唯一标识 + `event_type` 去重 + 加锁。
|
||||
- 委托代扣**没有 IP 白名单**对接入侧的限制;商户侧若有防火墙策略限制回调 IP,需放行通用 IP 段(详见 [📄 服务商模式回调处理](./回调处理.md) "回调 IP 白名单" 章节)。
|
||||
|
||||
## 八、签名 / 验签算法
|
||||
|
||||
> 详细签名步骤、Authorization 头格式、4/5/3 行签名串、调起支付签名等见 [📄 服务商模式签名与验签规则](./签名与验签规则.md)。
|
||||
|
||||
简要要点:
|
||||
|
||||
- V2 主体接口(签约 / 申请扣款 / 解约 / 查询 / 退款 / 关单 / 账单 / 回调)→ MD5 / HMAC-SHA256,密钥 = 服务商 APIv2 密钥
|
||||
- V3「预扣费通知」 → SHA256-RSA2048,私钥 = 服务商 API 证书私钥
|
||||
- V3 回调验签密钥 = 微信支付公钥 / 平台证书
|
||||
- 「申请退款」需要 `apiclient_cert.p12` 双向证书
|
||||
|
||||
## 九、其他业务规则
|
||||
|
||||
- **解约**:用户可直接解约,无需商户同意;解约后可继续以纯签约方式重新签约(注意签约单号不能重复)。
|
||||
- **续费协议提前续费**:必须在协议**最后一期(到期当月)**发起续期,不能在中间月份提前操作。
|
||||
- **首月优惠**:申请模板时配置后,签约页自动展示"首月优惠";服务商侧无需自行判断。
|
||||
- **首期扣费金额可以与后续不一致**:每期扣款金额在模板限额内即可。
|
||||
- **扣款失败用户无感知**(不会发通知给用户);可扣周期内可换单重试,周期结束需重新调「预扣费通知」。
|
||||
- **扣款失败自动关单**:所有可扣方式失败后订单变 `CLOSED`,再次扣费必须**换 `out_trade_no`** 重新调「申请扣款」。
|
||||
- **回调结果为 `err_code=SYSTEMERROR` 的扣款单**:先查询订单,若 `trade_state` ≠ `CLOSED`,需先关单,再换单号重新申请延迟扣费(24 小时后执行)。
|
||||
- **签约页 logo**:仅展示服务商 appid 的 logo,**不能切换为子商户 sub_appid 的 logo**。
|
||||
- **微信纯签约现支持鸿蒙**:仅 WXLaunchMiniProgram 调起方式支持,OpenBusinessWebview 不支持。
|
||||
@@ -0,0 +1,84 @@
|
||||
# 服务商模式接入质量检查
|
||||
|
||||
## 角色设定:金融支付系统技术专家
|
||||
|
||||
> ‼️ **本节角色、铁律和问题雷达是质检的全部驱动力,必须内化后再审代码。**
|
||||
|
||||
你是金融支付系统技术专家,全栈工程师出身,亲手写过从前端收银台到后端交易引擎的全链路代码。你主导过千万级用户规模的国民级支付系统架构设计,从零搭建过高并发交易平台。你熟悉主流支付平台的接入规范与安全体系,对 API 签名验签机制、异步回调通知处理、资金流对账有丰富的实战经验。你对代码质量有极强的直觉,尤其对资金链路上的异常处理缺失高度警觉。
|
||||
|
||||
你对支付系统的要求极高:接口交互必须有完善的异常处理和兜底方案,资金操作必须可追溯、可对账,所有外部输入必须经过校验才能进入业务逻辑。
|
||||
|
||||
## 铁律
|
||||
|
||||
**铁律一:高可用(99.9999%)**
|
||||
系统可用性要求 99.9999%(六个 9),即每一百万次请求中最多允许一次失败。支付链路上不允许存在单点故障,每一个外部调用都必须有超时、重试和降级方案。
|
||||
检查直觉:调用微信支付 API 超时了,代码会自动重试还是直接报错?重试的时候会不会导致重复下单?微信的支付回调一直没来,系统有没有定时去主动查询订单状态?用户快速点了两次支付按钮,会不会创建两笔订单?
|
||||
|
||||
**铁律二:资金安全(一分钱都不能错)**
|
||||
金额计算必须使用整数(单位:分),杜绝浮点精度丢失。每一笔资金变动(支付、退款、分账)都必须有据可查,系统必须在次日通过账单对账主动发现差异。
|
||||
检查直觉:金额字段的类型是 int/long 还是 double/float?用户申请退款时,代码有没有累加历史退款金额并校验是否超过订单总额?系统有没有每天自动拉取微信账单和本地订单做比对?
|
||||
|
||||
**铁律三:零信任(不信任任何未经验证的外部数据)**
|
||||
微信的回调通知、前端传入的参数、缓存中的数据,在进入业务逻辑前必须经过验证,未验证的输入一律视为不可信。
|
||||
检查直觉:收到支付回调后,代码是先验签还是直接解析 body 处理业务?下单接口的金额是从后端数据库查的还是直接用前端传过来的值?回调通知中的支付金额有没有和本地订单金额做比对?私钥是通过环境变量加载的还是硬编码在代码里?
|
||||
|
||||
## 检查方法
|
||||
|
||||
1. **扫代码** — 快速扫描代码,按问题雷达定位高风险区域
|
||||
2. **追链路** — 沿资金流完整走一遍:签约(含 sub_mch_id 子商户路由) → (周期扣款则)预扣费通知/24h 等待 → 申请扣款 → 扣款回调(按 sub_mch_id 路由分发) → 查询订单兜底 → 退款 → 对账,任何断点都是事故点
|
||||
3. **做预演** — 对每个关键节点问"如果这里故障了/超时了/被攻击了/来了两次/串单了,会怎样?"
|
||||
|
||||
**输出要求**:发现问题必须给出修复方向,不能只说"有风险";必须基于代码事实,不基于猜测;结果按 🔴🟡🟠 分级,致命问题置顶。
|
||||
|
||||
## 问题雷达
|
||||
|
||||
> **来源**:通用安全雷达(固定 4 项)+ 产品专属雷达(**重点从「开发指引」与「常见问题」提炼**,其他文档作为补充)。
|
||||
>
|
||||
> 以下仅列举常见的高风险问题,**不要只检查列出的项**。检查时应反向运用铁律:逐条铁律审视代码,发现未列出的同类问题。
|
||||
|
||||
### 通用安全雷达(所有产品必查)
|
||||
|
||||
> 4 项**独立判定**,每项必须给出"通过 / 未实现 / 不涉及"三选一的明确结论,**禁止合并多项为一条**。具体检查方法见 [签名与验签规则](./签名与验签规则.md)。
|
||||
|
||||
| # | 检查项 | 检查锚点 | 未实现的判定特征 | 默认级别 |
|
||||
| --- | ------ | -------- | ---------------- | -------- |
|
||||
| 1 | **HTTP 响应验签** | 发起请求并处理响应的代码(OkHttp `execute()` / HttpClient `send()` 等) | 收到 2XX 响应后直接解析返回数据,中间无任何验签调用 | 🔴 致命 |
|
||||
| 2 | **回调通知验签** | 处理回调通知的代码(含 `event_type` / `resource_type` / `encrypt-resource` 等字段) | 收到通知后**先解密或解析业务数据**,验签缺失或在解密之后 | 🔴 致命 |
|
||||
| 3 | **幂等去重 + 并发锁** | 回调处理流程的入口 | 既无按"业务唯一标识 + `event_type`"的去重查询,也无加锁逻辑(Redis 锁 / 行锁 / `synchronized` 等) | 🔴 致命 |
|
||||
| 4 | **探测流量未做特殊跳过** | 验签代码分支 | 对签名值含 `WECHATPAY/SIGNTEST/` 前缀的请求做了特殊跳过/早返回 | 🟠 可选 |
|
||||
|
||||
### 产品专属雷达
|
||||
|
||||
> ‼️ 委托代扣**主要走 V2(XML + MD5/HMAC-SHA256)**,**唯一例外**是「预扣费通知」走 V3(JSON + RSA)+ V3 回调通知走 AEAD_AES_256_GCM。审代码时必须**先确认协议版本**,不要套错算法。
|
||||
>
|
||||
> ‼️ 服务商模式下,**所有子商户共用同一回调 URL**,回调中携带 `sub_mch_id`(如有 `sub_appid` 一并返回 `sub_openid`),**必须按 `sub_mch_id` 路由分发**,否则会出现"A 子商户的扣款被 B 子商户业务处理"的串单事故。
|
||||
|
||||
| # | 检查项 | 检查锚点 | 未实现的判定特征 | 默认级别 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 5 | **服务商身份正确性** | 所有接口签名 / 请求体构造代码 | 误用了**子商户**的 mch_id / appid / APIv2 密钥 / API 证书去签名(应**全部使用服务商号**的);或 sub_mch_id 字段未传 | 🔴 致命 |
|
||||
| 6 | **`/partner/` 路径正确性** | 所有 V2 接口 URL 拼接 | 漏写 `/partner/` 段(如签约误用 `/papay/entrustweb` 而非 `/papay/partner/entrustweb`),导致 401 / 路径不匹配 | 🔴 致命 |
|
||||
| 7 | **回调中 `sub_mch_id` 路由分发** | 签约 / 解约 / 扣款回调入口 | 回调代码**未读取 `sub_mch_id`** 直接处理,或路由仅按 `change_type` 分类未按子商户分发,造成串单 | 🔴 致命 |
|
||||
| 8 | **回调中 `sub_openid` 校准** | 签约 / 扣款回调处理 | 商户记录的签约 / 扣款用户 sub_openid 直接用业务侧自记录的值,未以**回调中返回的 `sub_openid`** 为准 | 🔴 致命 |
|
||||
| 9 | **V2 签名密钥来源** | V2 接口签名调用 | APIv2 密钥**硬编码**在代码里,未从环境变量 / 配置中心 / KMS 加载;或与合作伙伴平台 APIv2 密钥不一致 | 🔴 致命 |
|
||||
| 10 | **V2 字符编码限制(UTF-8 子集)** | `contract_display_account` / `body` / `attach` 等字符串字段赋值处 | 字段直接传了用户微信昵称(含 emoji 4 字节字符),未做"非 1-3 字节字符过滤"或长度截断 | 🔴 致命 |
|
||||
| 11 | **`notify_url` 编码** | 签约接口 URL 拼接代码 | URL 中的 `notify_url` **未做一次 URL Encode** 或做了**双重 encode**;或 encode 后转义符未大写 | 🔴 致命 |
|
||||
| 12 | **签名时使用 encode 前的原值** | 签名串拼接逻辑 | 签名时使用了 encode 后的 `notify_url`(与发送的不一致),或字段顺序未按 ASCII 字典序 | 🔴 致命 |
|
||||
| 13 | **扣款时段前置校验** | 「申请扣款」/「预扣费通知」调用入口 | 调用前**未校验北京时间是否在 7:00-22:00**,22:00 后定时任务批量扣款会触发拒绝;或扣款失败时未识别"非可扣费时段"原因 | 🟡 推荐 |
|
||||
| 14 | **`out_trade_no` 唯一性 + 不复用** | 「申请扣款」`out_trade_no` 生成逻辑 | 重试场景使用了相同 `out_trade_no`(同 mch_id 同单号只会扣一次,旧单失败后必须**换新号**重新申请扣款);或换新号时**未先查询/关闭旧单** | 🔴 致命 |
|
||||
| 15 | **签约成功幂等性** | 签约成功回调处理 | 同一 `contract_code` 多次签约成功通知未做幂等去重,导致重复创建协议 | 🔴 致命 |
|
||||
| 16 | **扣款回调金额校验** | 扣款回调处理代码 | 回调中的 `total_fee` 未与本地订单金额比对,直接信任并入账 | 🔴 致命 |
|
||||
| 17 | **扣款失败 SYSTEMERROR 兜底流程** | 扣款回调失败分支 | 回调结果 `err_code=SYSTEMERROR` 时,**未先查询订单**确认状态,**未按"非 CLOSED → 关单 → 换号重新申请延迟扣费"流程操作** | 🔴 致命 |
|
||||
| 18 | **查询订单兜底** | 申请扣款失败 / 回调长时间未到的处理逻辑 | 申请扣款返回 `ORDER_ACCEPTED` / `SYSTEMERROR` 时,未调用「查询订单」确认实际状态就直接换号重试,导致重复扣款 | 🔴 致命 |
|
||||
| 19 | **扣款失败自动关单识别** | 查询订单 `trade_state` 处理 | 未识别 `trade_state=CLOSED`(自动关单状态),仍以原 `out_trade_no` 重试扣款 | 🔴 致命 |
|
||||
| 20 | **预扣费通知(V3)签名 / 密钥独立** | 预扣费通知接口调用代码 | 误用 V2 APIv2 密钥 + MD5 签名调 V3 接口;或路径未带 `/partner-papay/` 段(服务商专属路径 `/v3/partner-papay/contracts/{contract_id}/notify`) | 🔴 致命 |
|
||||
| 21 | **预扣费通知 V3 字段** | 预扣费通知请求体构造 | 误用商户字段 `mchid` / `appid`(应为服务商专属 `sp_mchid` / `sp_appid` + `sub_mchid` / `sub_appid`) | 🔴 致命 |
|
||||
| 22 | **预扣费通知 24 小时内首签免发** | 预扣费通知调用入口 | 未识别"首次签约 12 小时内申请扣款立即执行"规则,仍强制先调预扣费通知,导致用户重复收到提醒 | 🟡 推荐 |
|
||||
| 23 | **协议唯一性冲突处理** | 签约调用入口 | 用户已签约同 mch_id+plan_id 时,签约失败错误 `CONTRACT_CODE DUPLICATION` / "用户已签约"未先调「查询签约关系」确认状态后再决定走签约还是直接调用扣款 | 🟡 推荐 |
|
||||
| 24 | **不要使用「支付中签约」**(错误识别) | 子商户接入入口 | 误以为服务商模式可以用 `pay/contractorder`(**仅商户模式支持**);如有此需求需子商户自行直连接入 | 🔴 致命 |
|
||||
| 25 | **APP 调起签约 SDK 选型**(仅 APP 签约场景) | APP 签约 SDK 接入代码 | 新申请的模板仍接入 `OpenBusinessWebview`(已停止新申请),或鸿蒙系统未使用 `WXLaunchMiniProgram` | 🟡 推荐 |
|
||||
| 26 | **重复退款 / 单号复用**(仅退款场景) | 「申请退款」`out_refund_no` 生成 | 退款重试**更换了** `out_refund_no`(必须复用原单号);或未累加历史退款金额校验是否超过订单总额 | 🔴 致命 |
|
||||
| 27 | **退款证书 mTLS**(仅退款场景) | `/secapi/pay/refund` 调用代码 | HTTPS 客户端未加载**服务商**的 `apiclient_cert.p12` 双向证书(不是子商户的),直接报 401 / `CERT_ERROR` | 🔴 致命 |
|
||||
| 28 | **每日对账自动化**(有扣款的子商户必查) | 定时任务 / 跑批代码 | 未实现每日 10 点后自动调「下载交易账单」(可指定 `sub_mch_id`)+ 与本地订单逐笔比对 + 差异告警;或对账中未识别 `PAP` 交易类型为委托代扣 | 🟡 推荐 |
|
||||
| 29 | **子商户授权状态前置校验** | 服务商接入流程 | 接入新子商户前未确认子商户在合作伙伴平台「特约商户授权产品 → 服务商委托代扣 → 特约商户列表」中已授权 | 🟡 推荐 |
|
||||
| 30 | **金额单位 / 数据类型** | 金额字段定义和计算 | 金额字段使用 `double` / `float`(应为 `int` / `long`,单位分) | 🔴 致命 |
|
||||
| 31 | **APIv2 密钥与 APIv3 密钥不混用** | 两类签名调用代码 | V3 预扣费通知误用 APIv2 密钥;V2 签约 / 扣款误用 APIv3 密钥 | 🔴 致命 |
|
||||
@@ -0,0 +1,266 @@
|
||||
# 服务商模式签名与验签规则
|
||||
|
||||
> 委托代扣是 **API V2 主体 + 单接口 V3 例外**(参见 [SKILL.md](../../../SKILL.md) 全局规则 §3)。本文以 **V2 签名(MD5 / HMAC-SHA256)** 为主,V3 仅作为「预扣费通知」一个接口的例外说明。
|
||||
>
|
||||
> 来源:[V2 服务商协议规则](https://pay.weixin.qq.com/doc/v2/partner/4011988363.md) / [V2 安全规范-签名算法](https://pay.weixin.qq.com/doc/v2/merchant/4011985891.md) / [V2 在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md)
|
||||
>
|
||||
> ⚠️ **常见误解**:委托代扣不是 V3 RSA 签名体系,**绝大多数接口请使用 V2 MD5/HMAC-SHA256**。把 V3 的 5 行 RSA 签名套到委托代扣 V2 接口(签约 / 申请扣款 / 解约 / 查询 / 退款 / 关单 / 账单 / V2 异步回调)→ 100% 401 SIGN_ERROR。
|
||||
>
|
||||
> ⚠️ **服务商 / 商户在签名链路上完全一致**(算法、字段顺序规则、密钥种类、回调验签都相同)。两者只在**请求参数命名**上有差异:服务商的 `mch_id` 是**服务商号**,请求体多 `sub_mch_id`(必传)/ `sub_appid`(可选),核心接口路径多 `/partner/` 段,签名所用的 APIv2 密钥 / APIv3 密钥 / API 证书全部使用**服务商的**(不是子商户的)。**真正不同的是品牌直连**——委托代扣不涉及。
|
||||
|
||||
## 一、协议版本与签名算法对应关系
|
||||
|
||||
| 接口分组 | 协议版本 | 数据格式 | 签名算法 | 签名密钥 | 是否需双向证书 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 签约(公众号 / APP / 小程序 / H5 纯签约) | **V2** | XML / URL Query | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 申请扣款 `/pay/partner/pappayapply` | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 解约 `/papay/deletecontract` | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 查询签约关系 / 查询订单 | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 关闭订单 `/pay/closeorder` | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 下载交易账单 `/pay/downloadbill` | **V2** | XML 请求 / 文本响应 | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 申请退款 `/secapi/pay/refund` | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | **是**(服务商 apiclient_cert.p12) |
|
||||
| 查询退款 `/pay/refundquery` | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | 否 |
|
||||
| 签约 / 解约 / 扣款 / 退款 异步回调 | **V2** | XML | **MD5 或 HMAC-SHA256** | **服务商** APIv2 密钥 | — |
|
||||
| **预扣费通知** `/v3/partner-papay/contracts/{contract_id}/notify` | **V3** | JSON | **SHA256-RSA2048** | 服务商 API 证书私钥 | 否(验签需微信支付公钥) |
|
||||
|
||||
> **签名方式默认值**:除「H5 纯签约预签约接口」默认 `HMAC-SHA256` 外,其余 V2 接口默认 `MD5`;签约 / 申请扣款等支持通过 `sign_type` 字段显式指定。**收到响应/回调时按报文里的 `sign_type` 还原签名方式校验**,不要写死。
|
||||
>
|
||||
> ⚠️ **服务商模式不支持「支付中签约」**:`pay/contractorder` 仅商户模式可用,本文不涉及该接口的调起支付签名。
|
||||
|
||||
## 二、V2 请求签名(主体规范)
|
||||
|
||||
### 2.1 签名生成步骤
|
||||
|
||||
> 摘自官方 [V2 签名算法](https://pay.weixin.qq.com/doc/v2/merchant/4011985891.md),建议直接对照官方 [在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md) 复核。
|
||||
|
||||
**第一步**:所有发送或接收到的数据为集合 M,将 M 内**非空**参数值按参数名 ASCII 字典序(key1 < key2 < …)排序,使用 `key=value` 拼接成字符串 stringA。
|
||||
|
||||
**第二步**:在 stringA 末尾拼接 `&key=<服务商APIv2密钥>` 得到 stringSignTemp,对其做 MD5 或 HMAC-SHA256 运算,结果**全部转换为大写**,得到 sign。
|
||||
|
||||
**第三步**:将 sign 作为 `<sign>` 字段放入 XML 请求体一并发出。
|
||||
|
||||
### 2.2 必须遵守的 6 条规则
|
||||
|
||||
1. **ASCII 字典序**:参数名按 ASCII 从小到大排序,**不是字母序、不是字段定义顺序**。注意:服务商的 `sub_mch_id` / `sub_appid` 也是普通字段,按字典序入串(`sub_*` 排在 `sign_type` 之后、`total_fee` 之前)。
|
||||
2. **空值不参与签名**:参数值为空字符串、`null`、未传字段一律**不进入签名串**。`sub_appid` 未绑定时不传,则不进入签名。
|
||||
3. **`sign` 字段不参与签名**:响应/回调里收到的 `sign` 是结果,不能再放进签名串。
|
||||
4. **参数名区分大小写**:`mch_id` ≠ `Mch_Id`;字段拼写以官方文档为准。
|
||||
5. **使用原值签名,不做 URL Encode**:尤其是 `notify_url`,签名时使用 encode **之前**的原值;URL Encode 只发生在 URL 拼接传输环节。
|
||||
6. **支持扩展字段**:响应/回调可能新增字段,验签时**必须把新字段一起带进签名串**,否则未来某天突然报"验签失败"。
|
||||
|
||||
### 2.3 字符编码
|
||||
|
||||
- 强制 **UTF-8**,且**仅支持 1-3 字节编码字符**(Unicode BMP 子集)。
|
||||
- **不支持 4 字节字符**(emoji、部分罕用汉字、辅助平面字符)。
|
||||
- 高频踩雷:`contract_display_account`(签约展示账户名)、`body`(商品描述)传了带 emoji 的微信昵称 → 直接被网关拒绝。
|
||||
|
||||
### 2.4 完整示例(基于官方样例改造,含 sub_mch_id)
|
||||
|
||||
假设要发送的服务商申请扣款参数:
|
||||
|
||||
```
|
||||
appid: wxcbda96de0b165486 # 服务商 appid
|
||||
mch_id: 10000098 # 服务商号
|
||||
sub_mch_id: 10010405 # 子商户号
|
||||
nonce_str: 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
|
||||
body: 水电代扣
|
||||
out_trade_no: 217752501201407033233368018
|
||||
total_fee: 888
|
||||
trade_type: PAP
|
||||
spbill_create_ip: 8.8.8.8
|
||||
notify_url: http://yoursite.com/wxpay.html
|
||||
contract_id: Wx15463511252015071056489715
|
||||
```
|
||||
|
||||
**第一步:按 ASCII 字典序拼接(非空字段)**:
|
||||
|
||||
```
|
||||
stringA = "appid=wxcbda96de0b165486"
|
||||
+ "&body=水电代扣"
|
||||
+ "&contract_id=Wx15463511252015071056489715"
|
||||
+ "&mch_id=10000098"
|
||||
+ "&nonce_str=5K8264ILTKCH16CQ2502SI8ZNMTM67VS"
|
||||
+ "¬ify_url=http://yoursite.com/wxpay.html"
|
||||
+ "&out_trade_no=217752501201407033233368018"
|
||||
+ "&spbill_create_ip=8.8.8.8"
|
||||
+ "&sub_mch_id=10010405"
|
||||
+ "&total_fee=888"
|
||||
+ "&trade_type=PAP"
|
||||
```
|
||||
|
||||
> ⚠️ `notify_url` 此处是**未 URL Encode 的原值**;`body` 中的中文以 UTF-8 字节直接参与计算(不要 encode)。
|
||||
|
||||
**第二步:拼**服务商** APIv2 密钥并签名**:
|
||||
|
||||
```
|
||||
# 服务商 APIv2 密钥来自合作伙伴平台 → 账户中心 → API 安全 → APIv2 密钥
|
||||
stringSignTemp = stringA + "&key=<服务商APIv2密钥>"
|
||||
|
||||
# MD5
|
||||
sign = MD5(stringSignTemp).toUpperCase()
|
||||
|
||||
# 或 HMAC-SHA256(部分语言 hmac 输出二进制,需自行转十六进制再大写)
|
||||
sign = hash_hmac("sha256", stringSignTemp, key).toUpperCase()
|
||||
```
|
||||
|
||||
**第三步:组装最终 XML**:
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<sign>E1EE61A91C8E90F299DE6AE075D60A2D</sign>
|
||||
<body>水电代扣</body>
|
||||
<out_trade_no>217752501201407033233368018</out_trade_no>
|
||||
<total_fee>888</total_fee>
|
||||
<trade_type>PAP</trade_type>
|
||||
<spbill_create_ip>8.8.8.8</spbill_create_ip>
|
||||
<notify_url>http://yoursite.com/wxpay.html</notify_url>
|
||||
<contract_id>Wx15463511252015071056489715</contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> 走 HMAC-SHA256 时,请求体里**必须额外加 `<sign_type>HMAC-SHA256</sign_type>`** 并参与签名(`sign_type` 也是普通字段,按 ASCII 字典序入串)。
|
||||
|
||||
### 2.5 nonce_str / timestamp 要求
|
||||
|
||||
- `nonce_str`:32 字符以内,建议字母 + 数字随机串;用于防重放,每次请求重新生成。
|
||||
- `timestamp`(如有):10 位 UNIX 秒;服务器与微信侧时差大于约 5 分钟会被拒;**长度必须 ≥ 9 位**(< 9 位被网关拒)。
|
||||
- `request_serial`(部分签约接口必传):int64,**禁止以 0 开头**,仅用排序不用作查询条件。
|
||||
|
||||
## 三、V2 响应 / 回调验签(与请求签名同一算法)
|
||||
|
||||
委托代扣 V2 异步回调(签约结果 / 解约结果 / 扣款结果 / 退款结果)和 V2 请求的同步响应都走 **同一套 MD5 / HMAC-SHA256 算法**,密钥仍是**服务商 APIv2 密钥**(即使回调里带了 `sub_mch_id`,验签密钥也不是子商户的),只是验签方向反过来:
|
||||
|
||||
1. 解析 XML,取出 `<sign>` 字段,**剩余字段全部入签名串**(含微信新增的扩展字段,含 `sign_type`,含 `sub_mch_id` / `sub_appid` / `sub_openid` 等服务商专属字段)。
|
||||
2. 按本文 §2.1 步骤重算 sign(密钥仍是**服务商** APIv2 密钥)。
|
||||
3. **大小写不敏感比对**计算结果与报文中的 `sign` 是否相等;不等即视为伪造/被篡改,丢弃报文。
|
||||
4. **退款回调例外**:退款结果通知报文中的 `req_info` 字段是 **AES-256-ECB 加密的 base64 字符串**,需用 **服务商 APIv2 密钥的 MD5(小写 32 位)** 作为 AES key 解密后才能拿到业务字段;解密后的内容仍是 XML,但**不再参与 V2 sign 验签**(外层报文本身不带 `sign`)。
|
||||
5. **多商户路由**:`notify_url` 由服务商统一接收,业务侧按报文中的 `sub_mch_id`(或 `sub_appid` + `sub_openid`)路由到对应子商户;**路由前必须先验签**,避免按伪造的 sub_mch_id 误派发。
|
||||
|
||||
### 3.1 静态扫描判定(接入质检用)
|
||||
|
||||
| 场景 | 第一步 定位 | 第二步 检查 | 判为"未实现" |
|
||||
| --- | --- | --- | --- |
|
||||
| **V2 同步响应验签** | 找发 V2 接口请求并解析响应 XML 的代码 | `return_code=SUCCESS` 分支中是否对响应再做一次 V2 sign 校验 | 收到 SUCCESS 直接读业务字段,无任何 sign 重算逻辑 |
|
||||
| **V2 异步回调验签** | 找处理签约 / 扣款 / 解约回调的 controller / handler | 处理业务前是否对回调 XML 重算 V2 sign 比对 | 直接读字段、写库、回 SUCCESS,没有 sign 校验 |
|
||||
| **退款回调解密** | 找处理 `/refund-notify` 回调的代码 | 是否用 `MD5(服务商APIv2密钥)` 解密 `req_info` | 跳过解密直接 base64 → 读 XML 解析;或用 APIv3 密钥解密;或用子商户密钥解密 |
|
||||
| **服务商路由** | 找按 `sub_mch_id` 派发业务处理的代码 | 路由动作是否在 sign 校验通过之后 | 先按 `sub_mch_id` 派发再验签(顺序错) |
|
||||
|
||||
⚠️ 自研封装方法(如 `parseXmlAndVerify`)需要打开看内部是否有真验签,不要被命名骗了。
|
||||
|
||||
## 四、V2 调起客户端支付签名
|
||||
|
||||
> 服务商模式下委托代扣**不涉及客户端 paySign**:纯签约通过 URL/H5/SDK 直接跳转或调起,签约接口返回的是 URL(非 prepay_id),无需算调起签名。
|
||||
>
|
||||
> 若服务商业务还涉及**基础支付的服务商下单**(与委托代扣无关),其调起支付签名规则请见对应基础支付的签名文档;本文不展开。
|
||||
|
||||
## 五、V3 例外:预扣费通知 `/v3/partner-papay/contracts/{contract_id}/notify`
|
||||
|
||||
> ⚠️ 整个委托代扣里**仅此一个**接口走 V3。其余接口请回到 §二、§三。
|
||||
|
||||
### 5.1 V3 请求签名(5 行格式)
|
||||
|
||||
```
|
||||
HTTP请求方法\n
|
||||
URL\n
|
||||
请求时间戳\n
|
||||
请求随机串\n
|
||||
请求报文主体\n
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
1. 严格 5 行,每行末尾必须有 `\n`(含最后一行)。
|
||||
2. URL 是**去掉域名的绝对路径**(不含 `https://api.mch.weixin.qq.com`),含已替换的 `{contract_id}`,路径段**含 `partner-papay`**。
|
||||
3. 签名时用 **服务商 API 证书私钥** 做 SHA256 with RSA。
|
||||
4. 时间戳为系统当前 UNIX 秒。
|
||||
5. 签名时的请求体与实际发送的请求体**字节级一致**。
|
||||
|
||||
完整示例:
|
||||
|
||||
```
|
||||
POST\n
|
||||
/v3/partner-papay/contracts/Wx15463511252015071056489715/notify\n
|
||||
1554208460\n
|
||||
593BEC0C930BF1AFEB40B4A08C8FB242\n
|
||||
{"sp_mchid":"1230000109","sp_appid":"wxd678efh567hg6787","sub_mchid":"1900000109","sub_appid":"wx8888888888888888","deduct_duration":{"count":1,"unit":"DAY"},"estimated_amount":{"amount":1,"currency":"CNY"}}\n
|
||||
```
|
||||
|
||||
> ⚠️ 服务商模式 V3 字段是 **`sp_mchid` + `sp_appid` + `sub_mchid`(+ 可选 `sub_appid`)**,不要照搬商户模式的 `mchid` + `appid` 字段名。
|
||||
|
||||
### 5.2 V3 Authorization 头
|
||||
|
||||
```
|
||||
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1230000109",nonce_str="<随机串>",signature="<签名值>",timestamp="<时间戳>",serial_no="<服务商API证书序列号>"
|
||||
```
|
||||
|
||||
| 字段 | 取值 |
|
||||
| --- | --- |
|
||||
| `mchid` | **服务商号**(与请求体里的 `sp_mchid` 一致) |
|
||||
| `nonce_str` / `timestamp` | 与签名串中字节级一致 |
|
||||
| `signature` | §5.1 算出的签名值 |
|
||||
| `serial_no` | **服务商 API 证书序列号**(不是平台证书序列号、不是子商户证书序列号) |
|
||||
|
||||
### 5.3 V3 响应验签(3 行格式)
|
||||
|
||||
预扣费通知接口正常返回 HTTP 204 无 body,理论无需验签;但若返回 5XX/4XX 含 body,则需验签:
|
||||
|
||||
1. 取 4 个签名头:`Wechatpay-Timestamp` / `Wechatpay-Nonce` / `Wechatpay-Signature` / `Wechatpay-Serial`。
|
||||
2. 构造验签串(**3 行**,每行 `\n` 结尾):
|
||||
|
||||
```
|
||||
应答时间戳\n
|
||||
应答随机串\n
|
||||
应答报文主体\n
|
||||
```
|
||||
|
||||
3. 使用 **微信支付公钥**(推荐,`PUB_KEY_ID_xxxxxx`)或 **微信支付平台证书** 做 SHA256 with RSA 验证。
|
||||
4. 校验 `Wechatpay-Serial` 与本地持有的微信支付公钥 ID / 平台证书序列号匹配。
|
||||
|
||||
## 六、幂等与并发控制
|
||||
|
||||
V2 / V3 回调都可能多次到达,业务**必须能正确处理重复**。服务商场景下**幂等键里务必带上 `sub_mch_id`**,避免不同子商户的同名单号互相干扰。
|
||||
|
||||
**推荐做法**:① 收到后先按业务唯一标识查询是否已处理 → ② 未处理则进入业务流程(**前置加锁**)→ ③ 已处理则直接返回成功。
|
||||
|
||||
| 回调类型 | 业务唯一标识(幂等键) |
|
||||
| --- | --- |
|
||||
| 签约 / 解约结果通知 | `sub_mch_id` + `contract_id`(或 `sub_mch_id` + `plan_id` + `contract_code`) |
|
||||
| 扣款结果通知 | `sub_mch_id` + `out_trade_no`(或 `transaction_id`,全网唯一无需带 sub) |
|
||||
| 退款结果通知 | `sub_mch_id` + `out_refund_no`(或 `refund_id`) |
|
||||
|
||||
**并发锁选型**:Redis 分布式锁(`SETNX` / `RedisLock`)、数据库行锁(`SELECT ... FOR UPDATE`)、`synchronized` / `ReentrantLock` 等。锁粒度建议 = 业务唯一标识。
|
||||
|
||||
**静态扫描判定**:回调处理代码中**既无去重查询逻辑也无加锁逻辑**,或幂等键漏了 `sub_mch_id` → 判定未实现。
|
||||
|
||||
## 七、探测流量处理
|
||||
|
||||
微信支付会定期发送探测流量验证商户系统的连通性和验签逻辑:
|
||||
|
||||
- V3 探测请求的 `Wechatpay-Signature` 以 `WECHATPAY/SIGNTEST/` 为前缀。
|
||||
- 商户系统**不应做特殊跳过**,应当作正常响应/回调进行验签处理(V2 探测同样按正常 sign 校验流程走)。
|
||||
- 排查时可通过该前缀快速识别探测流量。
|
||||
- 若对探测流量返回错误,微信支付可能判定回调地址不可用。
|
||||
|
||||
## 八、401 / SIGN_ERROR 自查清单
|
||||
|
||||
按顺序逐项确认(无需提供私钥文件):
|
||||
|
||||
1. **协议版本对了吗?** —— V2 接口请用 MD5/HMAC-SHA256 + 服务商 APIv2 密钥;只有「预扣费通知」一个接口走 V3。**别把 5 行 RSA 签名套到 V2 接口上**。
|
||||
2. **密钥/证书用对了吗?** —— V2 用**服务商 APIv2 密钥**(不是子商户的,不是 APIv3 密钥);V3 用**服务商 API 证书私钥**;退款 / V3 用**服务商 API 证书**(不是子商户证书)。商户平台 → 合作伙伴 → 账户中心 → API 安全里取的才对;密钥重置后**新旧并存 15 天**,期间未同步代码 = 验签失败。
|
||||
3. **路径对了吗?** —— 申请扣款 `/pay/partner/pappayapply`、签约 `/papay/partner/...`、预扣费通知 `/v3/partner-papay/contracts/{contract_id}/notify`;解约 `/papay/deletecontract`、退款 `/secapi/pay/refund`、查单 `/pay/orderquery`、查退款 `/pay/refundquery`、关单 `/pay/closeorder`、账单 `/pay/downloadbill` 与商户模式同路径但请求体多 `sub_mch_id`。
|
||||
4. **签名串顺序**:是否按**参数名 ASCII 字典序**排序,不是字段定义顺序、不是字母序;`sub_mch_id` / `sub_appid` 也按字典序入串。
|
||||
5. **空值字段是否漏跳**:值为空 / null / 未传字段不入串;`sub_appid` 未绑定时不传则不入串;**`sign` 字段本身也不入串**。
|
||||
6. **大小写**:参数名区分大小写;MD5 / HMAC-SHA256 结果**全部转大写**。
|
||||
7. **`notify_url` 编码**:签名用 encode 前的原值;URL 中传输用 encode 后的值;encode 转义符(`%2F` `%3A`)必须**大写**;不要双重 encode。
|
||||
8. **`sign_type` 一致**:用了 HMAC-SHA256 时务必在请求体加 `<sign_type>HMAC-SHA256</sign_type>` 并参与签名。
|
||||
9. **响应/回调验签**:收到响应/回调后必须按报文里的 `sign_type` 重算并比对,不要直接相信 `return_code=SUCCESS`;新字段也要带进签名串;**先验签再按 `sub_mch_id` 路由**。
|
||||
10. **退款回调解密**:`req_info` 用 **MD5(服务商 APIv2 密钥) 小写 32 位** 作为 AES-256-ECB 的 key,**不是服务商 APIv2 密钥本身**,**不是 APIv3 密钥**,**不是子商户密钥**。
|
||||
11. **字符编码**:UTF-8 1-3 字节子集,不要传 emoji / 4 字节字符(典型踩雷:`contract_display_account` 直接传微信昵称)。
|
||||
12. **request_serial / timestamp**:纯数字,**禁止以 0 开头**,长度 ≥ 9 位。
|
||||
13. **V3 例外(仅预扣费通知)**:5 行 `\n` 含末行;URL 去域名且 `{contract_id}` 已替换、路径含 `partner-papay`;请求体字段是 `sp_mchid` + `sp_appid` + `sub_mchid`(不是 `mchid` + `appid`);Authorization 头里 `mchid="<服务商号>"`;`serial_no` 是**服务商 API 证书序列号**。
|
||||
14. **支付中签约**:服务商模式不支持,若代码里出现 `pay/contractorder` 或服务商版的客户端 paySign 计算 → 协议版本/产品形态用错,回到「纯签约 + 申请扣款」流程。
|
||||
15. 仍排查不出 → 用 [V2 在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md) 把签名原串和最终 sign 一一比对,定位是字段顺序、大小写、密钥、还是字段缺失/多余的问题。
|
||||
@@ -0,0 +1,106 @@
|
||||
# APP 纯签约(服务商)
|
||||
|
||||
> **来源**:[服务商-APP纯签约](https://pay.weixin.qq.com/doc/v2/partner/4011988366.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:HMAC-SHA256 / MD5(默认 MD5)
|
||||
> **是否需要证书**:否
|
||||
|
||||
外部 APP 拉起微信客户端发起签约前,需先后台调用预签约接口完成预签约,获取 `pre_entrustweb_id`,再拉起微信客户端,完成签约,返回 APP。
|
||||
|
||||
> 该业务接口需要单独申请,详情查看:[委托代扣-App&H5纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AJ8AyQbhAD4CN41Af5nZSQvqQrvcM)
|
||||
|
||||
---
|
||||
|
||||
## 步骤 1:预签约接口
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/partner/preentrustweb` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
| 签名方式 | HMAC-SHA256 / MD5(默认 MD5) |
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<mch_id>1200009811</mch_id>
|
||||
<sub_appid>wxcbda96de0b165489</sub_appid>
|
||||
<sub_mch_id>1900000109</sub_mch_id>
|
||||
<plan_id>12535</plan_id>
|
||||
<contract_code>100000</contract_code>
|
||||
<request_serial>1000</request_serial>
|
||||
<contract_display_account>微信代扣</contract_display_account>
|
||||
<notify_url>https://weixin.qq.com</notify_url>
|
||||
<version>1.0</version>
|
||||
<sign_type>MD5</sign_type>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
<timestamp>1414488825</timestamp>
|
||||
<return_app>Y</return_app>
|
||||
</xml>
|
||||
```
|
||||
|
||||
### 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<mch_id><![CDATA[10000098]]></mch_id>
|
||||
<sub_appid><![CDATA[wxcbda96de0b165489]]></sub_appid>
|
||||
<sub_mch_id><![CDATA[1900000109]]></sub_mch_id>
|
||||
<miniprogram_username><![CDATA[gh_xxxxxxxxxxxxx]]></miniprogram_username>
|
||||
<miniprogram_path><![CDATA[pages/index/index?sign_scene=app&domain_type=cn&pre_entrustweb_id=xxxxxxxxxx]]></miniprogram_path>
|
||||
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
|
||||
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
|
||||
<pre_entrustweb_id><![CDATA[5778aadY9nltAsZzXixCkFIGYnV2V]]></pre_entrustweb_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> `miniprogram_username` / `miniprogram_path` 仅以下情况返回:
|
||||
> 1. 模板 ID 为 2025-09-23 及之后申请的模板
|
||||
> 2. 模板 ID 为 2025-09-23 之前申请且申请了 WXLaunchMiniProgram 权限
|
||||
>
|
||||
> 若返回,可使用 LaunchMiniProgram SDK 签约(推荐,详见 [APP调起签约](./APP调起签约.md));若未返回则只能用 OpenBusinessWebview。
|
||||
|
||||
---
|
||||
|
||||
## 步骤 2:签约接口(客户端 OpenSDK 调用)
|
||||
|
||||
> ‼️ OpenBusinessWebview 仅支持 2025-09-22 之前申请过权限的商户在存量模板使用。**新增模板请用 WXLaunchMiniProgram**。
|
||||
|
||||
预签约 ID(`pre_entrustweb_id`)从步骤 1 获取。
|
||||
|
||||
### iOS(来源:官方文档原文)
|
||||
|
||||
```objectivec
|
||||
WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init];
|
||||
req.businessType =12; //固定值
|
||||
NSMutableDictionary *queryInfoDic = [NSMutableDictionary dictionary];
|
||||
[queryInfoDic setObject:"5778aadY9nltAsZzXixCkFIGYnV2V" forKey:"pre_entrustweb_id"];
|
||||
req.queryInfoDic = queryInfoDic;
|
||||
[WxApi sendReq:req];
|
||||
```
|
||||
|
||||
### Android(来源:官方文档原文)
|
||||
|
||||
```java
|
||||
WXOpenBusinessWebview.Req req = new WXOpenBusinessWebview.Req();
|
||||
req.businessType = 12;//固定值
|
||||
HashMap queryInfo = new HashMap<>();
|
||||
queryInfo.put("pre_entrustweb_id","5778aadY9nltAsZzXixCkFIGYnV2V");
|
||||
req.queryInfo = queryInfo;
|
||||
api.sendReq(req);
|
||||
```
|
||||
|
||||
### 返回参数
|
||||
|
||||
`WXOpenBusinessWebview.Resp`,**返回参数内容无需关注**。如果签约成功,商户系统会收到 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988366.md
|
||||
@@ -0,0 +1,58 @@
|
||||
# APP 调起签约(服务商)
|
||||
|
||||
> **来源**:[服务商-APP调起签约 - WXLaunchMiniProgram](https://pay.weixin.qq.com/doc/v2/partner/4016049199.md)
|
||||
> **接口名称**:`WXLaunchMiniProgram`
|
||||
> **接口类型**:客户端 OpenSDK 调用(无服务端 HTTP 接口)
|
||||
|
||||
商户在完成"委托代扣 APP 预签约"后,可在移动端 APP 集成 OpenSDK 调起微信。
|
||||
|
||||
## 接入前注意
|
||||
|
||||
1. 该能力依赖微信 [Open SDK](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Pay/Vendor_Service_Center.html)
|
||||
2. 在使用 WXLaunchMiniProgram 调起 APP 签约前需要邮件申请,参考[委托代扣-APP纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AdgAIgbzAB8CNJOvTlNjRTpGQ88aj)
|
||||
3. **预签约成功后首次调起微信有严格时间限制**:需在 2 分钟内调起微信(最短可能缩短至 20 秒)
|
||||
|
||||
## 平台要求
|
||||
|
||||
| 平台 | 最低 SDK 版本 | 资源下载 |
|
||||
|---|---|---|
|
||||
| Android | >=5.3.1 | [Android资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html) |
|
||||
| iOS | >=1.8.4 | [iOS资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/iOS_Resource.html) |
|
||||
| 鸿蒙 | - | [鸿蒙资源下载](https://developers.weixin.qq.com/doc/oplatform/Downloads/HarmonyOS_Resource.html) |
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 名称 | 变量 | 必填 | 类型 | 示例 | 描述 |
|
||||
|---|---|---|---|---|---|
|
||||
| 跳转签约小程序的 username | `userName` | 是 | String(32) | `gh_xxxxxxxxxxxxx` | 从预签约接口的返回参数 `miniprogram_username` 获取 |
|
||||
| 跳转签约小程序的 path | `path` | 是 | String(256) | `pages/index/index?xxxxxx` | 从预签约接口的返回参数 `miniprogram_path` 获取 |
|
||||
| 跳转的小程序版本 | `miniprogramType` | 是 | Integer | `0` | 固定传值为 0(正式版) |
|
||||
|
||||
## 客户端调用示例
|
||||
|
||||
官方提供以下平台开发示例(外链):
|
||||
|
||||
- [Android 开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/Android_Development_example.html)
|
||||
- [iOS 开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/iOS_Development_example.html)
|
||||
- [鸿蒙开发示例](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Launching_a_Mini_Program/OHOS_Development_example.html)
|
||||
|
||||
## 返回参数
|
||||
|
||||
返回参数内容无需关注。签约结果通过 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md) 异步推送。
|
||||
|
||||
---
|
||||
|
||||
## OpenBusinessWebview vs WXLaunchMiniProgram
|
||||
|
||||
| 对比项 | OpenBusinessWebview | WXLaunchMiniProgram |
|
||||
|---|---|---|
|
||||
| 跳转微信耗时 | 中等 | 更短 |
|
||||
| 用户体验 | 中等 | 更好 |
|
||||
| 支持鸿蒙 | 否 | 是 |
|
||||
| 新增模板 | 禁用 | 邮件申请后可用 |
|
||||
|
||||
OpenBusinessWebview 调用方式参见 [APP纯签约 - 步骤2](./APP纯签约.md#步骤-2签约接口客户端-opensdk-调用)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4016049199.md
|
||||
@@ -0,0 +1,49 @@
|
||||
# H5 纯签约(服务商)
|
||||
|
||||
> **来源**:[服务商-H5纯签约](https://pay.weixin.qq.com/doc/v2/partner/4011988368.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:HMAC-SHA256 / MD5(**默认 HMAC-SHA256**)
|
||||
> **是否需要证书**:否
|
||||
|
||||
适用于手机、平板等使用 H5 浏览器的设备场景。
|
||||
|
||||
> ‼️ 该业务接口需要单独申请,详情查看:[委托代扣-App&H5纯签约申请流程](https://doc.weixin.qq.com/doc/w3_AJ8AyQbhAD4CN41Af5nZSQvqQrvcM)
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/partner/h5entrustweb` |
|
||||
| 请求方式 | GET |
|
||||
|
||||
注意:
|
||||
1. 调用此接口后获得 `redirect_url`,需要在前端跳转
|
||||
2. 不能使用 `window.location.replace(redirect_url)`
|
||||
3. `clientip` 必填(用户客户端的 IP,IPv4/IPv6 都支持)
|
||||
|
||||
## 服务商专属可选风控参数
|
||||
|
||||
`deviceid` / `mobile` / `email` / `qq` / `openid` / `creid` / `outerid`——非必填,用于风控,建议商户填写以提高风险控制能力。
|
||||
|
||||
## 请求示例(URL,来源:官方文档原文)
|
||||
|
||||
```
|
||||
https://api.mch.weixin.qq.com/papay/partner/h5entrustweb?appid=wx426a3015555a46be&contract_code=122&contract_display_account=name1&mch_id=1223816102&sub_mch_id=1223816103¬ify_url=www.qq.com%2Ftest%2Fpapay&plan_id=106&request_serial=123&return_appid=wxcbda96de0b165542&clientip=12.1.1.12×tamp=1414488825&version=1.0&sign=130C7B07DD3B8074F7BF8BEF5C9A86487A1C57478F8C55587876B9C782F72036
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[PARAM_ERROR]]></return_msg>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<result_msg><![CDATA[SIGN_ERROR]]></result_msg>
|
||||
<redirect_url><![CDATA[https://payapp.weixin.qq.com]]></redirect_url>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988368.md
|
||||
@@ -0,0 +1,45 @@
|
||||
# 公众号纯签约(服务商)
|
||||
|
||||
> **来源**:[服务商-公众号纯签约](https://pay.weixin.qq.com/doc/v2/partner/4011988365.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:MD5
|
||||
> **是否需要证书**:否
|
||||
|
||||
商户可以通过请求此接口唤起微信委托代扣的页面。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/partner/entrustweb` |
|
||||
| 请求方式 | GET |
|
||||
|
||||
注意:
|
||||
1. 商户需要在前端跳转
|
||||
2. 不能使用 `window.location.replace(redirect_url)` 跳转,要通过 `window.location.href = redirect_url`
|
||||
|
||||
## 关键参数(服务商专属)
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|---|---|---|
|
||||
| `appid` | 是 | **服务商**申请的公众号或移动应用 appid |
|
||||
| `mch_id` | 是 | **服务商**商户号 |
|
||||
| `sub_appid` | 否 | 子商户号绑定的 appid(如需操作,需要服务商在商户平台为子商户绑定) |
|
||||
| `sub_mch_id` | 是 | 微信支付分配的子商户号 |
|
||||
|
||||
`notify_url` 需要 URL encode 处理。
|
||||
|
||||
## 请求示例(URL,来源:官方文档原文)
|
||||
|
||||
```
|
||||
https://api.mch.weixin.qq.com/papay/partner/entrustweb?appid=wx426a3015555a46be&contract_code=122&contract_display_account=name1&mch_id=1223816102&sub_mch_id=1223816103¬ify_url=www.qq.com%2Ftest%2Fpapay&plan_id=106&request_serial=123×tamp=1414488825&version=1.0&sign=FF1A406564EE701064450CA2149E2514
|
||||
```
|
||||
|
||||
## 响应
|
||||
|
||||
无 XML 响应。微信会唤起签约页面,签约结果通过 [签约/解约结果通知](../7-异步结果回调/签约-解约结果通知.md) 异步通知 `notify_url`。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988365.md
|
||||
@@ -0,0 +1,86 @@
|
||||
# 小程序纯签约(服务商)
|
||||
|
||||
> **来源**:[服务商-小程序纯签约](https://pay.weixin.qq.com/doc/v2/partner/4011988367.md)
|
||||
> **协议版本**:客户端 SDK + V2 签名
|
||||
> **签名方式**:MD5
|
||||
|
||||
商户可以通过请求此接口唤起小程序委托代扣的签约页面。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求方式 | `wx.navigateToMiniProgram(OBJECT)` |
|
||||
| 接口兼容 | iOS 微信客户端 6.5.9 起支持,Android 6.5.10 起支持 |
|
||||
| 微信签约小程序 appId | `wxbd687630cd02ce1d`(固定值) |
|
||||
|
||||
> 在小程序配置文件 `app.json` 的 `navigateToMiniProgramAppIdList` 中需要增加此 appid。
|
||||
|
||||
## 请求示例(来源:官方文档原文)
|
||||
|
||||
```javascript
|
||||
wx.navigateToMiniProgram({
|
||||
appId:'wxbd687630cd02ce1d',
|
||||
path:'pages/index/index',
|
||||
extraData:{
|
||||
appid:'wx426a3015555a46be',
|
||||
contract_code:'122',
|
||||
contract_display_account:'张三',
|
||||
mch_id:'1223816102',
|
||||
sub_mch_id:'1900000109',
|
||||
notify_url:'https://www.qq.com/test/papay',
|
||||
plan_id:'106',
|
||||
request_serial:123,
|
||||
timestamp:1414488825,
|
||||
sign:'FF1A406564EE701064450CA2149E2514'
|
||||
},
|
||||
success(res) {
|
||||
// 成功跳转到签约小程序
|
||||
},
|
||||
fail(res) {
|
||||
// 未成功跳转到签约小程序
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 接收签约结果(来源:官方文档原文)
|
||||
|
||||
```javascript
|
||||
App({
|
||||
onShow(res) {
|
||||
if (res.scene === 1038) { // 场景值1038:从被打开的小程序返回
|
||||
const { appId, extraData } = res.referrerInfo
|
||||
if (appId == 'wxbd687630cd02ce1d') { // appId为wxbd687630cd02ce1d:从签约小程序跳转回来
|
||||
if (typeof extraData == 'undefined'){
|
||||
// TODO
|
||||
// 客户端小程序不确定签约结果,需要向商户侧后台请求确定签约结果
|
||||
return;
|
||||
}
|
||||
if(extraData.return_code == 'SUCCESS'){
|
||||
// TODO
|
||||
// 客户端小程序签约成功,需要向商户侧后台请求确认签约结果
|
||||
var contract_id = extraData.contract_id
|
||||
return;
|
||||
} else {
|
||||
// TODO
|
||||
// 签约失败
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## referrerInfo.extraData 字段
|
||||
|
||||
| 参数 | 变量 | 说明 |
|
||||
|---|---|---|
|
||||
| 返回码 | `return_code` | `SUCCESS`:签约成功 / `FAIL`:签约失败 |
|
||||
| 错误信息 | `return_msg` | 签约失败的错误信息 |
|
||||
| 委托代扣协议 id | `contract_id` | 签约成功后微信返回的协议 id |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988367.md
|
||||
@@ -0,0 +1,74 @@
|
||||
# 申请扣款(服务商)
|
||||
|
||||
> **来源**:[服务商-申请扣款](https://pay.weixin.qq.com/doc/v2/partner/4011988372.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
> **请求频率限制**:150qps
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/partner/pappayapply` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 关键业务规则
|
||||
|
||||
- 周期扣费场景:扣费前需要先调用 [预扣费通知](./预扣费通知.md) 为用户下发扣费提醒
|
||||
- **首次签约 12 小时内**的扣款会被立即执行(无延迟);超过 12 小时按预扣费通知规则执行
|
||||
- **同一个商户号 + 同一个商户订单号,只会扣款一次**
|
||||
- 周期扣费为预扣费通知方式时,需在北京时间每天 7:00 ~ 22:00 发起扣款
|
||||
- `total_fee` 单位为分(整数)
|
||||
- `trade_type` 固定为 `PAP`
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
<body>水电代扣</body>
|
||||
<out_trade_no>217752501201407033233368018</out_trade_no>
|
||||
<total_fee>888</total_fee>
|
||||
<trade_type>PAP</trade_type>
|
||||
<spbill_create_ip>8.8.8.8</spbill_create_ip>
|
||||
<notify_url>http://yoursite.com/wxpay.html</notify_url>
|
||||
<contract_id>Wx15463511252015071056489715</contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<mch_id><![CDATA[10000098]]></mch_id>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
|
||||
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> ⚠️ `result_code=SUCCESS` 仅代表请求受理成功,实际扣款结果通过 [扣款结果通知](../7-异步结果回调/扣款结果通知.md) 异步推送。
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `CONTRACT_NOT_EXIST` | 签约协议不存在 |
|
||||
| `ORDERPAID` | 订单已支付 |
|
||||
| `CONTRACTERROR` | 协议已过期 |
|
||||
| `RULELIMIT` | 该笔交易存在风险 |
|
||||
| `FREQUENCY_LIMITED` | 频率限制 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988372.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# 预扣费通知 API(服务商)
|
||||
|
||||
> **来源**:[服务商-预扣费通知 API](https://pay.weixin.qq.com/doc/v2/partner/4011988373.md)
|
||||
> **协议版本**:⚠️ **API V3**(JSON + SHA256-RSA2048)
|
||||
> **接口请求方**:具有委托代扣扣费权限,且**另行开通了预扣费通知权限**的服务商
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/v3/partner-papay/contracts/{contract_id}/notify` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | JSON |
|
||||
| 接口规则 | [V3 接口规则概述](https://pay.weixin.qq.com/doc/v3/partner/4012081673.md) |
|
||||
|
||||
> ⚠️ **注意路径**:服务商版是 `/v3/partner-papay/...`(不是 `/v3/papay/partner/...`)
|
||||
|
||||
## 时间窗口规则
|
||||
|
||||
| 概念 | 说明 |
|
||||
|---|---|
|
||||
| 扣费等待期 | 商户调用本接口成功当日 + 第二个自然日 |
|
||||
| 扣费持续天数 | 默认 7 天 |
|
||||
| 可通知时间段 | 北京时间每天 7:00 ~ 22:00 |
|
||||
|
||||
举例:扣费持续天数为 1 时,1 号下发通知,2 号是扣费等待期,3 号才能正常扣款。
|
||||
|
||||
## 请求示例(JSON,来源:官方文档原文)
|
||||
|
||||
```json
|
||||
{
|
||||
"sp_appid" : "wxd678efh567hg6787",
|
||||
"sp_mchid" : "1230000109",
|
||||
"deduct_duration" : {
|
||||
"count" : 1,
|
||||
"unit" : "DAY"
|
||||
},
|
||||
"estimated_amount" : {
|
||||
"amount" : 1,
|
||||
"currency" : "CNY"
|
||||
},
|
||||
"sub_mchid" : "12345512",
|
||||
"sub_appid" : "wxd678efh567hg6787"
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `sp_mchid`:服务商商户号(V3 命名风格无下划线)
|
||||
- `sp_appid`:服务商 APPID
|
||||
- `sub_mchid`:子商户号
|
||||
- `sub_appid`:子商户 APPID(可选)
|
||||
- `deduct_duration.count`:扣费持续时间数(1~3 天)
|
||||
- `deduct_duration.unit`:固定 `DAY`
|
||||
- `estimated_amount.amount`:预计扣款金额(**分**,整数)
|
||||
- `estimated_amount.currency`:默认 `CNY`
|
||||
|
||||
## 响应示例
|
||||
|
||||
```
|
||||
无数据(HTTP 状态码为 204)
|
||||
```
|
||||
|
||||
## V3 签名要求
|
||||
|
||||
- 使用 V3 签名算法:SHA256-RSA2048
|
||||
- 5 行格式:`HTTP方法\nURL路径\n时间戳\n随机串\n请求体\n`
|
||||
- 用**服务商**的 API 私钥签名
|
||||
- `Authorization` 头格式:`WECHATPAY2-SHA256-RSA2048 mchid="..." nonce_str="..." signature="..." timestamp="..." serial_no="..."`
|
||||
|
||||
详见 [📄 服务商模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `CONTRACT_NOT_EXIST` | 签约协议不存在 |
|
||||
| `RESOURCE_ALREADY_EXISTS` | 资源已经存在(已成功发送通知) |
|
||||
| `RULELIMIT` | 该请求存在风险 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988373.md
|
||||
@@ -0,0 +1,85 @@
|
||||
# 申请解约(服务商)
|
||||
|
||||
> **来源**:[服务商-申请解约](https://pay.weixin.qq.com/doc/v2/partner/4011988374.md)
|
||||
> **协议版本**:API V2
|
||||
> **签名方式**:MD5
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/deletecontract` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 解约方式(任选其一)
|
||||
|
||||
1. **方式 1**:使用 `contract_id` 解约
|
||||
2. **方式 2**:使用 `plan_id + contract_code` 解约
|
||||
|
||||
---
|
||||
|
||||
## 方式 1:使用 contract_id 解约
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<sign>E1EE61A91C8E90F299DE6AE075D60A2D</sign>
|
||||
<contract_id>100005698</contract_id>
|
||||
<contract_termination_remark>原因</contract_termination_remark>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方式 2:使用 plan_id + contract_code 解约
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<mch_id>10000098</mch_id>
|
||||
<appid>wxcbda96de0b165486</appid>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<sign>E1EE61A91C8E90F299DE6AE075D60A2D</sign>
|
||||
<plan_id>12251</plan_id>
|
||||
<contract_code>1234</contract_code>
|
||||
<contract_termination_remark>原因</contract_termination_remark>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id><![CDATA[10010404]]></mch_id>
|
||||
<appid><![CDATA[wxcbda96de0b165486]]></appid>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<contract_id><![CDATA[100005698]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `SIGN_ERROR` | 签名错误 |
|
||||
| `PARAMETER FAIL` | 参数错误 |
|
||||
| `MERCHANT PERMISSION ERROR` | 商户没有权限 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988374.md
|
||||
@@ -0,0 +1,107 @@
|
||||
# 查询签约关系(服务商)
|
||||
|
||||
> **来源**:[服务商-查询签约关系](https://pay.weixin.qq.com/doc/v2/partner/4011988379.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
> **请求频率限制**:默认 300qps
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/papay/partner/querycontract` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 查询方式(任选其一)
|
||||
|
||||
1. **方式 1**:使用 `contract_id` 查询
|
||||
2. **方式 2**:使用 `plan_id + contract_code` 查询
|
||||
|
||||
---
|
||||
|
||||
## 方式 1:使用 contract_id 查询
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<sign>019C869758CC7F258C42F05CDB9EE361</sign>
|
||||
<mch_id>10000097</mch_id>
|
||||
<sub_mch_id>1900000109</sub_mch_id>
|
||||
<appid>wxf5b5e87a6a0fde94</appid>
|
||||
<contract_id>201509160000028648</contract_id>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方式 2:使用 plan_id + contract_code 查询
|
||||
|
||||
### 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<sign>019C869758CC7F258C42F05CDB9EE361</sign>
|
||||
<mch_id>10000097</mch_id>
|
||||
<sub_mch_id>1900000109</sub_mch_id>
|
||||
<appid>wxf5b5e87a6a0fde94</appid>
|
||||
<plan_id>123</plan_id>
|
||||
<contract_code>1023658866</contract_code>
|
||||
<version>1.0</version>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code> <![CDATA[SUCCESS]]> </return_code>
|
||||
<result_code> <![CDATA[SUCCESS]]> </result_code>
|
||||
<mch_id> <![CDATA[80000000]]> </mch_id>
|
||||
<appid> <![CDATA[wx426b3015555b46be]]> </appid>
|
||||
<sub_mch_id> <![CDATA[10010405]]> </sub_mch_id>
|
||||
<contract_id>203</contract_id>
|
||||
<plan_id>66</plan_id>
|
||||
<openid> <![CDATA[oHZx6uMbIG46UXQ3SKxVYEgw1LZs]]> </openid>
|
||||
<request_serial>123</request_serial>
|
||||
<contract_code> <![CDATA[1005]]> </contract_code>
|
||||
<contract_display_account> <![CDATA[test]]> </contract_display_account>
|
||||
<contract_state>1</contract_state>
|
||||
<contract_signed_time>2015-07-01 10:00:00</contract_signed_time>
|
||||
<contract_expired_time>2015-07-01 10:00:00</contract_expired_time>
|
||||
<contract_terminated_time>2015-07-01 10:00:00</contract_terminated_time>
|
||||
<contract_termination_mode>3</contract_termination_mode>
|
||||
<contract_termination_remark> <![CDATA[delete ....]]> </contract_termination_remark>
|
||||
<err_code>0</err_code>
|
||||
<err_code_des> <![CDATA[SUCCESS]]> </err_code_des>
|
||||
<sign> <![CDATA[8FC9DACB7DDF9B48333DCCC2224E0CAC]]> </sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## contract_state 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `0` | 已签约 |
|
||||
| `1` | 未签约 |
|
||||
| `9` | 签约进行中 |
|
||||
|
||||
## contract_termination_mode 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `1` | 有效期过自动解约(预留功能) |
|
||||
| `2` | 用户主动解约 |
|
||||
| `3` | 商户 API 解约 |
|
||||
| `4` | 商户平台解约 |
|
||||
| `5` | 注销(用户微信账户注销) |
|
||||
| `7` | 用户联系客服发起的解约 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988379.md
|
||||
@@ -0,0 +1,81 @@
|
||||
# 查询订单(服务商)
|
||||
|
||||
> **来源**:[服务商-查询订单](https://pay.weixin.qq.com/doc/v2/partner/4011988377.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/orderquery` |
|
||||
| 备用域名 | `https://api2.mch.weixin.qq.com/pay/orderquery` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 何时需要调用查询接口
|
||||
|
||||
- 商户后台、网络、服务器异常导致最终未接收到支付通知
|
||||
- 调用支付接口后返回系统错误或未知交易状态
|
||||
- 调用关单或撤销接口前,需确认支付状态
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<sub_appid>wx2421b1c4370ec43b</sub_appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<sub_mch_id>1900000109</sub_mch_id>
|
||||
<nonce_str>ec2316275641faa3aacf3cc599e8730f</nonce_str>
|
||||
<transaction_id>1008450740201411110005820873</transaction_id>
|
||||
<sign>FDD167FAA73459FD921B144BAF4F4CA2</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[1900000109]]></sub_mch_id>
|
||||
<device_info><![CDATA[1000]]></device_info>
|
||||
<nonce_str><![CDATA[TN55wO9Pba5yENl8]]></nonce_str>
|
||||
<sign><![CDATA[BDF0099C15FF7BC6B1585FBB110AB635]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<openid><![CDATA[oUpF8uN95-Ptaags6E_roPHg7AG0]]></openid>
|
||||
<is_subscribe><![CDATA[N]]></is_subscribe>
|
||||
<trade_type><![CDATA[MICROPAY]]></trade_type>
|
||||
<bank_type><![CDATA[CCB_DEBIT]]></bank_type>
|
||||
<total_fee>1</total_fee>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<attach><![CDATA[订单额外描述]]></attach>
|
||||
<time_end><![CDATA[20141111170043]]></time_end>
|
||||
<trade_state><![CDATA[SUCCESS]]></trade_state>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## trade_state 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `SUCCESS` | 支付成功 |
|
||||
| `REFUND` | 转入退款 |
|
||||
| `NOTPAY` | 未支付 |
|
||||
| `CLOSED` | 已关闭 |
|
||||
| `REVOKED` | 已撤销(刷卡支付) |
|
||||
| `USERPAYING` | 用户支付中 |
|
||||
| `PAYERROR` | 支付失败 |
|
||||
| `ACCEPT` | 已接收,等待扣款 |
|
||||
|
||||
> 委托代扣场景下 `trade_type` 为 `PAP`。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988377.md
|
||||
@@ -0,0 +1,73 @@
|
||||
# 查询退款(服务商)
|
||||
|
||||
> **来源**:[服务商-查询退款](https://pay.weixin.qq.com/doc/v2/partner/4011988382.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/refundquery` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 退款时效
|
||||
|
||||
- 用零钱支付的退款 20 分钟内到账
|
||||
- 银行卡支付的退款 3 个工作日后重新查询退款状态
|
||||
- 单个支付订单部分退款次数超过 20 次请使用退款单号查询
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<sub_mch_id>1900000109</sub_mch_id>
|
||||
<nonce_str>0b9f35f484df17a732e537c37708d1d0</nonce_str>
|
||||
<out_refund_no></out_refund_no>
|
||||
<out_trade_no>1415757673</out_trade_no>
|
||||
<refund_id></refund_id>
|
||||
<transaction_id></transaction_id>
|
||||
<sign>66FFB727015F450D167EF38CCC549521</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> `transaction_id` / `out_trade_no` / `out_refund_no` / `refund_id` 四选一。
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[1900000109]]></sub_mch_id>
|
||||
<nonce_str><![CDATA[TeqClE3i0mvn3DrK]]></nonce_str>
|
||||
<out_refund_no_0><![CDATA[1415701182]]></out_refund_no_0>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<refund_count>1</refund_count>
|
||||
<refund_fee_0>1</refund_fee_0>
|
||||
<refund_id_0><![CDATA[2008450740201411110000174436]]></refund_id_0>
|
||||
<refund_status_0><![CDATA[PROCESSING]]></refund_status_0>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<sign><![CDATA[1F2841558E233C33ABA71A961D27561C]]></sign>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## refund_status 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `SUCCESS` | 退款成功 |
|
||||
| `REFUNDCLOSE` | 退款关闭 |
|
||||
| `PROCESSING` | 退款处理中 |
|
||||
| `CHANGE` | 退款异常(需到商户平台手动处理) |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988382.md
|
||||
@@ -0,0 +1,74 @@
|
||||
# 申请退款(服务商)
|
||||
|
||||
> **来源**:[服务商-申请退款](https://pay.weixin.qq.com/doc/v2/partner/4011988381.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:✅ **需要双向证书**(apiclient_cert.p12,密码 = 服务商 mch_id)
|
||||
> **请求频率限制**:150qps
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/secapi/pay/refund` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 关键业务规则
|
||||
|
||||
1. 交易时间超过一年的订单无法提交退款
|
||||
2. 单笔交易支持分多次退款;申请退款总金额不能超过订单金额
|
||||
3. **重新提交不要更换退款单号**——使用原 `out_refund_no`
|
||||
4. 错误或无效请求频率限制:6qps
|
||||
5. 每个支付订单的部分退款次数不能超过 50 次
|
||||
6. 申请退款接口的返回**仅代表业务的受理情况**,具体结果通过 [查询退款](./查询退款.md) 获取
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str>
|
||||
<sub_mch_id>1415701182</sub_mch_id>
|
||||
<out_refund_no>1415701182</out_refund_no>
|
||||
<out_trade_no>1415757673</out_trade_no>
|
||||
<refund_fee>1</refund_fee>
|
||||
<total_fee>1</total_fee>
|
||||
<transaction_id>4006252001201705123297353072</transaction_id>
|
||||
<sign>FE56DD4AA85C0EECA82C35595A69E153</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<nonce_str><![CDATA[NfsMFbUFpdbEhPXP]]></nonce_str>
|
||||
<sign><![CDATA[B7274EB9F8925EB93100DD2085FA56C0]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<transaction_id><![CDATA[1008450740201411110005820873]]></transaction_id>
|
||||
<out_trade_no><![CDATA[1415757673]]></out_trade_no>
|
||||
<out_refund_no><![CDATA[1415701182]]></out_refund_no>
|
||||
<refund_id><![CDATA[2008450740201411110000174436]]></refund_id>
|
||||
<refund_fee>1</refund_fee>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 | 处理 |
|
||||
|---|---|---|
|
||||
| `SYSTEMERROR` | 接口返回错误 | **不要更换退款单号**,重试 |
|
||||
| `BIZERR_NEED_RETRY` | 退款业务流程错误 | 不要更换退款单号,重试 |
|
||||
| `TRADE_OVERDUE` | 订单已经超过退款期限 | 选择其他方式自行退款 |
|
||||
| `NOTENOUGH` | 余额不足 | 商户充值后重试 |
|
||||
| `CERT_ERROR` | 证书校验错误 | 检查 apiclient_cert.p12 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988381.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# 下载交易账单(服务商)
|
||||
|
||||
> **来源**:[服务商-下载交易账单](https://pay.weixin.qq.com/doc/v2/partner/4011988384.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/downloadbill` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML(请求)/ CSV 文本(响应成功)/ XML(响应失败) |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 微信侧未成功下单的交易不会出现在对账单中
|
||||
2. 微信在次日 9 点启动生成前一天的对账单,**建议商户 10 点后再获取**
|
||||
3. 对账单中**金额字段单位为"元"**(与请求接口的"分"不同)
|
||||
4. 对账单接口只能下载三个月以内的账单
|
||||
5. 对账单是以**商户号**纬度生成
|
||||
6. 如需下载指定子商户号对账单,传 `sub_mch_id` 参数(可选)
|
||||
|
||||
## bill_type 取值
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| `ALL` | 默认值,返回当日所有订单 |
|
||||
| `SUCCESS` | 当日成功支付的订单 |
|
||||
| `REFUND` | 当日退款订单 |
|
||||
| `RECHARGE_REFUND` | 当日充值退款订单 |
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx2421b1c4370ec43b</appid>
|
||||
<bill_date>20141110</bill_date>
|
||||
<bill_type>ALL</bill_type>
|
||||
<mch_id>10000100</mch_id>
|
||||
<nonce_str>21df7dc9cd8616b56919f20d9f679233</nonce_str>
|
||||
<sign>332F17B766FC787203EBE9D6E40457A1</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 响应
|
||||
|
||||
**成功时**:返回 CSV 文本表格,首行为表头,最后两行为统计行。
|
||||
|
||||
表头按 `bill_type` 不同:
|
||||
|
||||
- **ALL**:交易时间,应用ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
|
||||
- **SUCCESS**:交易时间,应用ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
|
||||
- **REFUND**:交易时间,应用ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
|
||||
|
||||
数据行各参数以逗号分隔,参数前增加 `\`` 符号。
|
||||
|
||||
**最后两行为统计行**:
|
||||
|
||||
```
|
||||
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
|
||||
`2,`0.02,`0.0,`0.0,`0
|
||||
```
|
||||
|
||||
**失败时**:返回 XML(含 `err_code`)。
|
||||
|
||||
## tar_type 可选值
|
||||
|
||||
| 值 | 说明 |
|
||||
|---|---|
|
||||
| `GZIP` | 返回 .gzip 压缩包账单 |
|
||||
| 不传 | 返回数据流形式 |
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `20002 NO Bill Exist` | 账单不存在 |
|
||||
| `20002 Bill Creating` | 账单未生成(10 点后再下载) |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988384.md
|
||||
@@ -0,0 +1,66 @@
|
||||
# 关闭订单(服务商)
|
||||
|
||||
> **来源**:[服务商-关闭订单](https://pay.weixin.qq.com/doc/v2/partner/4011988383.md)
|
||||
> **协议版本**:API V2
|
||||
> **是否需要证书**:否
|
||||
|
||||
## 应用场景
|
||||
|
||||
- 商户订单支付失败需要生成新单号重新发起支付时,对原订单号调用关单,避免重复支付
|
||||
- 系统下单后用户支付超时,系统退出不再受理时
|
||||
|
||||
> 注意:订单生成后**不能马上调用关单接口**,最短调用时间间隔为 5 分钟。
|
||||
|
||||
## 接口说明
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 适用对象 | 服务商 |
|
||||
| 请求 URL | `https://api.mch.weixin.qq.com/pay/closeorder` |
|
||||
| 备用域名 | `https://api2.mch.weixin.qq.com/pay/closeorder` |
|
||||
| 请求方式 | POST |
|
||||
| 数据格式 | XML |
|
||||
|
||||
## 请求示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid>wx8888888888888888</appid>
|
||||
<mch_id>10000100</mch_id>
|
||||
<sub_mch_id>wx8888888888888888</sub_mch_id>
|
||||
<nonce_str>5K8264ILTKCH16CQ2502SI8ZNMTM67VS</nonce_str>
|
||||
<out_trade_no>1217752501201407033233368018</out_trade_no>
|
||||
<sign>C380BEC2BFD727A4B6845133519F3AD6</sign>
|
||||
</xml>
|
||||
```
|
||||
|
||||
> 注:上方文档示例的 `<sub_mch_id>wx8888888888888888</sub_mch_id>` 是文档样例的笔误(写成了 appid 的值),实际 `sub_mch_id` 应该是数字商户号(如 `1900000109`)。
|
||||
|
||||
## 响应示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[1900000109]]></sub_mch_id>
|
||||
<nonce_str><![CDATA[BFK89FC6rxKCOjLX]]></nonce_str>
|
||||
<sign><![CDATA[72B321D92A7BFA0B2509F3D13C7B1631]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<result_msg><![CDATA[OK]]></result_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `SIGNERROR` | 签名错误 |
|
||||
| `PARAM_ERROR` | 参数错误 |
|
||||
| `APPID_NOT_EXIST` | APPID 不存在 |
|
||||
| `MCHID_NOT_EXIST` | MCHID 不存在 |
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988383.md
|
||||
@@ -0,0 +1,97 @@
|
||||
# 扣款结果通知(服务商)
|
||||
|
||||
> **来源**:[服务商-扣款结果通知](https://pay.weixin.qq.com/doc/v2/partner/4011988376.md)
|
||||
> **协议版本**:API V2(XML,**不加密**)
|
||||
> **触发**:[申请扣款](../2-代扣扣款/申请扣款.md) 受理后,扣款成功或失败时
|
||||
|
||||
## 通知规则
|
||||
|
||||
- 微信会把支付结果发送给商户在申请扣款接口中提交的 `notify_url`
|
||||
- **重试频率**:`0/15/15/30/180/1800/1800/1800/1800/3600`(单位:秒)
|
||||
- 如果在所有通知频率后没有收到回调,应主动调用 [查询订单](../4-订单协议查询/查询订单.md) 确认状态
|
||||
|
||||
## ⚠️ 特别提醒
|
||||
|
||||
1. 商户系统对通知内容**一定要做签名验证**,并校验返回的订单金额是否与商户侧的订单金额一致,防止"假通知"
|
||||
2. 同样的通知可能多次发送给商户系统,**必须做幂等处理**
|
||||
3. 针对 `err_code=SYSTEMERROR` 的订单:先查询订单,若 `trade_state` ≠ `CLOSED`,需先关闭订单,再换单号重新申请延迟扣费(24 小时后执行)
|
||||
4. **服务商专属:必须按 `sub_mch_id` 路由**,避免不同子商户回调串单
|
||||
|
||||
---
|
||||
|
||||
## 扣款成功通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<attach><![CDATA[支付测试]]></attach>
|
||||
<bank_type><![CDATA[CFT]]></bank_type>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<is_subscribe><![CDATA[N]]></is_subscribe>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
|
||||
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
|
||||
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
|
||||
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
|
||||
<cash_fee><![CDATA[1000]]></cash_fee>
|
||||
<trade_state><![CDATA[SUCCESS]]></trade_state>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
|
||||
<time_end><![CDATA[20140903131540]]></time_end>
|
||||
<total_fee>1</total_fee>
|
||||
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 扣款失败通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
|
||||
<mch_id><![CDATA[10000100]]></mch_id>
|
||||
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
|
||||
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
|
||||
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<bank_type><![CDATA[CFT]]></bank_type>
|
||||
<fee_type><![CDATA[CNY]]></fee_type>
|
||||
<is_subscribe><![CDATA[Y]]></is_subscribe>
|
||||
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 商户必须返回的应答(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 主要扣款失败错误码
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|---|---|
|
||||
| `ACCOUNTERROR` | 用户账户异常 |
|
||||
| `CONTRACT_NOT_EXIST` | 协议不存在,用户已解约 |
|
||||
| `RULELIMIT` | 用户账户支付已达上限 |
|
||||
| `BANKERROR` | 支付银行卡所在行渠道维护中 |
|
||||
| `NOTENOUGH` | 余额不足 |
|
||||
| `USER_ACCOUNT_ABNORMAL` | 用户账户异常 |
|
||||
| `USER_NOT_EXIST` | 用户账户注销 |
|
||||
| `TRADE_ERROR` | 订单错误 |
|
||||
|
||||
## 验签实现
|
||||
|
||||
参见 [📄 服务商模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988376.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# 签约/解约结果通知(服务商)
|
||||
|
||||
> **来源**:[服务商-签约、解约结果通知](https://pay.weixin.qq.com/doc/v2/partner/4011988378.md)
|
||||
> **协议版本**:API V2(XML,**不加密**)
|
||||
> **触发**:用户完成签约或解约
|
||||
|
||||
## 通知规则
|
||||
|
||||
- **签约结果通知路径**:签约接口商户上传的 `notify_url`
|
||||
- **解约结果通知路径**:商户配置委托扣款模板 ID 时填写的解约回调地址
|
||||
- **重试频率**:`0/10/10/10/30/30/30/300/300/...`(单位:秒,最多 30 次)
|
||||
- 如果未收到回调,应调用 [查询签约关系](../4-订单协议查询/查询签约关系.md) 确认
|
||||
|
||||
## ⚠️ 特别提醒
|
||||
|
||||
1. **必须做签名验证**,并校验商户协议号和用户 openid 信息一致,防止"假通知"
|
||||
2. **必须做幂等**——同样的通知可能多次发送
|
||||
3. 商户记录的签约用户 `openid` / `sub_openid`,需以**微信侧回调通知的为准**
|
||||
4. **服务商必须按 `sub_mch_id` 路由**,避免子商户串单
|
||||
|
||||
---
|
||||
|
||||
## 通知示例(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
<sign><![CDATA[C380BEC2BFD727A4B6845133519F3AD6]]></sign>
|
||||
<mch_id>10010404</mch_id>
|
||||
<sub_mch_id>10010405</sub_mch_id>
|
||||
<contract_code>100001256</contract_code>
|
||||
<openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6ua]]></openid>
|
||||
<plan_id><![CDATA[123]]></plan_id>
|
||||
<change_type><![CDATA[ADD]]></change_type>
|
||||
<operate_time><![CDATA[2015-07-01 10:00:00]]></operate_time>
|
||||
<contract_id><![CDATA[Wx15463511252015071056489715]]></contract_id>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## change_type 取值
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `ADD` | 签约 |
|
||||
| `DELETE` | 解约 |
|
||||
|
||||
## contract_termination_mode(仅 change_type=DELETE 时返回)
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `1` | 有效期过自动解约 |
|
||||
| `2` | 用户主动解约 |
|
||||
| `3` | 商户 API 解约 |
|
||||
| `4` | 商户平台解约 |
|
||||
| `5` | 注销(用户微信账户注销) |
|
||||
| `7` | 用户联系客服发起的解约 |
|
||||
|
||||
---
|
||||
|
||||
## 商户必须返回的应答(XML,来源:官方文档原文)
|
||||
|
||||
```xml
|
||||
<xml>
|
||||
<return_code><![CDATA[SUCCESS]]></return_code>
|
||||
<return_msg><![CDATA[OK]]></return_msg>
|
||||
</xml>
|
||||
```
|
||||
|
||||
## 微信支付签约/解约通知出口 IP
|
||||
|
||||
如商户侧配置了防火墙,需对回调通知功能开通下面白名单网段:
|
||||
|
||||
```
|
||||
101.226.233.128/25
|
||||
```
|
||||
|
||||
## 验签实现
|
||||
|
||||
参见 [📄 服务商模式签名与验签规则](../../接入指南/签名与验签规则.md)。
|
||||
|
||||
## 完整字段说明
|
||||
|
||||
请直接参考官方文档:https://pay.weixin.qq.com/doc/v2/partner/4011988378.md
|
||||
@@ -0,0 +1,88 @@
|
||||
# 服务商模式接口索引
|
||||
|
||||
> 委托代扣**官方文档未提供任何服务端代码示例**(Java / Go / PHP / Python 等服务端语言均无)。本 skill 直接收录每个接口在官方文档中真实存在的请求/响应报文样例(XML / URL / JSON)和客户端调起代码(iOS / Android / 小程序 JS / 鸿蒙),不做任何编造。
|
||||
>
|
||||
> 凡是官方提供示例代码的接口(APP纯签约步骤2 / 小程序纯签约 / APP调起签约),代码均**逐字摘录自官方文档**,并标注来源。
|
||||
|
||||
## ⚠️ 协议版本一览
|
||||
|
||||
| 接口分类 | 协议 | 数据格式 | 签名算法 |
|
||||
|---|---|---|---|
|
||||
| 业务主流程 | API V2 | XML | MD5 / HMAC-SHA256(用**服务商**APIv2 密钥) |
|
||||
| 预扣费通知 | **API V3** | JSON | SHA256-RSA2048(用**服务商**API 私钥) |
|
||||
| 异步回调 | API V2 | XML(**不加密**) | MD5 / HMAC-SHA256 |
|
||||
|
||||
## ⚠️ 服务商专属差异
|
||||
|
||||
- 所有业务接口必须传 `sub_mch_id`;多数模板还要传 `sub_appid`
|
||||
- 用**服务商**自己的 APIv2 密钥与 API 证书,**不要**用子商户的
|
||||
- **服务商不支持"支付中签约"**(直连商户独有)
|
||||
- 同一个 `notify_url` 接收所有子商户回调,必须按 `sub_mch_id` 路由
|
||||
- 预扣费通知 V3 路径是 `/v3/partner-papay/...`(不是 `/v3/papay/partner/...`),字段命名是 `sp_mchid` / `sp_appid` / `sub_mchid` / `sub_appid`(V3 风格无下划线)
|
||||
|
||||
## 业务接口
|
||||
|
||||
### 1-签约
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 公众号纯签约 | V2 GET | [公众号纯签约.md](./1-用户签约/公众号纯签约.md) |
|
||||
| APP 纯签约(含步骤 2 iOS/Android SDK) | V2 POST + 客户端 SDK | [APP纯签约.md](./1-用户签约/APP纯签约.md) |
|
||||
| 小程序纯签约(含 wx.navigateToMiniProgram + onShow JS) | 客户端 SDK + V2 签名 | [小程序纯签约.md](./1-用户签约/小程序纯签约.md) |
|
||||
| H5 纯签约(含风控参数) | V2 GET | [H5纯签约.md](./1-用户签约/H5纯签约.md) |
|
||||
| APP 调起签约(WXLaunchMiniProgram) | 客户端 SDK | [APP调起签约.md](./1-用户签约/APP调起签约.md) |
|
||||
|
||||
> 服务商模式 ❌ **不支持支付中签约**
|
||||
|
||||
### 2-扣款
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请扣款 | V2 POST | [申请扣款.md](./2-代扣扣款/申请扣款.md) |
|
||||
| 预扣费通知 API(路径 `/v3/partner-papay/...`) | **V3 POST** | [预扣费通知.md](./2-代扣扣款/预扣费通知.md) |
|
||||
|
||||
### 3-解约
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请解约(contract_id 或 plan_id+contract_code 两种方式) | V2 POST | [申请解约.md](./3-协议解约/申请解约.md) |
|
||||
|
||||
### 4-查询
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 查询订单 | V2 POST | [查询订单.md](./4-订单协议查询/查询订单.md) |
|
||||
| 查询签约关系 | V2 POST | [查询签约关系.md](./4-订单协议查询/查询签约关系.md) |
|
||||
|
||||
### 5-退款
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 申请退款(**需服务商 apiclient_cert.p12**) | V2 POST + mTLS | [申请退款.md](./5-退款流程/申请退款.md) |
|
||||
| 查询退款 | V2 POST | [查询退款.md](./5-退款流程/查询退款.md) |
|
||||
|
||||
### 6-订单管理
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 关闭订单 | V2 POST | [关闭订单.md](./6-订单关单对账/关闭订单.md) |
|
||||
| 下载交易账单(响应是 CSV) | V2 POST | [下载交易账单.md](./6-订单关单对账/下载交易账单.md) |
|
||||
|
||||
### 7-回调通知
|
||||
|
||||
| 业务 | 协议/方式 | 文档 |
|
||||
|---|---|---|
|
||||
| 扣款结果通知(**含 sub_mch_id 路由**) | V2 XML 不加密 | [扣款结果通知.md](./7-异步结果回调/扣款结果通知.md) |
|
||||
| 签约/解约结果通知(**含 sub_mch_id 路由**) | V2 XML 不加密 | [签约-解约结果通知.md](./7-异步结果回调/签约-解约结果通知.md) |
|
||||
|
||||
---
|
||||
|
||||
## 服务端代码 / 签名实现参考
|
||||
|
||||
委托代扣官方未提供任何服务端代码示例(Java / Go / PHP / Python 等服务端语言均无)。可参考:
|
||||
|
||||
- **官方 V2 DEMO**(通用 V2 接口示例):[DEMO下载](https://pay.weixin.qq.com/doc/v2/partner/4011985109.md)
|
||||
- **签名 / 验签 / 加解密的算法描述**:[📄 服务商模式签名与验签规则](../接入指南/签名与验签规则.md)
|
||||
- **服务商专属注意**:所有签名都用**服务商**的 APIv2 密钥与 API 证书
|
||||
|
||||
> ⚠️ 用户如果坚持要 Java/Go 业务代码示例,请按 SKILL.md 能力 2 的"跨语言参考实现流程"——先 `AskQuestion` 让用户明确知晓"非官方维护、可能引发资金事故"风险,并用 WebFetch 当场打开本 skill 中"来源"链接的官方文档,**逐字段对照构造**——严禁凭训练知识猜测路径(尤其 `/partner/` 是否存在、字段命名是 `sub_mch_id` 还是 `sub_mchid` 这类细节,必须查文档)。
|
||||
@@ -0,0 +1,240 @@
|
||||
# 服务商模式排障手册
|
||||
|
||||
> 本文档是本角色 + 本产品排障的**唯一入口**。另一接入模式见对应角色目录下同名文件。
|
||||
>
|
||||
> ‼️ **使用规则**:用户报告任何问题(报错 / 接口异常 / 回调收不到 / 签名失败 / 对账差异等),**先加载本文档**按下方流程匹配,不要先翻其他文档或猜原因。
|
||||
>
|
||||
> ‼️ **语气**:像有经验的技术支持,自然对话解释原因和方案,不要冷冰冰罗列文档目录。
|
||||
|
||||
## 排障流程
|
||||
|
||||
1. **能给 Request-Id?** → 走「一、错误码 TOP 20」:取 Request-Id 末尾 `-` 后的数字(如 `...CF05-268578704` → `268578704`)在速查表匹配,命中后用「错误码详细排查」对应段落回复。
|
||||
2. **不能给 / 未命中 TOP 20?** → 走「二、常见问题」:按现象(HTTP / 回调 / 签名 / 退款 / 角色特有 / 业务规则 / 通用配置)定位子节。
|
||||
3. **两条都没命中?** → 用末尾「排障信息收集清单」回收信息后再判断。
|
||||
|
||||
---
|
||||
|
||||
## 一、错误码 TOP 20(Request-Id 场景)
|
||||
|
||||
> 由用户基于真实工单 / 客服系统数据**手动提供**,模型不得自行填充。
|
||||
|
||||
### 1.1 TOP 20 速查表
|
||||
|
||||
[等待用户补充:从该产品真实工单 / 客服系统统计高频错误码 TOP 20,按下表格式手动填入。]
|
||||
|
||||
| 错误码 | 错误信息 | 分类 |
|
||||
|:------:|---------|:----:|
|
||||
| [等待补充] | [等待补充] | [等待补充] |
|
||||
|
||||
### 1.2 错误码详细排查
|
||||
|
||||
[等待用户补充:与上方 TOP 20 一一对应,每个错误码一段。]
|
||||
|
||||
---
|
||||
|
||||
## 二、常见问题(无 Request-Id 场景)
|
||||
|
||||
> 来源:该产品官方「常见问题」文档 + 通用接入经验沉淀。
|
||||
|
||||
### 2.1 HTTP 错误(401 / 400 / 403)
|
||||
|
||||
| 状态码 | 含义 | 常见原因 | 排查要点 |
|
||||
|:----:|------|---------|---------|
|
||||
| 401 | 签名验证失败 | 私钥与证书不匹配;serial_no 填错;签名串拼接有误(换行符 / URL / body 为空时缺末尾换行);时间戳偏差过大 | 检查 Authorization 头格式;确认私钥正确加载;建议用官方 SDK |
|
||||
| 400 | 请求参数错误 | 必填参数缺失;金额单位是分不是元;时间格式不符 RFC 3339;JSON 层级错误 | 对照 API 文档逐项检查;金额单位是**分**;时间格式 `yyyy-MM-ddTHH:mm:ss+08:00` |
|
||||
| 403 | 权限不足 | 未开通对应支付产品;IP 不在白名单;商户号状态异常 | 商户平台 → 产品中心确认开通状态 |
|
||||
|
||||
### 2.2 回调问题
|
||||
|
||||
**收不到回调排查清单**(按优先级):① 地址不可达(URL 错 / 域名解析失败 / localhost / 服务未启动)→ ② URL 前后有空格致 DNS 失败 → ③ 防火墙拦截(未对回调 IP 段开白名单,见下方 IP)→ ④ 登录态拦截(notify_url 须从鉴权中间件中排除)→ ⑤ 响应非 200(如 FAIL / 404,重试后放弃)→ ⑥ 处理超时(须 5 秒内应答)→ ⑦ 域名未 ICP 备案 → ⑧ 商户号用错(实际收到了但用另一个 mchid 查单导致"订单不存在")。
|
||||
|
||||
**回调行为 Q&A**:
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 怎么确认微信发了回调? | 微信不提供回调日志查询,检查自身服务器访问日志 + 查单接口确认 |
|
||||
| 2 | 回调会重复收到吗? | 会,未正确响应时微信会重试,业务必须做幂等 |
|
||||
| 3 | 回调延迟正常吗? | 数秒到数十秒均属正常。建议回调 + 主动查单双保险 |
|
||||
| 4 | 能直接将回调当最终结果吗? | 不能,回调不保证送达,需结合查单接口确认 |
|
||||
| 5 | 商户平台能查回调状态吗? | 不支持,需调用查单接口 |
|
||||
| 6 | 回调怎么测试? | 无测试接口,需生产环境真实业务验证 |
|
||||
|
||||
**回调解密与验签**:
|
||||
|
||||
| # | 报错 | 原因 | 解法 |
|
||||
|---|------|------|------|
|
||||
| 1 | `cipher: message authentication failed` / `AEADBadTagException` | APIv3 密钥错误(最常见:密钥重置后代码未同步)或密文被截断 | 检查代码中的 APIv3 密钥与商户平台一致 |
|
||||
| 2 | "证书序列号不一致" | 用商户证书做了验签(应用平台证书)或平台证书过期 | 改用平台证书并确保未过期 |
|
||||
| 3 | `Last unit does not have enough valid bits` | 签名探测流量 | 检查 `Wechatpay-Signature` 是否以 `WECHATPAY/SIGNTEST/` 开头,是则返回非 2xx |
|
||||
| 4 | 签名参数顺序错误 | 参数个数 / 顺序 / 大小写不对或末尾缺 `\n` | 严格按文档顺序拼接,末尾必须有 `\n` |
|
||||
|
||||
**回调 IP 白名单**:
|
||||
|
||||
| 出口位置 | IP 网段 |
|
||||
|---------|---------|
|
||||
| 上海电信 / 联通 / CAP | `101.226.103.0/25` / `140.207.54.0/25` / `121.51.58.128/25` |
|
||||
| 深圳电信 / 联通 / CAP | `183.3.234.0/25` / `58.251.80.0/25` / `121.51.30.128/25` |
|
||||
| 香港 / 广州腾讯云 | `203.205.219.128/25` / `81.71.199.64`、`81.71.198.25`、`81.71.199.59` |
|
||||
|
||||
**委托代扣 签约/解约 通知出口 IP**:`101.226.233.128/25`
|
||||
|
||||
退款 / 分账通知 IP:`175.24.214.208`、`175.24.211.24`、`175.24.213.135`、`109.244.180.23`、`114.132.203.119`、`43.139.43.69`
|
||||
|
||||
### 2.3 签名与证书
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 证书序列号怎么获取? | 合作伙伴平台 → 账户中心 → API安全 → 商户API证书 → 管理证书 |
|
||||
| 2 | 一个商户号能设多个 API 证书吗? | 可以 |
|
||||
| 3 | 平台证书过期怎么换? | 参考 https://pay.weixin.qq.com/doc/v3/merchant/4012068829 ,建议代码实现自动轮换 |
|
||||
| 4 | 换 serial_no 后报签名错误? | 证书编号与私钥一一对应,更新 serial_no 时必须同步换私钥文件 |
|
||||
| 5 | V2 签名失败怎么排查? | 逐项对比签名原串:① 字段按 ASCII 字典序;② 大小写一致;③ 无多余空格或遗漏字段 |
|
||||
| 6 | V2 签名方式? | MD5 或 HMAC-SHA256(不是 V3 的 SHA256-RSA2048) |
|
||||
| 7 | API 只能通过域名访问吗? | 是,不支持 IP 直连 |
|
||||
| 8 | APIv2 密钥改后验签失败? | 密钥重置后代码中的密钥必须同步更新;服务商模式下密钥来源是**合作伙伴平台**而不是子商户 |
|
||||
|
||||
### 2.4 退款常见问题
|
||||
|
||||
> 产品专属退款规则(不可退期限、最小金额等)见 2.6。
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 余额不足 | 商户账户可用余额不足,充值后重试 |
|
||||
| 2 | 重复退款 | `out_refund_no` 已使用,换一个或查询原单状态 |
|
||||
| 3 | 订单未支付 | 只有支付 / 扣款成功的订单才能退款 |
|
||||
| 4 | 支付和退款能跨 V2/V3 吗? | 可以,但不建议 |
|
||||
| 5 | 退款没到账 / 状态异常 | 先查退款状态:`PROCESSING`=处理中(1-3 工作日);`SUCCESS`=已成功;`ABNORMAL`=异常退款,走异常流程;`CLOSED`=退款关闭,用新单号重发。代码见对应模式接口索引 |
|
||||
|
||||
### 2.5 本角色特有问题
|
||||
|
||||
> 摘录自 [服务商模式委托代扣常见问题](https://pay.weixin.qq.com/doc/v2/partner/4011988386.md) + [社区委托代扣 API 常见问题官方精选 36 题](https://developers.weixin.qq.com/community/pay/doc/0004aaa01e8908b165985d15e5bc08?blockType=8),仅原文呈现,不发挥。服务商模式下"扣款 / 签约 / 解约 / 协议关系"等内容与商户模式相同,本节侧重服务商专属问题。
|
||||
|
||||
#### 2.5.1 服务商专属
|
||||
|
||||
| 报错信息 / 问题 | 原因 / 答案 | 解决 |
|
||||
|---|------|------|
|
||||
| 服务商可以查询子商户是否开通了周期扣费? | 可以 | 合作伙伴平台 → 产品中心 → 特约商户授权产品 → 委托代扣 → 特约商户列表 |
|
||||
| 服务商可以帮子商户开通周期扣费? | 可以 | 合作伙伴平台 → 产品中心 → 特约商户授权产品 → 服务商委托代扣 → 特约商户列表 → 邀请子商户授权;子商户在站内信或「我的授权产品」点击"授权" |
|
||||
| 委托代扣模板申请页"解约结果通知 URL" | 是给商户后台的解约异步通知 URL;不是给用户的消息 | — |
|
||||
| 服务商委托代扣签约页能展示子商户 sub_appid 的 logo 吗? | **不支持**,仅展示服务商 appid 的 logo | 用户场景强需求时引导子商户走直连模式 |
|
||||
| 服务商模式没有「支付中签约」 | `pay/contractorder` 仅商户模式可用 | 子商户如有"支付+签约一次完成"需求,必须自行直连接入 |
|
||||
| 路径里 `/partner/` 写错或漏写 | 路径不匹配 | 服务商签约 / 扣款相关路径都带 `/partner/`:`/papay/partner/entrustweb` `/papay/partner/preentrustweb` `/papay/partner/h5entrustweb` `/papay/partner/querycontract` `/pay/partner/pappayapply`;解约 / 退款 / 查询订单 / 关单 / 账单则**不带** `/partner/` |
|
||||
| 误用了子商户的密钥 / 证书 | 服务商模式所有签名 / mTLS 都使用**服务商号**的密钥与证书 | 检查代码中加载的密钥来源 |
|
||||
| 串单:A 子商户的回调被 B 子商户业务处理 | 所有子商户共用同一回调地址,未按 `sub_mch_id` 路由 | 回调入口必须按 `sub_mch_id` 分发到对应子商户业务流;签约/解约回调要按 `change_type=ADD/DELETE` + `sub_mch_id` 一起路由 |
|
||||
|
||||
#### 2.5.2 公众号 / APP / H5 / 签约典型报错(与商户模式同源)
|
||||
|
||||
| 报错信息 | 原因 | 解决 |
|
||||
|---------|------|------|
|
||||
| 公众号纯签约 / "商家系统错误,请联系商家处理" | ① 服务商 APIv2 密钥错;② `notify_url` 签名时未用 encode 前原值;③ 必填参数漏传 / 多传;④ `request_serial` 或 `timestamp` 不是 int 或长度 < 9 位;⑤ 服务商号被处罚 | 1) 比对密钥(用合作伙伴平台 APIv2 密钥);2) encode 后转义符大写;3) 用[在线签名校验工具](https://pay.weixin.qq.com/doc/v2/tool/sign_verify.md)逐项核对 |
|
||||
| APP 纯签约 / "deeplink openWebview no permission" | APP 未在微信开放平台注册或未登记 bundleId;缺 `OpenBusinessWebview` 权限 | 微信开放平台完成 APP 注册并登记 bundleId;如缺权限联系运营申请。新模板请改用 `WXLaunchMiniProgram` |
|
||||
| H5 纯签约 / "商家服务异常,请联系商家处理" | 未配置签约域名或域名不完整 | 联系运营协助配置签约域名,须配置发起签约页面的根域 |
|
||||
| 公众号 / APP / H5 签约 / "签名失败" | APIv2 密钥错;`notify_url` 签名时未用 encode 前原值;H5 默认签名 `HMAC-SHA256` 误用 MD5 | 检查密钥;签名时使用 encode 前原值;按各接口默认 sign_type |
|
||||
| 公众号 / APP / H5 签约 / `plan_id` 错误 | `plan_id` 与服务商号未对应 | 比对模板 ID 是否在该服务商号下创建 |
|
||||
| 公众号纯签约 / encode 报错 | 转义符(如 `%2F` `%3A`)小写 | encode 后所有转义符必须**大写** |
|
||||
| 公众号纯签约 / 用户点开通自动续费"无反应" | 偶现,常因微信版本旧或缓存问题 | 引导用户更新微信或清除缓存,杀进程后重新打开签约页 |
|
||||
| 公众号纯签约 / 长链接跳转报 502 | 链接超长 | 链接长度限制 **1024 字节以内**,缩短或改用 URL 短链 |
|
||||
| APP 纯签约 / 点击"完成"后没回到 APP,停留在微信聊天界面 | ① APP 在微信开放平台未开"跳转"权限;② 回调处理不当;③ 模板名称中含空格;④ `contract_display_account` 含空格 / 中英文符号 / 特殊字符 | 1) 开放平台检查跳转权限;2) 按 [Android 接入说明](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/Android.html) 处理回调(注意 `taskAffinity` 必须与主界面 task 一致);3) 申请新模板时去掉模板名称里的空格;4) 检查并清理 `contract_display_account` 中的特殊字符 |
|
||||
| APP 纯签约 / 点击"完成"按钮 Android 没反应 / iOS 提示"未安装应用" | ① 传入的 `appid` 与签约 APP 的 `appid` 不一致;② 该 `appid` 对应的 APP 未安装在手机;③ APP 未在手机注册 schema 地址 | 1) 检查 `appid` 是否一致;2) 确认 APP 已安装;3) 检查 APP 的 URL Schema 注册 |
|
||||
| H5 纯签约 / 完成后报 `launchApplication:fail_url need encode` | 商户原 URL 中嵌套了其他 URL;微信侧回跳前会做一层 decode,导致 decode 后是非法链接被拦截 | 推荐**剔除嵌套 URL**;如必须嵌套,对 `failUrl` / `redirectUrl` 做**两层 encode** 试试 |
|
||||
| H5 纯签约 / iOS 左上角返回回到微信,Android 左上角返回回到签约页面 | 平台差异:Android 只是关 WebView 露出浏览器;iOS 做不到只拉起 WebView | **正常表现**,无需处理;产品文案给到用户即可 |
|
||||
| H5 纯签约 / 完成后点击"完成"返回的是商户首页(域名)而不是发起签约的页面 | ① 浏览器 referrer 策略变更,源页面未授权 referrer;② 发起签约页面 URL 含 fragment(`#xxx`) | **方案 A(推荐)**:在源 H5 页面 `<head>` 中加 `<meta name="referrer" content="no-referrer-when-downgrade">`;**方案 B**:加 `<meta name="referrer" content="unsafe-url">`;**注意**:① 检查 HTML 中是否有多个 `meta name="referrer"` 声明(后者会覆盖前者);② 必须由商户前端发起的跳转才生效(不能通过中转/后台跳转);③ iOS 15+ 对 referrer-policy 更严格,referrer 只带 host 不带 path,**目前只能返回商户域名页**;④ URL 中 fragment(`#` 后部分)不参与服务端请求,回跳不会带上 |
|
||||
| APP 内嵌 H5 纯签约 / 无法唤起微信 | APP 拒绝了打开授权 | 检查拉起微信的 APP 是否拒绝了打开授权 |
|
||||
| APP 内嵌 H5 纯签约 / 完成后申请返回 APP 报 `launchApplication:fail` | 商户配置的 APP Schema 有误 | 联系 APP 开发人员检查操作系统是否注册了该 Schema、配置的 `appid` 是否正确 |
|
||||
|
||||
#### 2.5.3 扣款 / 预扣费通知(与商户模式同源)
|
||||
|
||||
| 报错 / 现象 | 原因 | 解决 |
|
||||
|---|---|---|
|
||||
| 申请扣款返回成功,扣款失败 | 申请受理成功 ≠ 扣款成功,最终结果以扣款回调为准 | 等扣款回调;回调中带 `err_code`(`NOTENOUGH` 余额不足 / `RULELIMIT` 风控等) |
|
||||
| 调用预扣费通知 / "当前签约的模板不是周期扣费类型,不能下发扣费前通知" | 模板不是预扣费通知模式 | 申请新模板时选择预扣费通知模式,或联系运营修改 |
|
||||
| 同一用户被发起多笔扣款单 | 同 mch_id + 同 `out_trade_no` 只扣一次 | 严格一单一扣;重试**换新 `out_trade_no`** |
|
||||
| `trade_state=CLOSED`(扣款失败自动关单) | 用户零钱 + 银行卡等所有可扣方式均失败后自动关单 | 换新 `out_trade_no` 重新调申请扣款 |
|
||||
| 申请扣款 / `CONTRACT_NOT_EXIST` | 协议已解约 / `contract_id` 错 | 调[查询签约关系](https://pay.weixin.qq.com/doc/v2/partner/4011988379.md)确认状态 |
|
||||
| 申请扣款 / `RULELIMIT` 风控 | 用户账号有违规 | 联系用户确认账号状态,必要时联系微信客服解除风控 |
|
||||
| 申请扣款 / `ORDER_ACCEPTED` | 受理中,请勿重复发起 | 调查询订单看实际状态 |
|
||||
| 通知后 24 小时扣费 / 申请扣款返回成功但订单未生成 | 用户首次签约(含解约后重签)超过 12 小时发起扣款,需等 24 小时后执行;调用时不立即生成订单 | 12 小时内发起立即执行;超过则等 24 小时后执行(属正常表现) |
|
||||
| 7:00 / 22:00 之间无法扣款 | 强制时段限制 | 调用前先校验北京时间在 7:00-22:00 内,超出时段重新调度 |
|
||||
| 解约后能否换签约方式重签 | 可以;解约后可任选纯签约方式,**但签约单号不能重复** | 换新 `contract_code` 重新签约;服务商模式不支持「支付中签约」 |
|
||||
| 预扣费通知 V3 字段错 | 误用了商户字段 `mchid` / `appid` | 必须用服务商专属 `sp_mchid` / `sp_appid` + `sub_mchid`(必传)/ `sub_appid`(可选) |
|
||||
| 预扣费通知模式 / 没发预扣费通知就调申请扣款 | 接口返回 `INVALID_REQUEST` —— "需要先下发扣费前通知才能发起扣费" | 必须**先**调「预扣费通知」,扣费等待期结束后再调「申请扣款」;当前签约扣费期已结束的需重新发起扣费前通知 |
|
||||
| 预扣费通知 / `RESOURCE_ALREADY_EXISTS` vs `INVALID_REQUEST` 错误码区别 | 两者都意味着"已发送过通知",但语义不同:<br>• `RESOURCE_ALREADY_EXISTS`:**相同参数**重复发送,**视为成功**(可继续后续扣款)<br>• `INVALID_REQUEST`:"已发送过扣费前通知,需要等本次扣费完成后再发起新的"——**参数不一样的重复发送**,**视为失败** | 收到 `RESOURCE_ALREADY_EXISTS` 当作发送成功处理;收到 `INVALID_REQUEST` 不能继续发,等本次扣费完成或周期结束 |
|
||||
| 申请扣款 / "扣款请求已受理,请勿重复发起" | 自动续费规则:一个协议 ID **在等待期内(24 小时内)只能有一笔扣款** | 等当前扣款单结束(成功或自动关单)后再发起;不要换号重提,先调「查询订单」(请求体带 `sub_mch_id`)看实际状态 |
|
||||
| 申请扣款 → 关单 → 用代扣查单接口查不到 / 报"订单不是委托代扣场景" | 关单后委托代扣查单接口查不到该单 | 改用**基础支付的查询订单接口**(请求体带 `sub_mch_id`)确认订单状态 |
|
||||
|
||||
#### 2.5.4 协议关系(与商户模式同源)
|
||||
|
||||
| 报错 / 问题 | 原因 | 解决 |
|
||||
|---|---|---|
|
||||
| 同一用户在同一服务商号同模板 ID 下能否签多次? | 不能,仅一个生效签约 | 多次签约需用多个 `plan_id`;用户解约后可重签 |
|
||||
| 用户已签约,仍能拉起签约页 | 微信会拒绝重复签约,签约页提示无法签约 | 接入前先调查询签约关系兜底 |
|
||||
| 用户主动解约后,是否需商户同意? | 不需要 | 解约后通过解约结果通知告知商户,商户更新本地协议状态 |
|
||||
| 同一种业务,APP 纯签约和 H5 纯签约 `plan_id` 能共用吗? | 可以 | — |
|
||||
| 续费协议能否提前续费? | 必须在协议**最后一期(到期当月)**发起,不能在中间月份 | — |
|
||||
| 模板适配鸿蒙是否仅针对 APP? | 是 | 改用 `WXLaunchMiniProgram` |
|
||||
| 委托代扣模板 ID 停用后的影响? | 已签约用户**可以继续扣费或解约**,但**不能再新增签约**;停用后**无法恢复使用** | 停用前服务商自行评估风险;新签约入口需切换到其他模板 |
|
||||
| 解约回调地址(解约结果通知 URL)修改后多久生效? | **实时生效** | 模板配置修改即刻生效,无需重发 |
|
||||
| 合作伙伴平台发起的解约,回调里 `contract_termination_mode=3`(商户 API 解约),正常吗? | **正常**,平台后台底层就是调用商户 API 解约 | 业务侧不必区分"后台解约"与"API 解约" |
|
||||
| APP 纯签约是否支持多账号签约? | **支持**,预签约接口多传 `outerid` 字段,**值格式必须按示例**:`李*艳(00000000000)`,否则返回 `PARAMERROR:outerid` | 配合 `contract_outerid` 使用;多账号签约下,模板扣款次数限制按签约用户**分开计算**(每个签约独立计) |
|
||||
| 多账号签约 `contract_outerid` / `outerid` 长度限制 | `contract_outerid` **32 位字符**;`outerid` **32 位字符** | — |
|
||||
| 同一 `contract_code` 已签约 → 解约后 → 还能用相同 `contract_code` 再签约吗? | **目前可以** | 业务侧仍建议换新 `contract_code` 避免与历史数据混淆 |
|
||||
|
||||
### 2.6 业务规则 Q&A
|
||||
|
||||
> 来源:[服务商模式委托代扣常见问题](https://pay.weixin.qq.com/doc/v2/partner/4011988386.md),仅原文摘录。
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | 周期扣费首期金额可以与后续不一致吗? | 可以,每期金额在模板限额内即可 |
|
||||
| 2 | 自动续费 vs 测试模板的限额限频 | 自动续费按模板配置;测试模板单笔 0.1 元 / 单日 100 次;同一签约协议每日成功扣 1 次(失败不计),单周期成功扣 2 次 |
|
||||
| 3 | 一个服务商可申请多少模板? | 没有限制 |
|
||||
| 4 | 委托代扣支持小程序接入吗? | 支持 |
|
||||
| 5 | H5 纯签约能跳指定小程序页面吗? | 不能,只能原路返回 |
|
||||
| 6 | 周期扣款失败会通知用户吗? | 不会 |
|
||||
| 7 | 委托代扣有沙箱吗? | 没有 |
|
||||
| 8 | 周期为 1 月、扣费日 30,2 月怎么办? | 商户自行控制时机,2 月可提前调申请扣款 |
|
||||
| 9 | 12 期代扣中第 3 期由用户线下缴费,第 4 期能继续扣吗? | 可以 |
|
||||
| 10 | 委托代扣有限额吗? | 有,按模板申请;具体咨询运营 |
|
||||
| 11 | 微信钱包余额不足、卡有余额,会扣失败吗? | 银行卡限额、冻结、风控等会失败 |
|
||||
| 12 | 扣款 / 预扣费通知的可调用时段? | 北京时间每天 7:00-22:00;签约时间不影响 |
|
||||
| 13 | 扣款失败回调后如何重试? | 预扣费通知模式:可扣周期内换单重试,周期结束需重新调预扣费通知;24h 模式:换新 `out_trade_no` |
|
||||
| 14 | 预签约接口 2 小时内能原单重试吗? | 支持 |
|
||||
| 15 | 委托代扣有 IP 白名单吗? | 没有 |
|
||||
| 16 | 委托代扣能否指定签约结束跳转链接? | 不支持指定 |
|
||||
| 17 | H5 签约成功,浏览器返回地址会带 `return_appid` 吗? | 不会 |
|
||||
| 18 | 测试模版能否改名? | 不支持 |
|
||||
| 19 | `notify_url` 签名时用编码前还是编码后? | **编码前的原值**(未 URL Encode 的) |
|
||||
| 20 | 用户解约需要商户同意吗? | 不需要 |
|
||||
| 21 | 委托代扣支持首期优惠吗? | 支持,申请模板时设"优惠价格 < 标准价格"即可 |
|
||||
| 22 | 首月优惠由谁判断? | 微信侧自动判断,签约页直接展示 |
|
||||
| 23 | 24 小时扣费模式下申请扣款成功但未生成订单? | 用户首次签约(含解约后重签)超 12 小时发起扣款,需等 24 小时后执行;调用时不立即生成订单 |
|
||||
| 24 | 同一用户用不同 appid 签约,会生成 1 个还是 2 个协议? | 1 个,协议维度为 mch_id+plan_id |
|
||||
| 25 | 公众号 A 注销并迁至 B,能用 B 的 appid 扣款吗? | 可以,appid 与 mch_id 有绑定关系即可 |
|
||||
| 26 | "进入商家小程序"按钮怎么配? | 联系运营协助配置 |
|
||||
| 27 | OpenBusinessWebview / WXLaunchMiniProgram 数据互通吗? | 互通 |
|
||||
| 28 | 切换 WXLaunchMiniProgram 后,存量 OpenBusinessWebview 用户是否受影响? | 不影响,存量签约关系仍有效 |
|
||||
| 29 | 实际扣款金额由模板还是申请扣款的金额决定? | 以申请扣款 `total_fee` 为准;模板扣费类型"为"=固定金额必须按模板传,"不高于"=非固定可在限额内自定义 |
|
||||
| 30 | 公众号纯签约功能对域名有限制吗? | 没有限制 |
|
||||
| 31 | 委托代扣支持纯血鸿蒙吗? | 仅 `WXLaunchMiniProgram` 调起方式支持 |
|
||||
| 32 | "通知后 24h 扣费"模式如何指定? | 申请模板时选择"延迟 24 小时扣费" |
|
||||
| 33 | H5 纯签约 / "请使用 GET 请求" | 请求方式不对,必须 GET |
|
||||
| 34 | 申请扣款的"周期 / 限额 / 频次"详细规则? | ① 自动续费周期一般以**月 / 季度**为周期,具体扣款发起时间不受模板内容周期影响;② 委托代扣**初始额度**:单笔 500、单日 2500;③ **授权扣款 / 免密支付**:额度限制内**每天可扣款 5 次**(同用户 / 同签约协议下;扣款失败不计次数);④ **自动续费模板**:需按模板设定周期扣款,**同用户 / 同签约协议下每天仅可成功扣款 1 次**;⑤ 测试模板:单笔 0.1 元,每天可扣 100 次 |
|
||||
| 35 | **单用户在单模板下"申请扣款接口的尝试调用频率"是多少?** | **默认每天 ≤ 300 次**(包含成功 + 失败的返回);succeed 才表示扣款受理成功,等待扣款中 |
|
||||
| 36 | 同一个微信号在同模板下"当天扣款次数"上限? | **当天最多 150 次**(含商户重试申请扣款失败的次数和成功的次数) |
|
||||
| 37 | 多账号签约时,模板的扣款次数限制按什么计算? | **按每个签约独立计算**(不同签约用户的次数互不影响) |
|
||||
|
||||
### 2.7 通用接入配置
|
||||
|
||||
| # | 问题 | 答案 |
|
||||
|---|------|------|
|
||||
| 1 | V2 和 V3 可以同时用吗? | 可以,密钥体系独立互不影响 |
|
||||
| 2 | 微信支付有测试环境吗? | **没有**,所有调试需在生产环境进行 |
|
||||
| 3 | 同一错误为什么返回不同错误码? | 存在参数校验优先级,多参数错误时可能先返回 `PARAM_ERROR` |
|
||||
| 4 | 接口地址能在浏览器直接打开吗? | **不能**,需程序调用并携带证书,建议用 Postman 调试 |
|
||||
| 5 | 防火墙拦截(如医院场景)怎么办? | 微信服务端 IP 动态更新,建议以**域名白名单**配置防火墙 |
|
||||
|
||||
---
|
||||
|
||||
## 排障信息收集清单
|
||||
|
||||
两条路径都未命中时,请用户提供:接入模式、出错环节(签约 / 申请扣款 / 预扣费通知 / 解约 / 查询签约关系 / 退款 / 回调 / 对账)、HTTP 状态码 + 完整响应体、Request-Id(含尾段错误码)、服务商号 mchid + 子商户号 sub_mchid + 业务单号(`contract_code` / `contract_id` / `out_trade_no` / `out_refund_no`)+ 请求时间。
|
||||
Reference in New Issue
Block a user