代码 微信支付 APIv3 接入笔记:商户证书、平台证书与 APIv3 密钥的职责划分

2026-09-03 09:02:35

微信支付 APIv3 接入笔记:商户证书、平台证书与 APIv3 密钥的职责划分

先记录一个典型的排查过程:回调解密一直报错,密文、 nonce 都对,最后发现是把商户 API 证书私钥和平台证书下载工具弄混了,拿商户私钥去解平台证书加密的回调报文,自然一路失败。这类问题在 APIv3 接入里不算罕见,核心是先分清三套密钥各管哪一段。

两把证书、一个密钥,各管一段

微信支付 APIv3 的密钥体系里,容易混淆的是这三样:

商户 API 证书(apiclient_cert.pem / apiclient_key.pem) 用于发起请求。商户用私钥对请求签名,微信支付用商户公钥验签。这个环节签名串是:

请求方法\n
URL\n
时间戳\n
随机串\n
报文主体\n

注意末尾必须有换行符;空 body 时最后一行只有一个 \n。常见 401 原因基本都出在这几处:签名串末尾漏换行、URL 与实际请求不一致、签名时和发送请求时用了不同的时间戳/随机串、两次序列化 JSON 的字段顺序或空格不一致、编码不是 UTF-8、用了错误的商户私钥。

判断当前私钥和证书是否成对,可以分别导出 modulus 对比:

openssl x509 -noout -modulus -in apiclient_cert.pem
openssl rsa -noout -modulus -in apiclient_key.pem

两边 modulus 一致才说明是同一对。

微信支付平台证书 用于接收应答和回调。微信支付用平台证书私钥对响应签名,商户用平台证书公钥验签。这个环节属于验签,和上面“商户发请求”用的是完全不同的证书。平台证书序列号在 HTTP 头 Wechatpay-Serial 里。注意:代理/CDN 如果过滤掉了 Wechatpay-* 自定义头,验签流程会因为拿不到必要头而失败。

应答/回调的验签名串是:

应答时间戳\n
应答随机串\n
报文主体\n

用 OpenSSL 验签时先导出公钥再验证:

openssl x509 -in wechatpay_platform_cert.pem -pubkey -noout > platform_pub.pem
openssl dgst -sha256 -verify platform_pub.pem -signature signature.txt < body.txt

其中 signature.txt 是来自头 Wechatpay-Signature 的 Base64 解码结果。

APIv3 密钥 是独立于前两者的 32 字节对称密钥,只用于解密回调报文。商户平台可查,字符集为数字和大小写字母。

判断“该用哪个”只需问一句:当前正在处理的是我发出去的请求(用商户 API 证书私钥签名),还是微信支付发回来的响应/主动通知(用平台证书验签)?如果是回调通知里的 resource 密文,再进入解密环节,用 APIv3 密钥。

回调解密:AES-256-GCM 的参数组成

回调 body 里的 resource 字段是 AES-256-GCM 加密后的结果,相关字段有:

  • original_type:原始报文类型,如 transaction
  • algorithm:固定 AEAD_AES_256_GCM
  • ciphertext:Base64 编码的密文
  • nonce:加解密使用的随机串
  • associated_data:附加数据,可能为空

解密用 APIv3 密钥,GCM 模式的参数是 ciphertext(Base64 解码)、nonce、associated_data。常见解密失败原因:

  • 用错密钥,比如拿 A 商户的 APIv3 密钥去解 B 商户的回调,或者误用 APIv2 的密钥;
  • ciphertext 直接传了 Base64 字符串而没有 Base64 解码(官方 SDK 一般内部处理掉了,自己拼 HTTP 客户端时需要手动解);
  • associated_data 或 nonce 漏传;
  • 运行环境不支持 256 位 AES:较旧 Java 版本需要安装 unlimited policy 或升级到 8u162+,PHP 需 7.1+。

回调应答与重试边界

收到回调后应在 5 秒内完成验签、解密和业务处理并返回 200 或 204(无包体)。验签或解密失败时返回 4xx/5xx,body 带:

{"code":"FAIL","message":"..."}

微信支付侧会按约 15s、15s、30s、3m 的间隔重试,最多 15 次。解密成功后拿到的业务字段主要有:transaction_idout_trade_nomchidtrade_state(SUCCESS)、amount.total(单位分)、bank_typepayer.openid 等。

应答还要注意防重放:校验 Wechatpay-Timestamp 与本地时间偏差,建议放宽到最多 5 分钟,服务器本身需要 NTP 同步。超出时间范围的请求应直接按失败处理。

签名探测流量

微信支付会在极少数应答或回调中故意放入错误签名,用于探测商户是否做了验签。这类签名的显著特征是带有 WECHATPAY/SIGNTEST/ 前缀。发现这种前缀不要做特殊放行,直接走正常验签流程:验签失败就按失败处理——回调返回 4xx/5xx 等待重发,应答失败则丢弃并提示用户重试。

边界说明

以上命令与字段来自微信支付官方商户文档,写本文时未在本站运行环境逐一实测;签名串拼接格式、重试间隔、回调字段名请以接入时点的最新官方文档为准。PHP 与 Java 的版本要求同样只适用于相应语言环境,其他语言 SDK 需另行确认对 AEAD_AES_256_GCM 的支持情况。

推荐文章

程序员茄子在线接单