微信支付 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 头。

三个容易出错的细节:

  1. 请求路径必须带查询串,且要和实际发出的 URL 完全一致,包括参数顺序。
  2. 请求体必须用最终发出的那串字节参与签名,序列化后再改动字段就会验签失败。
  3. 时间戳与服务器时间偏差不能太大,机器时钟漂移会导致签名过期。

建议把签名逻辑抽成单独的模块并写单元测试,用固定的输入输出做断言——这块一旦写错,后面所有接口都会失败,而错误码只有"签名错误"四个字。

四、回调验签与资源解密

支付结果通知是整条链路里最容易做错的一环,标准顺序是:

  1. 收集 Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Signature、Wechatpay-Serial 四个头;
  2. 用对应的平台证书公钥验签,确认报文未被篡改;
  3. 用 APIv3 密钥对 resource 字段做 AES-256-GCM 解密,得到明文业务数据;
  4. 校验解密后的 mchid、appid、金额、订单号与本地记录一致;
  5. 处理业务,然后按规范返回成功响应。

失败点集中在两处:一是 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 等官网文档及合同为准。