微信支付 APIv3 接入实操:签名、回调验签与幂等设计
微信支付 APIv3 是一次彻底的重构:签名算法从 MD5/HMAC 换成 SHA256-RSA2048,回调报文整体加密,平台侧改用证书体系。很多团队拿着旧版 v2 的代码和经验直接迁移,结果卡在验签失败或解密报错上,排查半天找不到原因。
如果你还没跑通最基础的一笔收款,建议先看 网站接入支付宝/微信支付教程 的资质与账号部分,本文从"账号已开通、要动手写代码"这个节点开始讲。微信支付本身的产品定位与能力边界见 微信支付。
一、APIv3 与旧版 v2 的关键差异
先把差异摆在前面,后面每一步都能对应上:
| 维度 | 旧版 v2 | APIv3 |
|---|---|---|
| 请求签名 | MD5 或 HMAC-SHA256 拼串 | SHA256-RSA2048,用商户私钥签名 |
| 请求格式 | XML | JSON |
| 回调报文 | 明文 XML | 整体加密,需用 APIv3 密钥解密 |
| 身份校验 | 单一 API 密钥 | 平台证书 + 商户私钥成对使用 |
| 证书轮换 | 手动换密钥 | 平台证书有有效期,需定期更新 |
这张表解释了绝大多数迁移失败:v2 只需记住一把密钥,v3 要把私钥、APIv3 密钥、平台证书三样都摆对位置,任何一样用错都会表现为"签名错误"或"解密失败",而这两种报错信息量极低。
二、准备工作:三份密钥材料
2.1 账号与产品开通
在微信支付商户平台完成商户号开通后,还需要单独申请对应的支付产品权限(JSAPI、Native、H5、APP 等)。注意:产品权限是按场景申请的,只开通了 Native 就去调 JSAPI 接口会直接返回无权限。同时要绑定 APPID,商户号与 APPID 是多对多关系,配置错会报"appid 与 mchid 不匹配"。
2.2 三份材料各自管什么
- 商户 API 私钥(apiclient_key.pem):自己生成、自己保管、绝不上传第三方。用于给每个请求签名,以及解密回调中涉及商户侧的内容。
- 商户证书序列号:随商户证书一起签发,放在请求头
Authorization里,让微信支付知道用哪把公钥验你的签名。 - APIv3 密钥:一串 32 字节的对称密钥,在商户平台自行设置。用于回调报文的 AES-256-GCM 解密,以及敏感字段加密。
- 平台证书:微信支付侧的公钥证书,用来验证回调确实来自微信支付。它有有效期,必须做定时更新,不能一次性下载后永久写死。
最常见的生产事故是把 APIv3 密钥和商户私钥搞混——前者是对称密钥、用来解密,后者是非对称私钥、用来签名,两者不可互换。
三、请求签名:SHA256-RSA2048
签名的构造规则是固定的:把 HTTP 方法、请求路径(含查询串)、时间戳、随机串、请求体拼成五行字符串,用商户私钥做 SHA256-RSA 签名,再按固定格式放进 Authorization 头。
三个容易出错的细节:
- 请求路径必须带查询串,且要和实际发出的 URL 完全一致,包括参数顺序。
- 请求体必须用最终发出的那串字节参与签名,序列化后再改动字段就会验签失败。
- 时间戳与服务器时间偏差不能太大,机器时钟漂移会导致签名过期。
建议把签名逻辑抽成单独的模块并写单元测试,用固定的输入输出做断言——这块一旦写错,后面所有接口都会失败,而错误码只有"签名错误"四个字。
四、回调验签与资源解密
支付结果通知是整条链路里最容易做错的一环,标准顺序是:
- 收集
Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Signature、Wechatpay-Serial四个头; - 用对应的平台证书公钥验签,确认报文未被篡改;
- 用 APIv3 密钥对
resource字段做 AES-256-GCM 解密,得到明文业务数据; - 校验解密后的
mchid、appid、金额、订单号与本地记录一致; - 处理业务,然后按规范返回成功响应。
失败点集中在两处:一是 Wechatpay-Serial 对应的平台证书没更新,回调验签直接失败;二是 把"验签通过"当成"已经处理",没做金额与订单号的双向核对,给伪造回调留了口子。
五、四种常用支付产品怎么选
| 产品 | 使用场景 | 关键约束 |
|---|---|---|
| JSAPI | 微信内 H5、公众号 | 需要用户 openid,需配置支付授权目录 |
| Native | PC 网站扫码 | 返回二维码链接,需自行渲染 |
| H5 | 微信外手机浏览器 | 需报备域名,部分场景受限 |
| APP | 自有移动应用 | 需绑定应用签名与包名 |
选型原则很简单:用户在微信里就用 JSAPI,在电脑前就用 Native,在手机外部浏览器用 H5,原生应用用 APP。错配会导致支付失败或体验断裂,比如在微信内用 H5 支付会被拦截。
六、订单状态机与幂等设计
支付系统的正确性靠状态机保证。建议只允许这几种状态流转:待支付 → 支付中 → 支付成功 / 支付关闭 / 已退款,且所有状态变更只能由服务端驱动,前端回跳只用于展示,不能作为支付成功的依据。
幂等要落在三个位置:回调处理(同一笔通知可能重复投递多次,用订单号 + 通知 ID 去重)、主动查单(网关超时时必须查单确认,不能直接判失败)、重试机制(微信支付侧会按策略重发通知,你的接口必须能承受重复调用)。
一个实用的做法是给回调处理加唯一索引:在流水表上对"商户订单号 + 事件类型"建唯一约束,插入冲突即代表已处理,直接返回成功。这比先查后写更可靠,也避免了并发下的重复入账。
七、联调与上线检查清单
- 商户私钥、APIv3 密钥、平台证书三份材料分别存放,权限最小化;
- 平台证书配置定时更新任务,并监控更新失败;
- 回调接口可公网访问、走 HTTPS、无鉴权拦截(不要加登录态校验);
- 回调处理有唯一索引或幂等键;
- 主动查单逻辑已实现并覆盖超时场景;
- 金额校验同时比对"元/分"单位,避免 100 倍错误;
- 日志脱敏,不打印完整密钥与用户敏感信息;
- 沙箱或小额真实订单跑通全链路,含退款。
测试方法论可参考 支付网关沙箱测试,支付链路的通用时序见 支付交易流程基础。
八、五个高频踩坑
一是金额单位:微信支付以"分"为单位,前端展示用"元",转换漏掉乘 100 是最常见的对账差异来源。二是回调重复处理:没做幂等,重复通知导致重复发货。三是证书未更新:平台证书过期后回调全部验签失败,且不会立刻报警。四是把前端回跳当成功:用户关掉页面就永远收不到结果,必须以服务端通知或主动查单为准。五是审批目录未配置:JSAPI 支付授权目录与实际页面路径不一致,拉起支付即报错。
对账与差错处理见 对账与账单下载操作手册,退款与结算见 退款与清结算操作手册,风控策略见 支付风控策略。更多支付方案可参见 支付 分类。
来源:16IDC 原创,文中涉及的产品能力、费率与准入要求以 pay.weixin.qq.com 等官网文档及合同为准。