微信支付 V3 的文档写得不算差,但第一次接的人几乎都会卡在同样的几个地方。这篇把我们踩过的坑按「出错频率」排个序,方便你对着排查。
坑一:私钥格式不对(最高频)
报错通常是「签名错误」或「私钥格式错误」,但根因是文件格式。
微信支付要求的是 PKCS#8 格式的私钥(-----BEGIN PRIVATE KEY-----),而很多证书工具导出的是 PKCS#1(-----BEGIN RSA PRIVATE KEY-----)。两者内容一样,头尾不同,SDK 直接解析失败。
转换命令:
openssl pkcs8 -topk8 -inform PEM -in apiclient_key.pem -outform PEM -nocrypt -out apiclient_key_pkcs8.pem
判断方法:打开文件看第一行。如果不是 BEGIN PRIVATE KEY,就需要转换。
另外注意:私钥在数据库或配置里存储时,换行符容易在传输过程中丢失,导致「看起来对但用不了」。建议存 base64 或 JSON 转义后的字符串,读取时还原换行。
坑二:签名串拼接错了
V3 的签名串是五行,用 \n 连接,最后一行也要有换行:
HTTP请求方法\n
URL\n
时间戳\n
随机串\n
请求报文主体\n
最常见的三个错误:
- 请求体用了格式化后的 JSON(多了空格和换行),必须用实际发送的那一串;
- GET 请求的 body 是空字符串,但漏了最后的
\n; - URL 带了 query 参数却只取了 path。
排查技巧:把待签名串打印出来,逐行比对。肉眼看不出问题的时候,用 var_dump 或 console.log 输出成数组更容易发现尾部空格。
坑三:回调验签与解密顺序搞反
支付结果通知是加密的:先验签(确认来自微信),再解密(拿到明文),顺序错了会得到一堆乱码。
需要注意:
- 验签用的是平台证书或微信支付公钥,不是你自己的商户证书;
- 微信支付正在从「平台证书」迁移到「微信支付公钥」模式,新商户直接按公钥模式配置,别照抄旧教程;
- 回调必须返回
{"code":"SUCCESS","message":"成功"},HTTP 200。返回纯文本success会被判定为失败并持续重试。
坑四:金额单位
V3 全部用「分」为单位,整数。1.00 元 要传 100。
这个坑的后果比较严重:传成 1 就是收了一分钱,传成 100.00 会直接报参数错误。建议在业务层就统一用整数分存储,展示时再除以 100,避免浮点数到处飘。
坑五:退款与对账的时间差
- 退款不是实时的,异步结果通过回调通知,别在接口返回成功后就改订单状态;
- 对账单是 T+1 生成,当天查不到;
- 对账文件的金额字段是元(带两位小数),和下单时的「分」不同单位,解析时容易错。
建议做法:订单表单独存「支付金额(分)」,对账时统一换算,别在多处做单位转换。
一个实用的排查顺序
遇到问题,按这个顺序走,能省掉大部分时间:
- 看错误码:V3 的错误码比 V2 清晰,
PARAM_ERROR、SIGN_ERROR、ORDER_NOT_EXIST含义明确; - 打印待签名串:90% 的签名问题在这一步现形;
- 单独跑一次最小请求:用一个固定参数的查询接口测通,再回到业务代码;
- 检查服务器时间:时间戳偏差超过 5 分钟会验签失败,这在容器环境里很常见;
- 确认证书序列号:请求头里的
serial_no必须和你用的证书匹配,切过证书后容易忘改。
最后一句
支付这块不建议「先跑通再优化」。金额、幂等、回调重试、退款状态,这四个地方一旦设计错,后期修起来比重写还麻烦。
接入前花半天把状态机和幂等键想清楚,能省掉后面两周的救火。