使用node处理微信小程序的v3支付与支付回调

记录一次使用node处理微信小程序的v3支付与支付回调

预付款签名生成

比较不好理解的就是签名怎么生成,理解过后其实也挺简单的。下面代码可以直接使用,某些关键参数使用自己的就可以了。 文档链接

    import { randomBytes, createPrivateKey, sign } from 'crypto'
    import { readFileSync } from 'fs'

    const tradeNo = '' // 根据自己业务逻辑生成订单号
    const openId = '' // 小程序支付用户的openId
    const goodsAmount = 1 // 支付金额:单位分
    const goodsName = 'test支付' // 显示在支付时商品名称
    const mchid = '' // 商户号
    const wxAppid = '' // 小程序appid
    const wxPayHost = 'https://api.mch.weixin.qq.com'
    const wxPayApi = '/v3/pay/transactions/jsapi'
    const apiUrl = wxPayHost + wxPayApi
    // 生成签名
    const generateSignature = (method, url, timestamp, nonce, data) => {
        const key = getWechatPrivateKeyInstance()
        const privateKey = createPrivateKey(key);
        const message = method + '\n' + url + '\n' + timestamp + '\n' + nonce + '\n' + data + '\n';
        const signature = sign('sha256', Buffer.from(message), { key: privateKey }).toString('base64');
        return signature;
    }
    // 读取商户API证书密钥
    const getWechatPrivateKeyInstance = (): Buffer => {
        const merchantPrivateKeyFilePath = 'pem/apiclient_key.pem'; // 证书文件官方后台生成下载
        return readFileSync(resolve(merchantPrivateKeyFilePath));
    }

    // 微信调起支付所需参数,可以参考文档
    const createOrderParams = (money: number, orderName: string, openId: string, tradeNo: string) => {
        return {
            mchid: mchid,
            out_trade_no: tradeNo,
            appid: this.wxAppid,
            description: orderName,
            notify_url: this.notifyUrl,
            amount: {
                total: money,
                currency: 'CNY'
            },
            payer: {
                openid: openId
            }
        }
    }
    // 固定格式
    const getPrepayIdString = (prepay_id: string) => {
        return "prepay_id=" + prepay_id
    }
    const getPaySign = (prepay_id: string)  => {
        const getPrepayIdString = getPrepayIdString(prepay_id)
        const key = getWechatPrivateKeyInstance()
        const privateKey = createPrivateKey(key);
        const nonce = randomBytes(16).toString('hex');
        const timestamp = Math.floor(Date.now() / 1000).toString();
        const message = wxAppid + '\n' + timestamp + '\n' + nonce + '\n' + getPrepayIdString + '\n';
        const signature = sign('sha256', Buffer.from(message), { key: privateKey }).toString('base64');
        return {
            timestamp,
            nonce,
            package: getPrepayIdString,
            signType: 'RSA', // 签名类型,默认为RSA,仅支持RSA
            paySign: signature
        }
    }

    const requestData = createOrderParams(goodsAmount, goodsName, openId, tradeNo) 

    const timestamp = Math.floor(Date.now() / 1000).toString();
    // 获取当前时间戳
    // 生成随机字符串
    const nonce = randomBytes(16).toString('hex');
    // 生成签名
    const signature = await generateSignature('POST', wxPayApi, timestamp, nonce, JSON.stringify(requestData));
    // 构造Authorization头部
    const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonce}",timestamp="${timestamp}",serial_no="${this.serial}",signature="${signature}"`;

    // 发起微信预付款请求
    axios.post(apiUrl, requestData, {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': this.authorization
        },
      }).then(async res => {
        // 拿到调起支付所需prepay_id
        const prepay_id = res.data.prepay_id

        // 拿到了prepay_id 需要去组串调起支付所需参数
        // https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-transfer-payment.html
        getPaySign(prepay_id)

      })



 以上流程就能够达到调起小程序微信支付的步骤,当微信支付完成过后,微信小程序官方会调用自己项目提供的notify接口以获取微信支付回调

微信支付回调解密处理

import { createPrivateKey } from 'crypto'
// 微信回调解密
const decodePayNotify = (resource: wxPayCallbackBodyType['resource']): wxPayCallbackDecodeType => {
    const v3Key = '' // 微信后台获取 v3 key
    try {
      const { ciphertext, associated_data, nonce } = resource
      const AUTH_KEY_LENGTH = 16
      // 密钥
      const key_bytes = Buffer.from(v3Key, 'utf-8')
      // 随机串
      const nonce_bytes = Buffer.from(nonce, 'utf-8')
      // 填充内容
      const associated_data_bytes = Buffer.from(associated_data, 'utf-8')
      // 密文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 = 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(),
      ]);
      // 解密后 转成 JSON 格式输出
      return JSON.parse(output.toString('utf8'));
    } catch (error) {
      console.log('解密失败', error)
      throw error
    }

  }


// 提供notify接口,需要在微信后台配置回调地址,比如接口地址时:/pay/notify。就需要提供路由为https://xxx/pay/notify的接口给微信调用
const notify = (req) => {
    console.log('微信回调')
    const data: wxPayCallbackBodyType = req?.body || {}

    if (data.event_type === 'TRANSACTION.SUCCESS') {
        const decodeInfo = decodePayNotify(data.resource)
        console.log('支付回调解析数据:', decodeInfo)
    }
}


// 微信回调内容
interface wxPayCallbackDecodeType {
	mchid: string
	appid: string
	out_trade_no: string
	transaction_id: string
	trade_state: string
	trade_state_desc: string
	bank_type: string
	attach: string
	success_time: string
	payer: { openid: string },
	amount: { total: number, payer_total: number, currency: string, payer_currency: string }
}
// 微信回调参数
interface wxPayCallbackBodyType {
	id: string
	create_time: number
	resource_type: string
	event_type: string
	summary: string
	resource: {
		original_type: string
		algorithm: string
		ciphertext: string
		associated_data: string
		nonce: string
	}
}

至此,微信小程序支付比较关键的预付款加密和回调解密都正常走通啦。当然还有一些其他逻辑,比如超时的预付款订单需要主动触发微信关闭订单接口,在此就不演示了。

文章转自:https://juejin.cn/post/7372377436464791578

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值