封装获取微信平台证书列表+解密得到微信公钥
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)
})()