hpyer / node-easywechat

EasyWeChat for Node.js
MIT License
138 stars 18 forks source link

获取微信支付V3平台证书能否集成框架 #57

Closed ValueLan closed 9 months ago

ValueLan commented 9 months ago

https://github.com/klover2/wechatpay-node-v3-ts/blob/550ea4bbc0e0b238038bd59d7aa2ca71f78a572d/index.ts#L97

ValueLan commented 9 months ago

如果使用第三方获取证书,还是用java 大部分nodejs开发者都不会,难度有点高 建议集成进去

hpyer commented 9 months ago

请更新3.5.9版本,新增了loadPlatformCerts方法用于读取平台证书。

用法:

const app = new Pay({});

// 执行后,平台证书的配置会自动写入商户的相关配置中,无需额外操作
// 注意:如果你在实例化 Pay 的时候,配置了 platform_certs 选项,那么该方法不会执行。
// 但是可以传入一个参数 true 强制从接口获取证书,获取证书后会覆盖原先的 platform_certs 配置
await app.loadPlatformCerts();

// 正常业务逻辑
ValueLan commented 9 months ago

这一行的结构是

{
data : [ {effective_time : 'date string', encrypt_certificate: obj,expire_time:'date string', serial_no: 'string'} ]
}

判断如下
if (data?.data && data.data.length > 0) {
}

https://github.com/hpyer/node-easywechat/blob/701441b9e02b98080b405e073043e4c0cbd162aa/src/Pay/Application.ts#L160

ValueLan commented 9 months ago

可以的话建议 lib里建立个缓存机制,在需要加载的时候自动加载 毕竟没人喜欢配置,配置会很繁琐 虽然我目前看了咱们代码也能配置,但是也需要反复请求证书

1、平台证书的作用猜测应该是和加密有关
然后需要在header上
Wechatpay-Serial: '相关序列号'
app.getUtils().encrypt("张三")

2. 在验证签名到时候需要用到 平台证书,这个是参考咱们这边代码
所以,个人觉得应该和代码强关联在一起,支付请求每次都发送  Wechatpay-Serial  加密也是相关  Serial
参考文档如下
https://pay.weixin.qq.com/docs/partner/apis/contracted-merchant-application/applyment/submit.html

https://pay.weixin.qq.com/docs/partner/apis/platform-certificate/api-v3-get-certificates/get.html
hpyer commented 9 months ago

这一行的结构是

{
data : [ {effective_time : 'date string', encrypt_certificate: obj,expire_time:'date string', serial_no: 'string'} ]
}

判断如下
if (data?.data && data.data.length > 0) {
}

https://github.com/hpyer/node-easywechat/blob/701441b9e02b98080b405e073043e4c0cbd162aa/src/Pay/Application.ts#L160

收到~

另,我看这个证书就是在收到通知回调的时候验证用的,而回调是有处理时间限制的,如果使用的时候才去加载,可能会导致业务处理延时。在业务逻辑之前预处理好证书是有好处的(通过配置项设置也是一样的道理)。 再者,我这毕竟是node版的easywechat,还是要尽量兼容php版本的功能设置的。

ValueLan commented 9 months ago

兼容PHP我能理解,目前看来对方也是不想维护,所以把所有接口搞的灵活起来,

hpyer commented 9 months ago

大家都是普通人,精力总是有限的。再说,微信整个生态越来越大,各种接口不计其数,封装接口烦就不说了,如果方法名取的不好,反而增加开发者的使用成本。

ValueLan commented 9 months ago

大家都是普通人,精力总是有限的。再说,微信整个生态越来越大,各种接口不计其数,封装接口烦就不说了,如果方法名取的不好,反而增加开发者的使用成本。

能理解,所以,咱们只要把平台证书这块搞通,比如可以不用配置 写到redis里,灵活缓存,就很完美,以后万变不离其宗

hpyer commented 9 months ago

这一行的结构是

{
data : [ {effective_time : 'date string', encrypt_certificate: obj,expire_time:'date string', serial_no: 'string'} ]
}

判断如下
if (data?.data && data.data.length > 0) {
}

https://github.com/hpyer/node-easywechat/blob/701441b9e02b98080b405e073043e4c0cbd162aa/src/Pay/Application.ts#L160

3.5.10版本已修复这个问题

ValueLan commented 9 months ago

我意思是,人家JAVA实现也是通过代码实现, 咱们能否把证书缓存起来,在需要的时候读取redis,或者远程证书, 场景案例1 V3验证签名,微信会返回证书序列号, 这个时候只需要远程请求证书 并且根据证书对验证签名

场景案例2 发送Wechatpay-Serial 给特殊字段签名,这个时候需要拿最新的发给微信

ValueLan commented 9 months ago

个人表达的可能不是太好, 如果不明白,希望能继续讨论

ValueLan commented 9 months ago

以我目前来看 php 版本的 easywechat, 在证书这块,肯定是做的很差


https://pay.weixin.qq.com/docs/partner/apis/platform-certificate/api-v3-get-certificates/get.html
这是官方文档的说明,
他开源的是PHP代码,但是让用户用java手动去生成证书,
而且间隔要求是12小时内,这么高频,明显不适合

程序实现定期更新平台证书的逻辑,不要硬编码验证应答消息签名的平台证书。
定期调用该接口,间隔时间小于12 小时。
加密请求消息中的敏感信息时,使用最新的平台证书(即:证书启用时间较晚的证书)。
``
hpyer commented 9 months ago

请更新 3.5.12。 原来的 app.loadPlatformCerts(); 方法挪动为 app.getMerchant().loadPlatformCerts(),且会在未配置 platform_certs 且需要使用平台的证书的情况下自动从接口获取并写入缓存(缓存时效10小时)。可以通过app.getMerchant().setPlatformCertKey()方法设置缓存名。如果需要强制刷新缓存,执行 app.getMerchant().loadPlatformCerts(true) 即可。

ValueLan commented 9 months ago

loadPlatformCerts 目前还没测试

加密我觉得有问题,加密
https://pay.weixin.qq.com/docs/partner/apis/contracted-merchant-application/applyment/submit.html
根据如上文档指引,需要最新平台证书序列号,应该是个异步方法,
比如取证书最后一条
https://github.com/hpyer/node-easywechat/blob/b6461b06e989c019088569d5598fad9405615042/src/Pay/Utils.ts#L20

解密方法,是否应该也要传平台证书号
参考验证支付
https://github.com/hpyer/node-easywechat/blob/b6461b06e989c019088569d5598fad9405615042/src/Pay/Validator.ts#L46C9-L46C19
解密这块也许是我搞错,但是加密一定是有问题
https://github.com/hpyer/node-easywechat/blob/b6461b06e989c019088569d5598fad9405615042/src/Pay/Utils.ts#L32
hpyer commented 9 months ago

确实搞错了,加密要用的是平台证书,我这想当然的用了API证书。

解密用的API证书中的私钥。 https://pay.weixin.qq.com/docs/partner/development/interface-rules/sensitive-data-encryption.html 这个链接里,解密示例章节的原话微信支付使用商户API证书中的公钥对下行的敏感信息进行加密。开发者应使用商户私钥对下行的敏感信息的密文进行解密。

ValueLan commented 9 months ago

关于 app.getMerchant().loadPlatformCerts,我想修改下,如果您这边没时间的话 下面两个 我想改成异步方法用于自动获取证书,只要有验证 可以的话加个微信讨论也可以 pay.Validator.validate
merchant.getPlatformCert

hpyer commented 9 months ago

这两个方法已经是异步的了

ValueLan commented 9 months ago

非常完美 encrypt 也可以搞个异步 解密就没必要了 loadPlatformCerts 开发者这个方法都没必要调用

ValueLan commented 9 months ago

需要新增个方法,用于header里添加 getLastSerial()

hpyer commented 9 months ago

更新到3.5.13。加密用法:

const app = new Pay({});
const merchant = app.getMerchant();
const utils = app.getUtils();

// 获取所有证书,键值对 { 序列号: PublicKey实例 }
// 如果要数组的,Object.values(certs) 就可以
let certs = await merchant.getPlatformCerts();

// 指定要使用的证书(可选)
utils.setPlatformCert(platformCert);

// 获取使用的证书
// 默认会取 certs[Object.keys(certs)[0]]
let cert = await utils.getPlatformCert();

// 获取证书序列号
let serialNo = cert.getSerialNo();

// 加密
let ciphertext = await utils.encrypt('str');
ValueLan commented 9 months ago

let cert = await utils.getPlatformCert(); 其他我感觉没问题,我就怕不是最新的证书,是否还要我自己获取计算下? 当然没测试过,纯属自己推测

hpyer commented 9 months ago

不放心的话可以每次用之前,调用 setPlatformCert 指定一下使用的证书

hpyer commented 9 months ago

于此同时,loadPlatformCerts(true) 这个方法的存在,就有作用了,,你可以另外跑一个定时任务,自行去刷新平台证书的缓存。

hpyer commented 9 months ago

等等,这个逻辑可能是要调整一下。 如果app实例不是每次都new的话,可能一直用第一次获取的证书。

hpyer commented 9 months ago

更新3.5.14,加解密的方式要调整一下

const app = new Pay({});
const utils = app.getUtils();

// 获取使用的证书(异步)
// 默认会取 certs[Object.keys(certs)[0]]
let cert = await utils.getPlatformCert();

// 获取证书序列号(非异步)
let serialNo = cert.getSerialNo();

// 获取加密机(异步)
// 这里的参数cert是可选的,不传会自动调用 utils.getPlatformCert() 来获取
// 但为了避免极端情况的出现,建议还是先获取证书,然后传给加密机
let encryptor = await utils.getEncryptor(cert);

// 加密(非异步)
let ciphertext = encryptor.encrypt('str');
// 解密(非异步)
let plaintext = encryptor.decrypt('xxxx');
ValueLan commented 9 months ago

const app = new Pay({});
const utils = app.getUtils();
let encryptor = await utils.getEncryptor();
encryptor .encrypt('str')
encryptor.getSerialNo()

能否这样

hpyer commented 9 months ago

const app = new Pay({});
const utils = app.getUtils();
let encryptor = await utils.getEncryptor();
encryptor .encrypt('str')
encryptor.getSerialNo()

能否这样

可以,那就不能直接返回RSA实例了

hpyer commented 9 months ago

请更新 3.5.15,之前 3.5.14 那种指定证书的方式也还是保留着。

ValueLan commented 9 months ago

感谢