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