微信支付之APIv3

1.安装依赖包

仓库地址:https://github.com/TheNorthMemory/wechatpay-axios-plugin
安装命令:npm install wechatpay-axios-plugin

2.生成v3平台证书

wxpay crt -m 1602388066 -s 53F5D1A7289C36ACCBB359DD591E52A5408FCB3C -f F:/zhangjia/projectWXAPIv3/static/pem/wx/apiclient_key.pem -k pppppppppppppppppppppppp -o F:/zhangjia/projectWXAPIv3/static/pem/wx
· 参数解释
 wxpay crt -m {商户号} -s {商户证书序列号} -f {商户API私钥文件路径} -k {APIv3密钥(32字节)} -o {生成证书保存地址}
· 参数完整解释
wxpay crt

The WeChatPay APIv3's Certificate Downloader

cert
  -m, --mchid       The merchant's ID, aka mchid.  [string] [required]
  -s, --serialno    The serial number of the merchant's certificate aka serialno.  [string] [required]
  -f, --privatekey  The path of the merchant's private key certificate aka privatekey.  [string] [required]
  -k, --key         The secret key string of the merchant's APIv3 aka key.  [string] [required]
  -o, --output      Path to output the downloaded WeChatPay's platform certificate(s)  [string] [default: "/tmp"]

Options:
      --version  Show version number  [boolean]
      --help     Show help  [boolean]
  -u, --baseURL  The baseURL  [string] [default: "https://api.mch.weixin.qq.com/"]

3.初始化

· 引用导入包
const { Wechatpay } = require('wechatpay-axios-plugin');
const { readFileSync } = require('fs');
· 初始化各项参数
// 商户号
const merchantId = '1602388066';
// 商户证书序列号
// 也可以使用openssl命令行获取证书序列号
// openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'
const merchantCertificateSerial = '53F5D1A7289C36ACCBB359DD591E52A5408FCB3C';// API证书不重置,商户证书序列号就是个常量
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
const merchantPrivateKeyFilePath = './static/pem/wx/apiclient_key.pem';
// 商户API私钥 PEM格式的文本字符串或者文件buffer
const merchantPrivateKeyInstance = readFileSync(merchantPrivateKeyFilePath);
// 第2小节中生成的平台证书,可由下载器 `wxpay crt -m {商户号} -s {商户证书序列号} -f {商户API私钥文件路径} -k {APIv3密钥(32字节)} -o {保存地址}`
// 载器生成并假定保存为 `./static/pem/wx/wechatpay_61164AFB43674E6C3C4D3C09EFB175596CBE2EA8.pem`
const platformCertificateFilePath = './static/pem/wx/wechatpay_61164AFB43674E6C3C4D3C09EFB175596CBE2EA8.pem';
const platformCertificateInstance = readFileSync(platformCertificateFilePath);
// 平台证书序列号,下载器下载后有提示序列号字段,也可由命令行查询:
// openssl x509 -in ./static/pem/wx/wechatpay_61164AFB43674E6C3C4D3C09EFB175596CBE2EA8.pem -noout -serial | awk -F= '{print $2}'
const platformCertificateSerial = '61164AFB43674E6C3C4D3C09EFB175596CBE2EA8';
const wxpay = new Wechatpay({
    mchid: merchantId,
    serial: merchantCertificateSerial,
    privateKey: merchantPrivateKeyInstance,
    certs: {[platformCertificateSerial]: platformCertificateInstance,},
    // 使用APIv2时,需要至少设置 `secret`字段,示例代码未开启
    // APIv2密钥(32字节)
    // secret: 'your_merchant_secret_key_string',
    // // 接口不要求证书情形,例如仅收款merchant对象参数可选
    // merchant: {
    //   cert: readFileSync('/path/to/merchant/apiclient_cert.pem'),
    //   key: merchantPrivateKeyInstance,
    //   // or
    //   // passphrase: 'your_merchant_id',
    //   // pfx: fs.readFileSync('/your/merchant/cert/apiclient_cert.p12'),
    // },
});

4.前端页面JSAPI调起支付API

· 调起方法  WeixinJSBridge.invoke (只在微信浏览器内起作用)

WeixinJSBridge.invoke(
              'getBrandWCPayRequest', {
              "appId": data.appId,   //公众号名称,由商户传入
              "timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
              "nonceStr": data.nonceStr, //随机串
              "package": data.package,//"prepay_id=up_wx2120185573033"
              "signType": data.signType, //微信签名方式
              "paySign": data.paySign//微信签名
            },
             function (res) {
               if (res.err_msg == "get_brand_wcpay_request:ok") {
                   // 使用以上方式判断前端返回,微信团队郑重提示:
                   //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
            }
       });
注意:
· 以上字段均通过post后端接口得到,如后续小节的  unifiedorderAPIV3  下单接口;
· 其中 微信签名 需要加密算法单独计算。

5.APIv3数据签名(后台下单接口返回数据时使用)

· 引入依赖包

const {Rsa, Formatter} = require('wechatpay-axios-plugin')
const privateKey = require('fs').readFileSync('/your/merchant/priviate_key.pem')

· 数据签名加密算法
const params = {
                appId: config.app_id,//应用ID
                timeStamp: `${Formatter.timestamp()}`,//时间戳
                nonceStr: Formatter.nonce(),//随机字符串,32位数
                package: 'prepay_id='+res.data.prepay_id,//订单详情扩展字符串,prepay_id=wx1111
                signType: 'RSA',
            }
            params.paySign = Rsa.sign(Formatter.joinedByLineFeed(
                params.appId, params.timeStamp, params.nonceStr, params.package
            ), privateKey)

            console.info(params)

6.后台下单接口方法详细内容(JSAPI)v3

· JSAPI合单支付下单
wxpay.v3.combineTransactions.jsapi
  .post({/*文档参数放这里就好*/})
  .then(res => console.info(res.data))
  .catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))
 · JSAPI下单举例,此处将APIv3数据签名方法付诸实践,并将结果返回给前端调起支付页面
详细参数含义见微信开发文档 :
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
app.post('/unifiedorderAPIV3', function (req, response) {
    const openid = req.body.openid;
    // 商户订单号
    const out_trade_no = 'test' + new Date().getTime();
    // 统一下单的相关参数
    wxpay.v3.pay.transactions.jsapi
        .post({
            mchid: config.mch_id,
            out_trade_no: out_trade_no,
            appid: config.app_id,
            description: '测试jsapi支付功能',
            notify_url: 'https://weixin.qq.com/',
            amount: {
                total: 1,
                currency: 'CNY'
            },
            payer: {
                openid: openid
            }
        })
        .then(res => {
            // APIv3数据签名
            const params = {
                appId: config.app_id,
                timeStamp: `${Formatter.timestamp()}`,
                nonceStr: Formatter.nonce(),
                package: 'prepay_id='+res.data.prepay_id,
                signType: 'RSA',
            }
            params.paySign = Rsa.sign(Formatter.joinedByLineFeed(
                params.appId, params.timeStamp, params.nonceStr, params.package
            ), privateKey)

            //console.info(params)
            response.send(params);// APIv3数据签名后返回给前端

            // response.send(res.data);
        })
        .catch(({response: {status, statusText, data}}) => console.log(status, statusText, data))
});

7.notify_url 对应后台方法

· 微信支付后,支付结果通知会以 post 方式发送到 notify_url 对应地址,如 notify_url 的具体地址为  xxxx/wxresponse 时候:

//微信支付后回调地址
app.post('/wxresponse', function (req, res) {
    console.log(req.body);
    let key = `zhangjia123456789123456789123456`;
    let nonce = req.body.resource.nonce;  // 加密使用的随机串
    let associated_data = req.body.resource.associated_data;  // 加密用的附加数据
    let ciphertext = req.body.resource.ciphertext;  // 加密体 base64

    let payData = weixin_PAY.V3decodeByAES(undefined, ciphertext, nonce, associated_data);//第一个参数有默认值
    console.log(payData);
    // 订单状态更新操作
    res.send({
        "code": "SUCCESS",
        "message": "成功"
    });
});

·微信支付结果通知数据格式见【微信官方文档】

·其中加密数据解密方法具体为:

/**
 * 解密微信支付或退款通知数据数据
 * 使用AEAD_AES_256_GCM解密数据
 * @param cipherText 密文
 * @param key API V3密钥
 * @param nonce nonce字符串
 * @param add associated_data字符串
 */
function V3decodeByAES(key = config.mch_V3_key, ciphertext, nonce, associated_data) {
    // 解密 ciphertext字符  AEAD_AES_256_GCM算法
    ciphertext = Buffer.from(ciphertext, 'base64');
    let authTag = ciphertext.slice(ciphertext.length - 16);
    let data = ciphertext.slice(0, ciphertext.length - 16);
    let decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
    decipher.setAuthTag(authTag);
    decipher.setAAD(Buffer.from(associated_data));
    let decoded = decipher.update(data, null, 'utf8');
    decipher.final();
    let payData = JSON.parse(decoded); //解密后的数据
    return payData;
}
参考:https://github.com/TheNorthMemory/wechatpay-axios-plugin
(0)

相关推荐