封装获取微信平台证书列表+解密得到微信公钥
wx回调本地地址时使用wx证书的公 钥进行解密。
WxPayment //验证签名 timestamp,nonce,serial,signature均在HTTP头中获取,body为请求参数 async verifySign({ timestamp, nonce, serial, body, signature }) { // 拼接参数 let data = `${timestamp}\n${nonce}\n${typeof body == 'string' ? body : JSON.stringify(body)}\n`; // 用crypto模块解密 let verify = crypto.createVerify('RSA-SHA256'); // 添加摘要内容 verify.update(Buffer.from(data)); // 从初始化的平台证书中获取公钥 for (let cert of this.certificates) { if (cert.serial_no == serial) { return verify.verify(cert.public_key, signature, 'base64'); } else { throw new Error('平台证书序列号不相符') } } } //解密证书列表 解出CERTIFICATE以及public key async decodeCertificates() { let result = await this.getCertificates(); if (result.status != 200) { throw new Error('获取证书列表失败') } let certificates = typeof result.data == 'string' ? JSON.parse(result.data).data : result.data.data for (let cert of certificates) { let output = this.dec ode(cert.encrypt_certificate) cert.decrypt_certificate = output.toString() let beginIndex = cert.decrypt_certificate.indexOf('-\n') let endIndex = cert.decrypt_certificate.indexOf('\n-') let str = cert.decrypt_certificate.substring(beginIndex + 2, endIndex) // 生成X.509证书 let x509Certificate = new x509.X509Certificate(Buffer.from(str, 'base64')); let public_key = Buffer.from(x509Certificate.publicKey.rawData).toString('base64') // 平台证书公钥 cert.public_key = `-----BEGIN PUBLIC KEY-----\n` + public_key + `\n-----END PUBLIC KEY-----` } return this.certificates = certificates } //解密 decode(params) { const AUTH_KEY_LENGTH = 16; // ciphertext = 密文,associated_data = 填充内容, nonce = 位移 const { ciphertext, associated_data, nonce } = params; // 密钥 const key_bytes = Buffer.from(this.apiv3_private_key, 'utf8'); // 位移 const nonce_bytes = Buffer.from(nonce, 'utf8'); // 填充内容 const associated_data_bytes = Buffer.from(associated_data, 'utf8'); // 密文Buffer const ciphertext_bytes = Buffer.from(ciphertext, 'base64'); // 计算减去16位长度 const cipherdata_length = ciphertext_bytes.length - AUTH_KEY_LENGTH; // upodata const cipherdata_bytes = ciphertext_bytes.slice(0, cipherdata_length); // tag const auth_tag_bytes = ciphertext_bytes.slice(cipherdata_length, ciphertext_bytes.length); const decipher = crypto.createDecipheriv( 'aes-256-gcm', key_bytes, nonce_bytes ); decipher.setAuthTag(auth_tag_bytes); decipher.setAAD(Buffer.from(associated_data_bytes)); const output = Buffer.concat([ decipher.update(cipherdata_bytes), decipher.final(), ]); return output; }
封装验证微信发起的签名+解密数据得到用户订单信息
开发微信服务器回调接口
callback: async (req) => { let timestamp = req.header('Wechatpay-Timestamp') let nonce = req.header('Wechatpay-Nonce') let serial = req.header('Wechatpay-Serial') let signature = req.header('Wechatpay-Signature') let body = req.body // 1.校验收到的请求是否来自微信服务器和平台证书是否一致 let result = await payment.verifySign({ timestamp, nonce, serial, signature, body }) if (!result) { return } // 2.解密body中的数据,拿到用户订单信息 let bufferoOne = payment.decode(body.resource) let json = JSON.parse(bufferoOne.toString('utf8')) let { out_trade_no, trade_state } = json console.log(json) if (trade_state === 'SUCCESS') { // 3.根据微信服务器返回的订单信息更新数据库中改订单的支付状态 await DB.ProductOrder.update({ order_state: 'PAY' }, { where: { out_trade_no } }) // 4.更新redis课程热门排行榜数据 let productItem = await DB.ProductOrder.findOne({ where: { out_trade_no }, raw: true }) let memberInfo = { id: productItem.product_id, title: productItem.product_title, img: productItem.product_img, } let time = dayjs(Date.now()).format('YYYY-MM-DD') await redisConfig.zincrby({ key: `${time}:rank:hot_product`, increment: 1, member: JSON.stringify(memberInfo) }) } return BackCode.buildSuccess() },
这里redis的逻辑是
DB.ProductOrder.findOne({ where: { out_trade_no }, raw: true })
: 这是一个数据库查询操作,通过out_trade_no
作为查询条件,在ProductOrder
表中查找匹配的记录,并返回一个包含产品项信息的对象。raw: true
选项表示返回原始数据,而不进行模型实例化。- 接下来,将产品项的信息存储到
memberString
对象中,包括产品的 ID、标题和图片。 dayjs(Date.now()).format('YYYY-MM-DD')
: 这行代码使用了第三方库dayjs
,用于获取当前日期并以 'YYYY-MM-DD' 的格式进行格式化。生成的日期字符串被存储在time
变量中。redisConfig.zincrby({ key:
{time}:rank:hot_product, increment: 1, member: JSON.stringify(memberString) })
: 这是一个使用 Redis 的命令,通过zincrby
方法向 Redis 的有序集合中更新数据。key
参数表示要更新的有序集合的键名,这里使用了{time}:rank:hot_product
作为键名;increment
参数表示每次增加的分值,这里设置为 1;member
参数表示要添加或更新的成员,将前面存储的产品项信息以 JSON 字符串的形式作为成员。
每天凌晨清除昨天的数据
定时插件
yarn add node-schedule@2.1.0
配置
// 定时任务工具 const schedule = require('node-schedule') let rule = new schedule.RecurrenceRule() class ScheduleTool { // 每天凌晨0点执行 static dayJob(handle) { rule.hour = 0 schedule.scheduleJob(rule, handle) } } module.exports = ScheduleTool
定时任务执行
ScheduleTool.dayJob(() => { let yesterday = dayjs().subtract(1, 'day').format('YYYY-MM-DD') redisConfig.del(`${yesterday}:rank:hot_product`) })
我们要拿到的就是body.resource
pay.weixin.qq.com/wiki/doc/ap…
订单状态确认-查询订单⽀付状态逻辑封装+快速验证
node服务器主动进行接口调用查询订单
查询订单逻辑封装
//通过out_trade_no查询订单 async getTransactionsByOutTradeNo(params) { return await this.wxSignRequest({ pathParams: params, type: 'getTransactionsByOutTradeNo' }) }
验证订单状态查询
const { payment } = require('./config/wechatPay'); (async () => { let wechatOrder = await payment.getTransactionsByOutTradeNo({ out_trade_no: '123456789wqjeqjwdiqhdhqd' }) console.log(wechatOrder.data) })()