微信支付 v3 的证书和密钥,四类东西别再配混了
接微信支付 v3,商户平台上要拿四类东西:APIv3 密钥、API 证书、商户证书序列号、微信支付公钥。
落到配置文件里,这四类会摊成八个字段。网上大部分教程只讲了其中两三个,剩下的等你签名失败的时候自己撞。
踩错的代价
这几样配错,报错长得都一样:SIGN_ERROR,HTTP 401,「签名验证失败」。
它不会告诉你是私钥错了、序列号错了、还是公钥拿反了。八个变量,一个错误码。
更难受的是它不是必现。下单接口能通,回调解密失败——因为下单用的是商户私钥签名,回调用的是 APIv3 密钥解密,两套东西。你以为配好了,钱收不到才发现。
别逐个字段试,按环节反推
八个字段一个个换着试,是最慢的排查方式。按失败发生在哪个环节,能直接圈到某一类上:
| 走到哪一步 | 说明这几个是对的 | 问题只可能在 |
|---|---|---|
| 下单请求就被拒(401) | —— | 商户私钥、证书序列号、商户号 |
| 下单成功,拿到 prepay_id | 私钥、序列号、商户号都对 | —— |
| 收到回调但解密失败 | 签名那套全对 | 只可能是 APIv3 密钥 |
| 解密出来了但验签不过 | 密钥也对 | 微信支付公钥 / 公钥 ID |
下单成功这件事本身就是一个强信号:它证明签名那一套(私钥 + 序列号 + 商户号)全对,剩下的问题不可能在那三个上面。
很多人卡住是因为一看到「签名失败」就把八个字段全怀疑一遍。先看走到哪一步断的,一次能排掉一半。
先分清方向:签名和验签不是同一把钥匙
配之前先把方向理清楚,后面八个字段就不会乱。
签名和验签是两个相反的方向,用的是两套不同的钥匙:
| 我方 → 微信 | 微信 → 我方 | |
|---|---|---|
| 动作 | 给请求签名 | 验证响应和回调的签名 |
| 用什么 | 商户 API 私钥(apiclient_key.pem) | 微信支付公钥 |
| 标识 | 商户证书序列号 | 公钥 ID |
回调报文的解密是第三件事,跟上面两个方向都无关,用 APIv3 密钥。
三件事、三套东西,混在一起就是各种「签名失败」。
四类东西分别是什么
一、APIv3 密钥(32 个字符)
在商户平台的「API 安全」页面里设置(账户中心下面),自己定的 32 个字符。
用途只有两个:解密回调报文(AES-256-GCM)、下载平台证书。
最常见的坑:跟 v2 的「API 密钥」搞混。 商户平台上这是两个独立入口,都是 32 位,都叫密钥。v2 那个做 MD5/HMAC 签名,v3 这个做解密,互相不通用。设置页面还挨在一起。
这个坑不是「不小心」,是结构性的——后面配置那节你会看到,代码里 mchKey 和 apiV3Key 就是两个独立字段,摆着两个坑位等你填错。
二、API 证书(两个文件)
同一个页面申请下载,解压后你要的是两个:
apiclient_key.pem—— 商户 API 私钥,签名用它apiclient_cert.pem—— 商户证书,序列号从它里面读
这个只能下载一次,重新申请会作废旧的。生产环境的证书丢了就是重新申请,中间这段时间收不到钱。
路径用相对路径或从配置注入,不要写绝对路径。 本地跑通了传到服务器上找不到文件。
三、商户证书序列号
从证书里读:
openssl x509 -noout -serial \
-in apiclient_cert.pem输出形如 serial=1A2B3C...,去掉 serial= 前缀,转成大写填进配置。
「API 安全」页面上也能直接看到,两边应该一致。不一致说明你手里的证书不是当前生效的那张——大概率是重新申请过,本地还留着旧的。
四、微信支付公钥 —— 这里 2024 年之后变了
这是全文最容易过时的一节,也是最多人配错的一节。
新申请的商户号:直接在商户平台下载「微信支付公钥」,配一个公钥文件加一个公钥 ID(形如 PUB_KEY_ID_...)。静态的,不会变。
老商户号:两种模式并存。平台证书那套还能用,但公钥模式同样可以申请——
在「账户中心 → API 安全」里,官方也推荐迁过去(平台证书五年一换,公钥不用换)。
如果你还在用平台证书:它不能在页面上直接下载,要用工具或调接口拿,
拿的时候还得用 APIv3 密钥解密。
平台证书模式有个隐藏的坑:证书会轮换。微信提前一段时间下发新证书,旧的到期作废。如果你把平台证书硬编码进代码、或者只在启动时加载一次,某天会突然全线验签失败,而你什么都没改。
官方 SDK 里有自动更新平台证书的实现,别自己写。
落到配置里,实际是八个字段
前面四类摊开是六个字段,加上 appId 和 mchId 一共八个。这张表是我配的时候最想要、但网上找不到的东西:
(先说个数:一个真实项目的微信支付配置类,78 行、12 个字段。
12 个里 4 个是身份——AppID、商户号、子商户 AppID、子商户号;
8 个是凭据。下面这张表是那 8 个里属于 v3 的 6 个,加上 2 个身份字段。
剩下那两个凭据字段属于 v2,本文最后一节单说。)
| 配置字段 | 是什么 | 去哪拿 |
|---|---|---|
appId | 小程序或公众号 AppID | 小程序/公众号后台 |
mchId | 商户号 | 商户平台首页 |
apiV3Key | APIv3 密钥(32 位) | 「API 安全」页面,自己设 |
certSerialNo | 商户证书序列号 | openssl 从证书读,或「API 安全」页看 |
privateCertPath | apiclient_cert.pem 路径 | 申请证书时下载 |
privateKeyPath | apiclient_key.pem 路径 | 同上,同一个压缩包 |
publicKeyPath | 微信支付公钥文件路径 | 商户平台下载 |
publicKeyId | 公钥 ID(PUB_KEY_ID_...) | 跟公钥一起给的 |
配置写出来是这样:
wxpay:
# 小程序或公众号的 AppID —— 注意不是商户号
app-id: wx**********
mch-id: 16**********
# APIv3 密钥,32 位
# 不是 v2 的那个 API 密钥
api-v3-key: ****************************
# 商户证书序列号,大写
# 从 apiclient_cert.pem 里读出来
cert-serial-no: 1A2B3C**********
# 相对路径,别写绝对路径
private-cert-path: wx/apiclient_cert.pem
private-key-path: wx/apiclient_key.pem
# 微信支付公钥模式(新商户号)
public-key-path: wx/pub_key.pem
public-key-id: PUB_KEY_ID_**********配完自己核一遍:证书序列号和商户平台页面上显示的一致,这一条能挡掉一半的签名失败。
还有四个字段不属于 v3,但最容易顺手填错
翻配置的时候你还会看到这几个,它们不是 v3 要的:
mchKey—— v2 的 API 密钥。跟apiV3Key长得一样都是 32 位,填串了就是签名失败keyPath—— v2 的 p12 证书,老版本退款用的subAppId/subMchId—— 服务商模式才用。你是普通商户就留空,填了反而报错
也就是说,那 12 个字段实际分属三套体系:
| 体系 | 字段数 |
|---|---|
v2(mchKey + p12) | 2 |
| v3 证书模式 | 4 |
| v3 公钥模式(2024 年 8 月后的新商户) | 2 |
| 身份(两套都要) | 4 |
一个配置类里同时躺着三代凭据方案,这本身就是最容易配混的原因。
字段名不会告诉你哪个属于哪一代,注释才会——
那两个公钥字段的注释里明写着「2024.08 后的新商户验签需要用此公钥」。
看到一个字段不知道要不要填的时候,先去看它的注释,不要看名字。
最后这对我单独说一句:服务商模式是另一套玩法(分账、子商户),跟普通商户号的配置不通用。看到配置里有这两个字段不代表你需要填。 什么时候才该上服务商模式,我另外写。
与其配完靠试,不如让它启动就炸
上面那张八字段表,配完之后怎么知道对不对?
大部分人的答案是「发一笔一分钱的单试试」。这个答案的问题在于它只能试出一半——
前面说过,下单成功只证明签名那三个字段对,回调解密和验签要等真有人付款才暴露。
更好的做法是:把能在启动时查的都查掉。
我在一个项目里见过这么干的,一个 158 行的配置校验器,跑在启动阶段。
它查这几样:
| 查什么 | 判据 |
|---|---|
| APIv3 密钥 | 有值,且长度必须是 32 位 |
| 商户号、证书序列号 | 有值 |
| 商户私钥、商户证书 | 路径有值,且文件真的读得到 |
| 微信支付公钥、公钥 ID | 两个都有值 |
| 回调地址 | 四个地址(支付、合单、分账、退款)逐个查 |
八个字段里,有六个能在启动时查出问题:长度不对、文件不存在、值是空的。
剩下两个(序列号跟证书对不对得上、公钥 ID 跟公钥文件配不配套)
才真的要发一笔单才知道。
这个校验器最值得抄的不是它查什么,是它怎么报。
它不是查到第一个错就抛,而是把所有错攒起来,编号列全,最后一次性抛,
消息里带一句「共 N 项」。
差别在返工次数:一次抛一个错,你改一个、重启一次、再发现下一个,
八个字段能让你重启五六次。一次抛全,改一轮就完事。
配置错误天生适合攒起来一起报,因为它们之间没有依赖——
不像业务校验,第一步没过后面根本没法算。
顺带一个值得抄的细节:那个校验器把「致命」和「告警」分开。
凭据缺失是致命,直接抛;回调地址没配只是告警,写条日志继续跑——
因为回调地址在有些部署方式下是运行时才拼出来的。
分清这两档,校验器才不会变成大家争着注释掉的东西。
常见报错对照表
| 报错 | 真正的原因 | 怎么查 |
|---|---|---|
SIGN_ERROR / 401 签名验证失败 | certSerialNo 或商户私钥不对 | openssl 重新读一次序列号,跟商户平台核对,注意大写 |
| 下单成功,回调解密报错 | apiV3Key 填成了 v2 的 mchKey | 回「API 安全」页确认是哪个入口的密钥 |
| 验签失败,但签名没问题 | 公钥拿反了,或用商户证书去验签 | 确认验签用的是微信那边的公钥,不是自己的 |
| 昨天还好好的,今天全部验签失败 | 平台证书轮换了 | 换成 SDK 的自动更新,或迁到公钥模式 |
| 本地通,服务器 500,找不到文件 | 证书用了绝对路径 | 改相对路径或从配置注入 |
| 提示子商户号错误 | 普通商户填了 subMchId | 服务商模式才用,普通商户留空 |
NOAUTH | 不是签名问题,是商户号没签约这个产品 | 商户平台的「产品中心」确认 JSAPI/APP 支付已开通 |
