背景
对接企业付款到零钱,微信api请求时需要用到证书(node环境使用pem格式)
微信API:【微信支付】付款开发者文档
已经用微信的\WXCertUtil工具在本地生成了证书文件
问题所在:
证书使用方式错误,原本我是使用:
// 需要证书
agentOptions: {
cert: '../cert.pem',
key:'../key.pem'
},
正确写法:
agentOptions: {
cert: '-----BEGIN CERTIFICATE-----\r\nMIID6TXXXXXXXUNN6Lt1v7o\r\n-----END CERTIFICATE-----',
key:'-----BEGIN PRIVATE KEY-----\r\nMIIEvgIxxxxxxxxxxxxxHZXsjwoJ0gf71bQLrrAMHXh0i73N+KmItDaxTerRhK1XpLehFgTFoPQfmz6pPheHkDvLf9tsOiWZ4uF71+I24G4jHixFDDEyNy4cduzBRJgQTJrBooHLpPDFj2m6uqv\r\n-----END PRIVATE KEY-----',
},
请求中的agentOptions对象 中证书直接使用pem格式的内容即可
注意:用微信工具WXCertUtil 生成的pem格式的文件有换行,node 请求中识别不了,一定要压缩一下,在加入\r\n
压缩工具:CSS代码压缩/解压缩_CSS在线格式化_CSS还原和美化_55查询
以下是相关代码参考:node egg企业付款到微信零钱_今天有坑么的博客-CSDN博客
//helper.js
const Xml2js = require('xml2js')
const Crypto = require('crypto')
const EARTH_RADIUS = 6378.137
module.exports = {
// 计算两坐标点之间的距离 1.
async space(lat1, lng1, lat2, lng2) {
var radLat1 = lat1 * Math.PI / 180.0;
var radLat2 = lat2 * Math.PI / 180.0;
var a = radLat1 - radLat2;
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * 6378.137;
s = Math.round(s * 10000) / 10000;
return s // 单位千米
},
// 计算两坐标点之间的距离 2.-1
async rad(d) {
return d * Math.PI / 180.0;
},
// 计算两坐标点之间的距离 2.-2
async getDistance(lng1, lat1, lng2, lat2) {
// * 谷歌地图计算两个坐标点的距离
// * @param lng1 经度1
// * @param lat1 纬度1
// * @param lng2 经度2
// * @param lat2 纬度2
// * @return 距离(千米)
var radLat1 = await this.rad(lat1);
var radLat2 = await this.rad(lat2);
var a = radLat1 - radLat2;
var b = await this.rad(lng1) - await this.rad(lng2);
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2)
* Math.pow(Math.sin(b / 2), 2)));
s = s * EARTH_RADIUS;
s = Math.round(s * 10000) / 10000;
return s;
},
// 计算坐标距离 3.
async GetMapDistance(lat1, lng1, lat2, lng2) {
let MathLeb = Math.PI / 180.0;//经纬度转换成三角函数中度分表形式。
var radLat1 = Number(lat1 * MathLeb);
var radLat2 = Number(lat2 * MathLeb);
var a = radLat1 - radLat2;
var b = Number(lng1 * MathLeb) - Number(lng2 * MathLeb);
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * 6378.137;//地球半径;
s = Math.round(s * 10000) / 10000;//以Km输出
s = s.toFixed(2);
return s;
},
// 返回MD5加密
async md5(data) {
return Crypto.createHash('md5').update(data).digest('hex')
},
// 返回一个32位字符串
async GetStr32() {
return Crypto.randomBytes(16).toString('hex')
},
// 返回sha1 加密
async sha1(data) {
return Crypto.createHash('sha1').update(data).digest('hex')
},
// 构成XML
createXML(obj) {
return new Promise((resolve) => {
var builder = new Xml2js.Builder({
rootName: 'xml',
headless: true,
cdata: true,
});
return resolve(builder.buildObject(obj))
})
},
// 公众号 XML 转为json
async parseXMLAsync(data) {
let { xml } = await Xml2js.parseStringPromise(data, { explicitArray: false })
return xml
},
// 生成订单编号
async 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码排序
async 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;
},
/**
* 企业付款到零钱签名
*/
async transfersSign (map) {
let ret = {
mch_appid: map.mch_appid,
mchid: map.mchid,
nonce_str: map.nonce_str,
partner_trade_no: map.partner_trade_no,
openid: map.openid,
check_name: map.check_name,
amount: map.amount,
desc: map.desc,
spbill_create_ip: map.spbill_create_ip,
}
console.log(ret)
var string =await this.raw(ret);
var key = map.mchkey;
string = string + '&key=' + key;
return Crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
}
}
//egg.js serive
'use strict';
const Service = require('egg').Service;
const fs = require('fs')
const path = require('path')
const xml2js = require('xml2js')
const request = require('request')
class QiyezzService extends Service {
async transfers(map) {
let mapInfo = {}
// 商户appid
// 这里的商户appid是公众号appid
mapInfo.mch_appid = ''
// 商户id
mapInfo.mchid = ''
// 商户密钥
mapInfo.mchkey =''
// 随机字符串
mapInfo.nonce_str = await this.ctx.helper.GetStr32()
// 商户内部流水号
mapInfo.partner_trade_no = map.recordId
// 用户的openid
mapInfo.openid = map.openid
// 是否需要校验名字
mapInfo.check_name = 'NO_CHECK'
// 金额,单为是分
mapInfo.amount = map.amount * 100
// 描述
mapInfo.desc = '企业付款到零钱'
// ip,该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP。
// mapInfo.spbill_create_ip = ''
// 签名
let sign = await this.ctx.helper.transfersSign(mapInfo)
// 拼接xm字符串
let formData = "<xml>";
formData += "<mch_appid>" + mapInfo.mch_appid + "</mch_appid>";
formData += "<mchid>" + mapInfo.mchid + "</mchid>";
formData += "<nonce_str>" + mapInfo.nonce_str + "</nonce_str>";
formData += "<partner_trade_no>" + mapInfo.partner_trade_no + "</partner_trade_no>";
formData += "<openid>" + mapInfo.openid + "</openid>";
formData += "<check_name>" + mapInfo.check_name + "</check_name>";
formData += "<amount>" + mapInfo.amount + "</amount>";
formData += "<desc>" + mapInfo.desc + "</desc>";
formData += "<spbill_create_ip>" + mapInfo.spbill_create_ip + "</spbill_create_ip>";
formData += "<sign>" + sign + "</sign>";
formData += "</xml>";
console.log(formData)
// 请求路径
let url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'
return new Promise((resolve, reject) => {
request({
url: url,
// 需要证书
agentOptions: {
cert: this.config.wxpay_cert_pem,
key:this.config.wxpay_cert_keypem
},
method: 'post',
body: formData,
}, function (err, response, body) {
if (!err && response.statusCode == 200) {
// 创建一个解析xml的对象
let parser = new xml2js.Parser({
trim: true,
explicitArray: false,
explicitRoot: false
});
//解析签名结果xml转json
parser.parseString(body, (err, res) => {
console.log(res)
let result = {}
// 判断return_code 为fail的时候,的错误信息
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)
})
})
}
}
module.exports = QiyezzService;