微信支付 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 -in apiclient_cert.pem -noout -serial输出形如 serial=1A2B3C...,去掉 serial= 前缀,转成大写填进配置。
「API 安全」页面上也能直接看到,两边应该一致。不一致说明你手里的证书不是当前生效的那张——大概率是重新申请过,本地还留着旧的。
四、微信支付公钥 —— 这里 2024 年之后变了
这是全文最容易过时的一节,也是最多人配错的一节。
新申请的商户号:直接在商户平台下载「微信支付公钥」,配一个公钥文件加一个公钥 ID(形如 PUB_KEY_ID_...)。静态的,不会变。
老商户号:还是平台证书模式。平台证书不能在页面上直接下载,要用工具或调接口拿,拿的时候还得用 APIv3 密钥解密。
平台证书模式有个隐藏的坑:证书会轮换。微信提前一段时间下发新证书,旧的到期作废。如果你把平台证书硬编码进代码、或者只在启动时加载一次,某天会突然全线验签失败,而你什么都没改。
官方 SDK 里有自动更新平台证书的实现,别自己写。
落到配置里,实际是八个字段
前面四类,摊到配置文件是这八个。这张表是我配的时候最想要、但网上找不到的东西:
| 配置字段 | 是什么 | 去哪拿 |
|---|---|---|
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: cert/apiclient_cert.pem
private-key-path: cert/apiclient_key.pem
# 微信支付公钥模式(新商户号)
public-key-path: cert/pub_key.pem
public-key-id: PUB_KEY_ID_**********配完自己核一遍:证书序列号和商户平台页面上显示的一致,这一条能挡掉一半的签名失败。
还有三个字段不属于 v3,但最容易顺手填错
翻配置的时候你还会看到这几个,它们不是 v3 要的:
mchKey—— v2 的 API 密钥。跟apiV3Key长得一样都是 32 位,填串了就是签名失败keyPath—— v2 的 p12 证书,老版本退款用的subAppId/subMchId—— 服务商模式才用。你是普通商户就留空,填了反而报错
最后这对我单独说一句:服务商模式是另一套玩法(分账、子商户),跟普通商户号的配置不通用。看到配置里有这两个字段不代表你需要填。 什么时候才该上服务商模式,我另外写。
常见报错对照表
| 报错 | 真正的原因 | 怎么查 |
|---|---|---|
SIGN_ERROR / 401 签名验证失败 | certSerialNo 或商户私钥不对 | openssl 重新读一次序列号,跟商户平台核对,注意大写 |
| 下单成功,回调解密报错 | apiV3Key 填成了 v2 的 mchKey | 回「API 安全」页确认是哪个入口的密钥 |
| 验签失败,但签名没问题 | 公钥拿反了,或用商户证书去验签 | 确认验签用的是微信那边的公钥,不是自己的 |
| 昨天还好好的,今天全部验签失败 | 平台证书轮换了 | 换成 SDK 的自动更新,或迁到公钥模式 |
| 本地通,服务器 500,找不到文件 | 证书用了绝对路径 | 改相对路径或从配置注入 |
| 提示子商户号错误 | 普通商户填了 subMchId | 服务商模式才用,普通商户留空 |
NO_AUTH | 不是签名问题,是商户号没签约这个产品 | 商户平台的「产品中心」确认 JSAPI/APP 支付已开通 |
