171 lines
5.8 KiB
JavaScript
171 lines
5.8 KiB
JavaScript
'use strict';
|
|
const md5 = require('md5');
|
|
const xml2js = require('xml2js');
|
|
const util = require('../utils/util');
|
|
const request = require('superagent');
|
|
const crypto = require('crypto');
|
|
// xmlBuilder工具
|
|
const builder = new xml2js.Builder({
|
|
'headless': true,
|
|
'allowSurrogateChars': true,
|
|
'rootName': 'xml',
|
|
'cdata': true,
|
|
});
|
|
// 请求路径
|
|
const urls = {
|
|
'unifiedorder': 'https://api.mch.weixin.qq.com/pay/unifiedorder', // 统一下单
|
|
'orderquery': 'https://api.mch.weixin.qq.com/pay/orderquery', // 查询订单
|
|
'closeorder': 'https://api.mch.weixin.qq.com/pay/closeorder', // 关闭订单
|
|
'refund': 'https://api.mch.weixin.qq.com/secapi/pay/refund', // 申请退款
|
|
'refundquery': 'https://api.mch.weixin.qq.com/pay/refundquery', // 查询退款
|
|
'downloadbill': 'https://api.mch.weixin.qq.com/pay/downloadbill', // 下载交易账单
|
|
'downloadfundflow': 'https://api.mch.weixin.qq.com/pay/downloadfundflow', // 下载资金账单
|
|
'report': 'https://api.mch.weixin.qq.com/payitil/report', // 交易保障
|
|
'batchquerycomment': 'https://api.mch.weixin.qq.com/billcommentsp/batchquerycomment', // 拉取订单评价数据
|
|
'reverse': 'https://api.mch.weixin.qq.com/secapi/pay/reverse', // 撤销订单
|
|
'micropay': 'https://api.mch.weixin.qq.com/pay/micropay', // 付款码支付
|
|
'authcodetoopenid': 'https://api.mch.weixin.qq.com/tools/authcodetoopenid', // 付款码查询openid
|
|
};
|
|
// 微信支付相关接口
|
|
class IOrder {
|
|
// options 参数值 参考微信支付文档
|
|
constructor(options) {
|
|
this._options = {};
|
|
this._options['appid'] = options.appid;
|
|
this._options['pfx'] = options.pfx;
|
|
this._options['mch_id'] = options.mch_id;
|
|
this._options['key'] = options.partner_key;
|
|
this._options['sign'] = '';
|
|
|
|
this._params = {};
|
|
}
|
|
// 添加其他参数
|
|
_otherParams(params) {
|
|
this._params = {
|
|
'nonce_str': util.getNonceStr(),
|
|
...params,
|
|
...this._options,
|
|
};
|
|
if (!this._params['sign_type'] || ![ 'MD5', 'HMAC-SHA256' ].includes(this._params['sign_type'])) this._params['sign_type'] = 'MD5';
|
|
}
|
|
// 参数检验
|
|
_checkOptions(properties) {
|
|
if (!this._params.appid) throw new Error('缺少appid');
|
|
if (!this._params.mch_id) throw new Error('缺少mch_id');
|
|
if (!this._params.key) throw new Error('缺少partner_key');
|
|
|
|
properties.forEach(item => {
|
|
if (this._params[item] === undefined || this._params[item] === null) throw new Error('缺少' + item);
|
|
});
|
|
}
|
|
// MD5加密
|
|
_md5(params) {
|
|
let object = {
|
|
...this._params,
|
|
};
|
|
if (params) {
|
|
object = {
|
|
...params,
|
|
'key': this._options['key'],
|
|
};
|
|
}
|
|
const querystring = Object.keys(object).filter(function(key) {
|
|
return object[key] !== undefined && object[key] !== '' && ![ 'pfx', 'sign', 'partner_key', 'key', 'redirect_url' ].includes(key);
|
|
}).sort()
|
|
.map(function(key) {
|
|
return key + '=' + object[key];
|
|
})
|
|
.join('&') + '&key=' + object.key;
|
|
return md5(querystring).toUpperCase();
|
|
}
|
|
// HMAC-SHA256 加密
|
|
_hmac(params) {
|
|
let object = {
|
|
...this._params,
|
|
};
|
|
if (params) {
|
|
object = {
|
|
...params,
|
|
'key': this._options['key'],
|
|
};
|
|
}
|
|
|
|
const querystring = Object.keys(object).filter(function(key) {
|
|
return object[key] !== undefined && object[key] !== '' && ![ 'pfx', 'sign', 'partner_key', 'key', 'redirect_url' ].includes(key);
|
|
}).sort()
|
|
.map(function(key) {
|
|
return key + '=' + object[key];
|
|
})
|
|
.join('&') + '&key=' + object.key;
|
|
const hash = crypto.createHmac('sha256', this._options['key'])
|
|
.update(querystring)
|
|
.digest('hex');
|
|
|
|
return hash.toUpperCase();
|
|
}
|
|
// toxml
|
|
_jsontoxml() {
|
|
const object = {
|
|
...this._params,
|
|
};
|
|
if (object['sign_type'] === 'HMAC-SHA256') {
|
|
object.sign = this._hmac();
|
|
} else {
|
|
object.sign = this._md5();
|
|
}
|
|
|
|
// 移除证书
|
|
delete object['pfx'];
|
|
delete object['key']; // 移除密钥 不然会报{ return_code: 'FAIL', return_msg: '不识别的参数key' }
|
|
delete object['redirect_url'];
|
|
|
|
// 生成请求统一下单下单xml参数
|
|
const xmlOption = builder.buildObject(object);
|
|
return xmlOption;
|
|
}
|
|
_xmltojson(data) {
|
|
let body = {};
|
|
xml2js.parseString(data, { 'trim': true, 'explicitArray': false }, (err, result) => {
|
|
if (err) {
|
|
// throw new Error(err);
|
|
console.error(err);
|
|
body = {};
|
|
} else {
|
|
body = result.xml;
|
|
}
|
|
|
|
});
|
|
|
|
return body;
|
|
}
|
|
// 请求
|
|
async _request(name, xmlOption) {
|
|
const url = urls[name];
|
|
let result = await request.post(url)
|
|
.send(xmlOption)
|
|
.pfx({
|
|
'pfx': this._options.pfx, // 证书
|
|
'passphrase': this._options.mch_id, // 证书秘钥【微信设置为商户号id】
|
|
})
|
|
.type('xml');
|
|
|
|
return this._xmltojson(result.text);
|
|
}
|
|
// 请求
|
|
async _request2(name, xmlOption) {
|
|
const url = urls[name];
|
|
let result = await request.post(url)
|
|
.send(xmlOption)
|
|
.pfx({
|
|
'pfx': this._options.pfx, // 证书
|
|
'passphrase': this._options.mch_id, // 证书秘钥【微信设置为商户号id】
|
|
})
|
|
.type('xml');
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = IOrder;
|