zoukankan      html  css  js  c++  java
  • node-koa2 微信支付-企业付款到银行卡

    微信支付用的V2版本

    微信支付说明文档:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=24_2   参数详细说明请自行查看

    提示语:

    微信支付2.0还是xml传输数据,用到了解析模块xml2js

    可以自行创建一个wxpay.js,将下面获取公钥和付款到银行卡的代码贴进去

    特别说明一下:付款到银行卡,不仅需要证书,还需要用微信提供的公钥(下面有获取代码示例),对收款方姓名和收款银行卡号,进行RSA加密,

    上代码,

    获取微信提供的加密公钥

    /**
     * 获取公钥
     * 
     * 获取企业付款到银行卡的加密公钥
     */
    exports.getpublickey = async (payConfig) => {
        let mch_id = payConfig.Wechat_merchant_number
        let mchkey = payConfig.Wechat_pay_key
        let url = 'https://fraud.mch.weixin.qq.com/risk/getpublickey'
        let formData = "<xml>"
    
        let mapInfo = {}
        // 商户号
        mapInfo.mch_id = mch_id
        // 随机字符串
        mapInfo.nonce_str = wxpay.getRnd32()
        // 商户密钥
        mapInfo.mchkey = mchkey
        // 加密方式
        mapInfo.sign_type = "MD5"
        // 签名
        let sign = wxpay.getpublickkeySign(mapInfo)
    
        formData += "<mch_id>" + mch_id + "</mch_id>"
        formData += "<nonce_str>" + mapInfo.nonce_str + "</nonce_str>"
        formData += "<sign>" + sign + "</sign>"
        formData += "<sign_type>MD5</sign_type>"
        formData += "</xml>"
        return new Promise((resolve, reject) => {
            request({
                url: url,
                agentOptions: {
                    cert: fs.readFileSync(path.resolve(`./public/config/cert/${payConfig.wx_cert}`)),
                    key: fs.readFileSync(path.resolve(`./public/config/cert/${payConfig.wx_key}`))
                },
                method: 'post',
                body: formData,
            }, function (err, response, body) {
                if (!err && response.statusCode == 200) {
                    let parser = new xml2js.Parser({
                        trim: true,
                        explicitArray: false,
                        explicitRoot: false
                    }); //解析签名结果xml转json
                    parser.parseString(body, (err, res) => {
                        console.log(res)
                        // console.log(res)
    
                        if (res.return_code == 'FAIL') {
                            reject(res.return_msg)
                        }
                        // return_code是success 的话, 只代表退款业务已受理, 并不代表已退款成功
                        // result_code是success 的话, 才算是退款成功, fail的话,返回错误信息
                        if (res.return_code == 'SUCCESS' && res.result_code == 'FAIL') {
                            reject(res.err_code_des)
                        } else {
                            resolve(res.pub_key)
                        }
                    })
                }
                reject(err)
            })
        })
    }

    微信支付付款到银行卡的方法封装

    const fs = require('fs')
    const path = require('path')
    const xml2js = require('xml2js')
    const request = require('request')
    const crypto = require('crypto')
    const NodeRSA = require('node-rsa');
    
    /**
     * RSA加密
    *
    * RSA加密可以用crypto模块。也可以使用node-rsa模块,参考如下连接:https://www.cnblogs.com/huangdaozhang/p/11109417.html 
    */ let encryptRSA = (key, hash) => { return crypto.publicEncrypt(key, Buffer.from(hash)).toString('base64') } /** * 付款到银行卡 */ exports.payBank = async (map) => { let publicKey = map.publicKey let url = 'https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank' let mapInfo = {} // 商户号 mapInfo.mch_id = '' // 商户密钥 mapInfo.mchkey = '' // 商户付款单号 mapInfo.partner_trade_no = map.recordId // 随机字符串 mapInfo.nonce_str = payUtil.getRnd32() // 收款方开户行银行编号 mapInfo.bank_code = map.bankCode // 收款方用户名 mapInfo.enc_true_name = encryptRSA(publicKey, map.payee) // 收款方银行卡号 mapInfo.enc_bank_no = encryptRSA(publicKey, map.receivingAccount) // 付款金额 mapInfo.amount = Math.round(map.amount * 100 * 100) / 100 // 付款说明 mapInfo.desc = map.desc // 签名 let sign = payUtil.payBankSign(mapInfo) // console.log(publicKey) // const a_public_key = new NodeRSA(publicKey); let formData = "<xml>"; formData += "<amount>" + mapInfo.amount + "</amount>"; formData += "<bank_code>" + mapInfo.bank_code + "</bank_code>"; // formData += "<desc>" + mapInfo.desc + "</desc>"; formData += "<enc_bank_no>" + mapInfo.enc_bank_no + "</enc_bank_no>"; formData += "<enc_true_name>" + mapInfo.enc_true_name + "</enc_true_name>"; formData += "<mch_id>" + mapInfo.mch_id + "</mch_id>"; formData += "<nonce_str>" + mapInfo.nonce_str + "</nonce_str>"; formData += "<partner_trade_no>" + mapInfo.partner_trade_no + "</partner_trade_no>"; formData += "<sign>" + sign + "</sign>"; formData += "</xml>"; console.log(formData) return new Promise((resolve, reject) => { request({ url: url, agentOptions: { cert: fs.readFileSync(path.resolve(`./public/config/cert/${payConfig.wx_cert}`)), key: fs.readFileSync(path.resolve(`./public/config/cert/${payConfig.wx_key}`)) }, method: 'post', body: formData, }, function (err, response, body) { if (!err && response.statusCode == 200) { let parser = new xml2js.Parser({ trim: true, explicitArray: false, explicitRoot: false }); //解析签名结果xml转json parser.parseString(body, (err, res) => { console.log(res) let result = { recordId: mapInfo.partner_trade_no } if (res.return_code == 'FAIL') { result.msg = res.return_msg reject(result) } // return_code是success 的话, 只代表退款业务已受理, 并不代表已退款成功 // result_code是success 的话, 才算是退款成功, fail的话,返回错误信息 if (res.return_code == 'SUCCESS' && res.result_code == 'FAIL') { result.msg = res.err_code_des reject(result) } else { result.msg = '' resolve(result) } }) } reject(err) }) }) }

    1. 接下来获取加密公钥,获取成功后,可以写入文件,,本人比较懒,没有搞呢。是不会变化的这个公钥

    2. 获取公钥成功后,调用payBank方法,实现付款到银行卡

    // 微信支付方法路径自行修改成自己的
    const { payBank } = require('./wxpay')
    const { createOrderNumber } = require('./payUtil')
    
    router.post('/', async () => {
      let map = {
    // 金额
        amount: 1,
        // 备注
        desc: '提现',
        // 用户openid
        openid: '',
        // 系统内部流水号
        recordId: createOrderNumber(),
    }
    // 第一步先获取公钥
    let publicKey;
    try {
        publicKey = await getpublickey(payConfig)
    } catch (err) {
        console.log(err)
        ctx.body = {
            code: 500,
            msg: err
        }
        return
    }
    // 获取公钥成功后,调用payBank方法,付款到银行卡
    try {
        let result = await payBank(map)
        ctx.body = {
            code: 200,
            msg: '微信受理成功, T+1到账'
        }
    } catch (err) {
        console.log(err)
        ctx.body = {
            code: 500,
            msg: err.msg
        }
        return
    }
    })

    下面贴一些工具方法,可以自行创建一个payUtil.js,将下面代码贴入即可

    生成随机字符出、付款到银行卡签名、获取公钥签名

    // 生成随机随机32 位  字符串
    exports.getRnd32 = () => {
        let str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
        let result = ''
        for (let i = 0; i < 32; i++) {
            let rnd = Math.floor(Math.random() * str.length)
            result += str[rnd]
        }
        return result
    }
    
    
    // 生成时间戳
    exports.createTimeStamp = () => {
        return parseInt(new Date().getTime() / 1000) + '';
    }
    
    
    // 生成订单编号
    exports.createOrderNumber = () => {
        let str = ''
        for (let i = 0; i < 10; i++) {
            let num = Math.floor(Math.random() * 10)
            str += num
        }
        let time = new Date()
        let year = time.getFullYear().toString()
        let month = time.getMonth().toString() + 1
        let day = time.getDate().toString()
        let hours = time.getHours().toString()
        let minutes = time.getMinutes().toString()
        let seconds = time.getSeconds().toString()
        let mill = time.getMilliseconds().toString()
        str += year += month += day += hours += minutes += seconds += mill
        return str;
    }
    
    
    // 按照ascll码排序
    function raw(args) {
        var keys = Object.keys(args);
        keys = keys.sort()
        var newArgs = {};
        keys.forEach(function (key) {
            newArgs[key] = args[key];
        });
        var string = '';
        for (var k in newArgs) {
            string += '&' + k + '=' + newArgs[k];
        }
        string = string.substr(1);
        return string;
    }
    
    
    /**
     * 企业付款到银行卡签名
     */
     exports.payBankSign = (map) => {
        let ret = {
          amount: map.amount,
          bank_code: map.bank_code,
          enc_bank_no: map.enc_bank_no,
          enc_true_name: map.enc_true_name,
          mch_id: map.mch_id,
          nonce_str: map.nonce_str,
          desc: map.desc,
          partner_trade_no: map.partner_trade_no,
        }
        console.log(ret)
        var string = raw(ret);
        var key = map.mchkey;
        string = string + '&key=' + key;
        var crypto = require('crypto');
        return crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
      }
    
    /**
     * 获取公钥进行签名
     */
     exports.getpublickkeySign = (map) => {
        let ret = {
            mch_id: map.mch_id,
            nonce_str: map.nonce_str,
            sign_type: map.sign_type
        }
        console.log(ret)
        var string = raw(ret);
        var key = map.mchkey;
        string = string + '&key=' + key;
        var crypto = require('crypto');
        return crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
    }

    最后提一些我测试时出现的问题,有时你在本地测试的时候,会有如下报错

    error:此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置

     可以参考下面的链接:https://blog.csdn.net/yexiaomodemo/article/details/109316364

  • 相关阅读:
    关于 flutter_boost Attempt to invoke virtual method ' com.idlefish.flutterboost.FlutterViewContainerManager.getCurrentTopRecord()' on a null object reference
    关于 flutter_boost TextField的autofocus导致页面闪动回弹
    关于 flutter ios打开一个带有百度地图的native页面闪退Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread
    关于 flutter_boost demo 隐藏导航栏后push flutter page 右滑返回失效
    关于 flutter 只能用armeabi单架构的问题
    关于数组的length属性
    python 常见问题:导入py文件易忽略问题
    IE浏览器兼容性测试 文档模式,浏览器模式
    关于事件mouseover ,mouseout ,mouseenter,mouseleave的区别
    [BS-28] iOS中分页的几种算法
  • 原文地址:https://www.cnblogs.com/naturl/p/15393789.html
Copyright © 2011-2022 走看看