微信支付v3退款签名失败主因是证书序列号(serial_no)与商户平台显示不一致或sha256withrsa签名原文拼接错误:必须严格按“方法\n路径\n时间戳\n随机串\n请求体\n”格式,含末尾换行,路径不含域名和查询参数,时间戳为秒级,私钥须为pkcs#8格式。

微信支付V3退款接口签名失败,八成出在证书序列号或SHA256withRSA签名构造上——不是私钥格式错了,就是时间戳单位搞混了,再或者URL路径多了一个斜杠。
怎么拿到正确的证书序列号(serial_no)
文档里写的“从证书中提取”容易误导人。实际开发中,serial_no必须和你上传到微信商户平台的那张证书完全一致,且是32位十六进制字符串,不能从本地.pem文件里用OpenSSL命令硬算。
- 登录微信商户平台 →「账户中心」→「API安全」→「管理证书」,页面上直接显示的就是你要用的
serial_no - 千万别用
openssl x509 -in apiclient_cert.pem -noout -serial去解析——它返回的是serial=XXXX格式,还要手动去掉serial=并转大写,极易出错 - ThinkPHP里建议统一从配置读取:
config('wechat.cert_serial_no'),而不是写死在代码里 - 如果用了多个子商户或服务商模式,每个商户的
serial_no都不同,必须按请求上下文动态切换
退款请求签名为什么总报401(Authorization无效)
签名原文(message)拼接顺序和内容稍有偏差,就会导致openssl_sign()生成的signature完全错误。微信不校验中间过程,只认最终base64结果,所以排查极难定位。
支持AI生成符合公众号规范的图文,推送至草稿箱;兼容其他技能生成的图文/图片。通过向导扫码授权,支持多账号;无需暴露Secret密钥或配置IP白名单。
- 签名原文严格为:
HTTP方法\nURL路径\n时间戳\n随机串\n请求体\n(注意每段末尾都有换行符\n,最后一行也是\n) -
URL路径必须是绝对路径且不含域名,也不带查询参数;例如退款接口是/v3/pay/transactions/id/{transaction_id}/refunds,不能写成https://api.mch.weixin.qq.com/v3/...或/v3/...?a=1 -
时间戳必须是秒级Unix时间戳,time()即可;用microtime(true)或Carbon::now()->timestamp没做整除会直接失败 -
随机串长度32以内、纯ASCII字符(推荐bin2hex(random_bytes(16))),避免中文或特殊符号 - POST请求的
请求体必须是原始JSON字符串,不能是PHP数组,也不能经过json_encode()二次处理(比如已编码过还再encode一次)
ThinkPHP里加载私钥老是失败:PKCS#1 vs PKCS#8
微信导出的apiclient_key.pem默认是PKCS#8格式,但很多开发者用旧工具生成或转换过,变成PKCS#1,PHP的openssl_pkey_get_private()会静默失败,openssl_sign()抛空异常。
- 用
head -n1 apiclient_key.pem检查开头:要是-----BEGIN RSA PRIVATE KEY-----,就是PKCS#1,需转成PKCS#8:openssl pkcs8 -topk8 -inform PEM -in apiclient_key.pem -outform PEM -nocrypt - 要是
-----BEGIN PRIVATE KEY-----,才是微信要求的PKCS#8,可直接用 - 私钥带密码?微信后台导出时若设置了密码,
openssl_pkey_get_private($pem, $passphrase)第二个参数必须传入,漏掉就加载失败 - ThinkPHP中建议封装加载逻辑,加
is_readable()和openssl_pkey_get_private()返回值判断,别等发请求时才报错
AES-GCM回调验签+解密时tag校验失败(Invalid AEAD tag)
这不是签名问题,而是微信退款结果通知的解密环节卡住了。很多人以为验签通过就能直接json_decode(),其实通知体是AES-256-GCM加密的,必须先完整还原nonce/ciphertext/associated_data/tag四部分。
- 从原始
php://input读取JSON后,必须提取resource.nonce、resource.ciphertext、resource.associated_data、resource.tag四个字段,缺一不可 - 这四个值全是base64编码,要分别
base64_decode();其中tag解码后必须是16字节二进制数据,长度不对会直接触发Invalid AEAD tag -
openssl_decrypt()第五个参数$tag必须传入解码后的tag二进制值,不能传base64字符串,也不能用substr()截断 - 务必先用平台证书公钥验签成功,再执行解密;否则攻击者可伪造加密体绕过验签
最常被忽略的一点:微信平台证书不是一劳永逸的,它会定期轮换。ThinkPHP里如果把平台证书公钥硬编码或缓存成静态变量,某天突然所有回调验签失败,大概率是证书过期了——得监听/v3/certificates接口主动刷新,并落地存储。这个动作不在退款流程里,但决定了整个通知链路是否可靠。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










