写在开头:
其实微信支付侧在很早~很早~~很早~~~之前就打算将平台证书抛弃改换公钥模式进行敏感信息加密和回调签名验证了,平台证书只有5年有效期,现在的开发者只会管当下“能run就行”,哪会考虑到5年之后平台证书过期的事情,导致在首次平台证书过期时间点(2023年末2024年初)很多线上平稳运营多年的系统就突然炸了,还一时不容易定位问题。
在解决平台证书问题上,微信支付推出了新的公钥模式,由于前期一直在各种线下会议上宣传,没有给出正式通知和明确的使用指引,高估了大部分开发者的情况,再加上很突然的线上灰度,盲猜内部没协调好引发新申请商户号调用“获取平台证书”接口出现已读乱回的情况,社区因此出现大量此类报错提问,可以看出很多开发着开始迷茫了:“我是谁?我在哪?为啥同样代码其他商户号都正常?为啥我’差不多‘时间申请的商户号其他的能有,就这不能用?”。
出于作为“用爱发电的热心网友”,就简单写个让大家能看得懂的说明吧。
PS:其实我们要感谢微信支付团队,给各位开发者多找点事情做,避免大家被“毕业”,要感谢他们的良苦用心~!
吐槽两句
真不知道负责这块接口设计的人是咋想的,在不知情需要获取公钥的情况下,你获取平台证书接口提示个“证书不存在”是写给谁看?写的报错提示谁能直接看得出来你想表达的意思?脑回路清奇,建议拉出去弹吉他十分钟!!!
问题说明
微信支付对于新申请商户号以及平台证书过期商户已不再签发平台证书,需要更换使用微信支付平台公钥进行敏感信息加密、通知回调签名验证,因此此类商户号调用获取平台证书接口时会出现报错“证书不存在”或者“系统繁忙”的情况。
解决方案
目前会存在两种情况,一种是新申请商户号商户后台没有“平台证书”管理入口,另一种为存量商户存在有在有效期的平台证书和平台证书过期没有签发,第一种可以直接在商户后台->账户中心->API安全->启用“微信支付公钥”,下面教程主要以存量商户切换公钥进行说明(懒得申请新商户号了,拿个白名单商户来写的)。
1.1获取商户对应的平台公钥
商户后台->账户中心->API安全->申请“微信支付公钥“,在点击申请的时候会提示你查看指引,要点查看才可以进行公钥申请!!!
下载公钥
点击“下载公钥”后会自动下载文件名为’pub_key.pem’的公钥并在后台生成“PUB_KEY_ID”开头的公钥ID(丢了也没事,后台可以重复下载,公钥ID不变)
1.2 接口开发
下面所写示例说明均使用官方sdk,仅供参考
PHP
同时支持平台证书和平台公钥两种方法,在返回的wechatpay-serial值,在certs里有定义,就会自动匹配
// 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名,这里直接使用前面从后台获取的微信支付平台公钥;
$platformCertificateOrPublicKeyFilePath = 'file:///path/to/wechatpay/certificate_or_publickey.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 「微信支付平台证书」的「证书序列号」或者是「微信支付平台公钥ID」
// 「平台证书序列号」及/或「平台公钥ID」可以从 商户平台 -> 账户中心 -> API安全 直接查询到,这里直接写前面从后台获取的微信支付平台公钥ID,注意要带上'PUB_KEY_ID_'
$platformCertificateSerialOrPublicKeyId = 'PUB_KEY_ID_0114232134912410000000000000';
java
将原“RSAAutoCertificateConfig.Builder”配置改为使用“RSAPublicKeyConfig.Builder”,publicKeyId填写前面从后台获取的微信支付平台公钥ID,注意要带上’PUB_KEY_ID_’
// 可以根据实际情况使用publicKeyFromPath或publicKey加载公钥
Config config =
new RSAPublicKeyConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.publicKeyFromPath(publicKeyPath)
.publicKeyId(publicKeyId)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
go
仅可使用微信支付的公钥验证应答和回调的签名,使用公钥ID初始化
var (
wechatpayPublicKeyID string = "PUB_KEY_ID_0114232134912410000000000000" // 微信支付公钥ID
)
wechatpayPublicKey, err = utils.LoadPublicKeyWithPath("/path/to/wechatpay/pub_key.pem")
if err != nil {
panic(fmt.Errorf("load wechatpay public key err:%s", err.Error()))
}
// 初始化 Client
opts := []core.ClientOption{
option.WithWechatPayPublicKeyAuthCipher(
mchID,
mchCertificateSerialNumber, mchPrivateKey,
wechatpayPublicKeyID, wechatpayPublicKey),
}
client, err := core.NewClient(ctx, opts...)
// 初始化 notify.Handler
handler := notify.NewNotifyHandler(
mchAPIv3Key,
verifiers.NewSHA256WithRSAPubkeyVerifier(wechatpayPublicKeyID, *wechatPayPublicKey))
1.3以上是使用sdk的参考,不使用sdk的情况下,可以参考下面链接内的示例(应该能跑,没测试):点我查看
1.4 开发对接完成后,在商户后台更换验签方式
此操作可在灰度完成之前操作终止,操作需要超管进行操作并进行安全验证,更换以后回调灰度进度由平台控制在7天内完成,应答进度由商户请求参数控制,更换完成后才可以进行平台证书作废操作。
兄弟们,请注意:go sdk需要升级到 v0.2.20
这个我们支付用的vp2,但目前因为商家转账到零钱必须vp3,现在还必须要用公钥, 那会影响到我们原有的vp2支付逻辑吗?
从哪里找客服申请
难道说 不让你通过api刷新证书,而是手动去商户平台 后台 拉数据 再手动更新到 服务器?
恕我愚钝,平台证书和平台公钥 不是一个东西吗?本质不都是RSA 公私钥吗? 平台公钥一样会过期啊。
服了他们了,问题查半天,一直提示找不到证书,接口下载证书确认没有,平台没地方配置,排队等人工几个小时才知道,前面一点通知那是没有的。
go 初始化出来的client,如下:
client, err := core.NewClient(ctx, opts...)
是不是能够复用的? 是不是线程安全的?
这样事先一声没说,导致后台接口挂了。。。。。
楼主,我没太看明白,我们使用的是官方给的sdk,这里包含了自动下载 的逻辑,意思是不使用官方sdk 吗?
private synchronized void downloadAndUpdateCert(String merchantId, Verifier verifier, Credentials credentials, byte[] apiV3Key) throws HttpCodeException, IOException, GeneralSecurityException { try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() .withCredentials(credentials) .withValidator(verifier == null ? (response) -> true : new WechatPay2Validator(verifier)) .withProxy(proxy) .build()) { HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH); httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString()); try (CloseableHttpResponse response = httpClient.execute(httpGet)) { int statusCode = response.getStatusLine().getStatusCode(); String body = EntityUtils.toString(response.getEntity()); if (statusCode == SC_OK) { Map<BigInteger, X509Certificate> newCertList = CertSerializeUtil.deserializeToCerts(apiV3Key, body); if (newCertList.isEmpty()) { log.warn("Cert list is empty"); return; } ConcurrentHashMap<BigInteger, X509Certificate> merchantCertificates = certificates.get(merchantId); merchantCertificates.clear(); merchantCertificates.putAll(newCertList); } else { log.error("Auto update cert failed, statusCode = {}, body = {}", statusCode, body); throw new HttpCodeException("下载平台证书返回状态码异常,状态码为:" + statusCode); } } } }
wechatpay-java https://github.com/wechatpay-apiv3/wechatpay-java支持
wechatpay-apache-httpclient https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient 不支持
非常感谢楼主解答,这个问题属实卡主了。