微信支付用的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