1
This commit is contained in:
6
tool/node-wxpay/index.js
Normal file
6
tool/node-wxpay/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
// 支付 查询 退款
|
||||
exports.WxPay = require('./lib/wx_pay');
|
||||
// 提现到零钱 提现到银行卡 优惠券
|
||||
exports.WxWithdraw = require('./lib/wx_withdraw');
|
||||
exports.RefundMiddleware = require('./utils/refund_middleware');
|
||||
170
tool/node-wxpay/lib/wx_ipay.js
Normal file
170
tool/node-wxpay/lib/wx_ipay.js
Normal file
@@ -0,0 +1,170 @@
|
||||
'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;
|
||||
164
tool/node-wxpay/lib/wx_iwithdraw.js
Normal file
164
tool/node-wxpay/lib/wx_iwithdraw.js
Normal file
@@ -0,0 +1,164 @@
|
||||
'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 = {
|
||||
'sendredpack': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack', // 发放红包
|
||||
'sendgroupredpack': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack', // 发放裂变红包
|
||||
'gethbinfo': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo', // 查询红包记录
|
||||
'sendminiprogramhb': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendminiprogramhb', // 小程序红包
|
||||
'transfers': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers', // 企业付款到零钱
|
||||
'gettransferinfo': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo', // 查询企业付款到零钱
|
||||
'pay_bank': 'https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank', // 企业付款到银行卡
|
||||
'query_bank': 'https://api.mch.weixin.qq.com/mmpaysptrans/query_bank', // 查询企业付款到银行卡
|
||||
'getpublickey': 'https://fraud.mch.weixin.qq.com/risk/getpublickey', // 获取RSA加密公钥
|
||||
'send_coupon': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/send_coupon', // 发放代金券
|
||||
'query_coupon_stock': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/query_coupon_stock', // 查询代金券批次
|
||||
'querycouponsinfo': 'https://api.mch.weixin.qq.com/mmpaymkttransfers/querycouponsinfo', // 查询代金券信息
|
||||
};
|
||||
class IWithdraw {
|
||||
constructor(options) {
|
||||
this._options = {};
|
||||
this._options['pfx'] = options.pfx;
|
||||
this._options['key'] = options.partner_key;
|
||||
this._options['sign'] = '';
|
||||
|
||||
this._params = {};
|
||||
}
|
||||
// 添加其他参数
|
||||
_otherParams(params) {
|
||||
this._params = {
|
||||
'nonce_str': util.getNonceStr(),
|
||||
...params,
|
||||
...this._options,
|
||||
};
|
||||
}
|
||||
// 参数检验
|
||||
_checkOptions(properties) {
|
||||
if (!this._params.key) throw new Error('缺少partner_key');
|
||||
if (!this._params.pfx) throw new Error('缺少pfx');
|
||||
|
||||
properties.forEach(item => {
|
||||
if (this._params[item] === undefined || this._params[item] === null) throw new Error('缺少' + item);
|
||||
});
|
||||
}
|
||||
// MD5加密
|
||||
_md5(params) {
|
||||
let object = {
|
||||
...this._params,
|
||||
};
|
||||
const exclude = [ 'pfx', 'sign', 'partner_key', 'key' ];
|
||||
|
||||
if (params) {
|
||||
object = {
|
||||
...params,
|
||||
'key': this._options['key'],
|
||||
};
|
||||
}
|
||||
|
||||
// 不能把sign_type 带入加密中
|
||||
const querystring = Object.keys(object).filter(function(key) {
|
||||
return object[key] !== undefined && object[key] !== '' && !exclude.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' ].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' }
|
||||
|
||||
// 生成请求统一下单下单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._params.mch_id || this._params.mchid, // 证书秘钥【微信设置为商户号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._params.mch_id || this._params.mchid, // 证书秘钥【微信设置为商户号id】
|
||||
})
|
||||
.type('xml');
|
||||
|
||||
// application/xml 返回参数是buffer
|
||||
return this._xmltojson(result.body.toString());
|
||||
}
|
||||
}
|
||||
module.exports = IWithdraw;
|
||||
256
tool/node-wxpay/lib/wx_pay.js
Normal file
256
tool/node-wxpay/lib/wx_pay.js
Normal file
@@ -0,0 +1,256 @@
|
||||
'use strict';
|
||||
const IOrder = require('./wx_ipay');
|
||||
const util = require('../utils/util');
|
||||
|
||||
// 支付
|
||||
class Pay extends IOrder {
|
||||
constructor({ appid, mch_id, partner_key, pfx }) {
|
||||
super({ appid, mch_id, partner_key, pfx });
|
||||
}
|
||||
// 初始化参数 请求
|
||||
async init(params, name, properties) {
|
||||
this._otherParams(params);
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
const result = await this._request(name, this._xml);
|
||||
return result;
|
||||
}
|
||||
// md5加密 暴露给外部调用
|
||||
md5(params) {
|
||||
return this._md5(params);
|
||||
}
|
||||
// HMAC-SHA256 加密 暴露给外部调用
|
||||
hmac(params) {
|
||||
return this._hmac(params);
|
||||
}
|
||||
// xml 转json 暴露给外部调用
|
||||
xmltojson(data) {
|
||||
return this._xmltojson(data);
|
||||
}
|
||||
// 统一下单
|
||||
async unifiedorder(params) {
|
||||
// 必传参数
|
||||
const properties = ['body', 'out_trade_no', 'total_fee', 'spbill_create_ip', 'notify_url', 'trade_type'];
|
||||
if (params.trade_type === 'JSAPI') properties.push('openid');
|
||||
if (params.trade_type === 'NATIVE') properties.push('product_id');
|
||||
if (params.trade_type === 'MWEB') {
|
||||
properties.push('scene_info');
|
||||
properties.push('redirect_url');
|
||||
};
|
||||
|
||||
const result = await this.init(params, 'unifiedorder', properties);
|
||||
|
||||
const { prepay_id, return_code, return_msg, result_code } = result;
|
||||
if (return_code !== 'SUCCESS' || result_code !== 'SUCCESS') return result;
|
||||
|
||||
// 参数处理
|
||||
let _data = {};
|
||||
switch (this._params.trade_type) {
|
||||
case 'JSAPI':
|
||||
_data['appId'] = this._params.appid;
|
||||
_data['timeStamp'] = `${parseInt((+new Date()) / 1000)}`;
|
||||
_data['package'] = `prepay_id=${prepay_id}`;
|
||||
_data['nonceStr'] = util.getNonceStr().toLowerCase();
|
||||
_data['signType'] = params['sign_type'] || 'MD5';
|
||||
if (params['sign_type'] === 'HMAC-SHA256') {
|
||||
_data['paySign'] = this._hmac(_data);
|
||||
} else {
|
||||
_data['paySign'] = this._md5(_data);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'APP':
|
||||
_data['appid'] = this._params.appid;
|
||||
_data['timestamp'] = `${parseInt((+new Date()) / 1000)}`;
|
||||
_data['partnerid'] = this._params.mch_id;
|
||||
_data['prepayid'] = prepay_id;
|
||||
_data['package'] = 'Sign=WXPay';
|
||||
_data['noncestr'] = util.getNonceStr().toLowerCase();
|
||||
if (params['sign_type'] === 'HMAC-SHA256') {
|
||||
_data['sign'] = this._hmac(_data);
|
||||
} else {
|
||||
_data['sign'] = this._md5(_data);
|
||||
}
|
||||
break;
|
||||
case 'NATIVE': // pc端网站 模式二
|
||||
_data = { // 把code_url 生成图片
|
||||
...result,
|
||||
};
|
||||
break;
|
||||
case 'MWEB':
|
||||
// 不能直接在浏览器中访问mweb_url,会报商家参数错误,因为他会检测Referer,所以Referer不能为空
|
||||
// 手机浏览器中支付 https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
|
||||
_data = {
|
||||
...result,
|
||||
'mweb_url': result.mweb_url + `&redirect_url=${encodeURIComponent(this._params.redirect_url)}`,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
console.error('trade_type参数有误');
|
||||
_data = {};
|
||||
}
|
||||
|
||||
const req_data = {
|
||||
...this._params,
|
||||
};
|
||||
delete req_data['pfx'];
|
||||
delete req_data['key'];
|
||||
delete req_data['sign'];
|
||||
|
||||
return {
|
||||
// req_data, // 创建订单的参数 用于回调验证
|
||||
..._data,
|
||||
return_code,
|
||||
return_msg,
|
||||
result_code,
|
||||
};
|
||||
}
|
||||
// 支付回调验证
|
||||
callback_check(data) {
|
||||
let _sign = '';
|
||||
if (data.sign_type === 'HMAC-SHA256') {
|
||||
_sign = this._hmac(data);
|
||||
} else {
|
||||
_sign = this._md5(data);
|
||||
}
|
||||
return data.sign === _sign; // boolean true 成功
|
||||
}
|
||||
// 订单查询
|
||||
async orderquery(params) {
|
||||
// 必传参数
|
||||
const properties = [];
|
||||
if (!params.transaction_id && !params.out_trade_no) throw new Error('缺少参数transaction_id或out_trade_no!');
|
||||
|
||||
let reuslt = await this.init(params, 'orderquery', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 关闭订单
|
||||
async closeorder(params) {
|
||||
const properties = ['out_trade_no'];
|
||||
let reuslt = await this.init(params, 'closeorder', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 申请退款
|
||||
async refund(params) {
|
||||
if (!params.transaction_id && !params.out_trade_no) throw new Error('缺少参数transaction_id或out_trade_no!');
|
||||
if (!this._options.pfx) throw new Error('缺少pfx');
|
||||
const properties = ['out_refund_no', 'total_fee', 'refund_fee'];
|
||||
let reuslt = await this.init(params, 'refund', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 查询退款
|
||||
async refundquery(params) {
|
||||
if (!params.transaction_id && !params.out_trade_no && !params.out_refund_no && !params.refund_id) {
|
||||
throw new Error('缺少参数transaction_id或out_trade_no或refund_id或out_refund_no!');
|
||||
}
|
||||
const properties = [];
|
||||
let reuslt = await this.init(params, 'refundquery', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 下载交易账单
|
||||
async downloadbill(params) {
|
||||
this._otherParams(params);
|
||||
const properties = ['bill_date'];
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
const result = await this._request2('downloadbill', this._xml);
|
||||
|
||||
if (result.status !== 200 && result.statusCode !== 200) return result;
|
||||
// 参数处理
|
||||
if (result.type === 'application/x-gzip') {
|
||||
return {
|
||||
'data': result.body,
|
||||
'return_code': 'SUCCESS',
|
||||
};
|
||||
} else if (result.type === 'text/plain') {
|
||||
if (result.text.indexOf('<xml>') !== -1) return this._xmltojson(result.text);
|
||||
return {
|
||||
'data': result.text,
|
||||
'return_code': 'SUCCESS',
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 下载资金账单
|
||||
async downloadfundflow(params) {
|
||||
this._otherParams(params);
|
||||
if (params.sign_type !== 'HMAC-SHA256') throw new Error('请选择HMAC-SHA256加密!');
|
||||
if (!this._params.pfx) throw new Error('缺少pfx');
|
||||
const properties = ['bill_date', 'account_type'];
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
const result = await this._request2('downloadfundflow', this._xml);
|
||||
|
||||
if (result.status !== 200 && result.statusCode !== 200) return result;
|
||||
// 参数处理
|
||||
if (result.type === 'application/x-gzip') {
|
||||
return {
|
||||
'data': result.body,
|
||||
'return_code': 'SUCCESS',
|
||||
'result_code': 'SUCCESS',
|
||||
};
|
||||
} else if (result.type === 'text/plain') {
|
||||
if (result.text.indexOf('<xml>') !== -1) return this._xmltojson(result.text);
|
||||
return {
|
||||
'data': result.text,
|
||||
'return_code': 'SUCCESS',
|
||||
'result_code': 'SUCCESS',
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 交易保障
|
||||
async report(params) {
|
||||
const properties = ['interface_url', 'execute_time_', 'return_code', 'result_code', 'user_ip'];
|
||||
let reuslt = await this.init(params, 'report', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 拉取订单评价数据
|
||||
async batchquerycomment(params) {
|
||||
this._otherParams(params);
|
||||
const properties = ['begin_time', 'end_time', 'offset', 'limit'];
|
||||
if (!this._params.pfx) throw new Error('缺少pfx');
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
|
||||
const result = await this._request2('batchquerycomment', this._xml);
|
||||
|
||||
if (result.status !== 200 && result.statusCode !== 200) return result;
|
||||
// 参数处理
|
||||
if (result.type === 'text/html') {
|
||||
if (result.text.indexOf('<xml>') !== -1) {
|
||||
return this._xmltojson(result.text);
|
||||
}
|
||||
return {
|
||||
'data': result.text,
|
||||
'return_code': 'SUCCESS',
|
||||
'result_code': 'SUCCESS',
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 付款码支付
|
||||
async micropay(params) {
|
||||
const properties = ['body', 'out_trade_no', 'total_fee', 'spbill_create_ip', 'auth_code'];
|
||||
let reuslt = await this.init(params, 'micropay', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 撤销订单(只支持付款码支付的订单才可以撤销,统一下单生成的订单不能撤销)
|
||||
async reverse(params) {
|
||||
if (!params.transaction_id && !params.out_trade_no) throw new Error('缺少参数transaction_id或out_trade_no!');
|
||||
const properties = [];
|
||||
let reuslt = await this.init(params, 'reverse', properties);
|
||||
return reuslt;
|
||||
}
|
||||
// 付款码查询openid
|
||||
async authcodetoopenid(params) {
|
||||
const properties = ['auth_code'];
|
||||
let reuslt = await this.init(params, 'authcodetoopenid', properties);
|
||||
return reuslt;
|
||||
}
|
||||
}
|
||||
module.exports = Pay;
|
||||
145
tool/node-wxpay/lib/wx_withdraw.js
Normal file
145
tool/node-wxpay/lib/wx_withdraw.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict';
|
||||
// 微信提现相关接口
|
||||
const IWithdraw = require('./wx_iwithdraw');
|
||||
const urlencode = require('urlencode');
|
||||
const util = require('../utils/util');
|
||||
// const NodeRSA = require('node-rsa');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class Withdraw extends IWithdraw {
|
||||
constructor({ partner_key, pfx }) {
|
||||
super({ partner_key, pfx });
|
||||
}
|
||||
// md5加密 暴露给外部调用
|
||||
md5(params) {
|
||||
return this._md5(params);
|
||||
}
|
||||
// HMAC-SHA256 加密 暴露给外部调用
|
||||
hmac(params) {
|
||||
return this._hmac(params);
|
||||
}
|
||||
// 公钥加密
|
||||
publicEncrypt(publicKey, data) {
|
||||
// const clientKey = new NodeRSA(publicKey);
|
||||
// // 在node-rsa模块中加解密默认使用 pkcs1_oaep ,而在js中加密解密默认使用的是 pkcs1
|
||||
// clientKey.setOptions({ 'encryptionScheme': 'pkcs1_oaep' }); // RSA_PKCS1_OAEP_PADDING
|
||||
// let encrypted = clientKey.encrypt(data, 'base64');
|
||||
// return encrypted;
|
||||
|
||||
return crypto.publicEncrypt(publicKey, Buffer.from(data)).toString('base64');
|
||||
}
|
||||
async init(params, name, properties) {
|
||||
this._otherParams(params);
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
const result = await this._request(name, this._xml);
|
||||
return result;
|
||||
}
|
||||
// 发放红包
|
||||
async sendredpack(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'mch_billno', 'mch_id', 'wxappid', 'send_name', 're_openid', 'total_amount', 'total_num',
|
||||
'wishing', 'client_ip', 'act_name', 'remark' ];
|
||||
let result = await this.init(params, 'sendredpack', properties);
|
||||
return result;
|
||||
}
|
||||
// 发放裂变红包
|
||||
async sendgroupredpack(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'mch_billno', 'mch_id', 'wxappid', 'send_name', 're_openid', 'total_amount', 'total_num',
|
||||
'amt_type', 'wishing', 'wishing', 'remark' ];
|
||||
let result = await this.init(params, 'sendgroupredpack', properties);
|
||||
return result;
|
||||
}
|
||||
// 查询红包记录
|
||||
async gethbinfo(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'mch_billno', 'mch_id', 'appid', 'bill_type' ];
|
||||
let result = await this.init(params, 'gethbinfo', properties);
|
||||
return result;
|
||||
}
|
||||
// 小程序红包
|
||||
async sendminiprogramhb(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'mch_billno', 'mch_id', 'wxappid', 'send_name', 're_openid', 'total_amount', 'total_num', 'wishing', 'client_ip',
|
||||
'act_name', 'remark', 'notify_way' ];
|
||||
let result = await this.init(params, 'sendminiprogramhb', properties);
|
||||
|
||||
const _data = {};
|
||||
if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS' && result.package) {
|
||||
|
||||
_data['timestamp'] = `${parseInt((+new Date()) / 1000)}`;
|
||||
_data['package'] = urlencode(result.package);
|
||||
_data['signType'] = 'MD5';
|
||||
_data['nonceStr'] = util.getNonceStr().toLowerCase();
|
||||
_data['paySign'] = this._md5(_data);
|
||||
|
||||
_data['return_code'] = 'SUCCESS';
|
||||
_data['result_code'] = 'SUCCESS';
|
||||
return _data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// 企业付款到零钱
|
||||
async transfers(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'mch_appid', 'mchid', 'partner_trade_no', 'openid', 'check_name', 'amount', 'desc' ];
|
||||
if (params['check_name'] === 'FORCE_CHECK' && !params['re_user_name']) throw new Error('缺少 re_user_name');
|
||||
let result = await this.init(params, 'transfers', properties);
|
||||
return result;
|
||||
}
|
||||
// 查询企业付款到零钱
|
||||
async gettransferinfo(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'partner_trade_no', 'mch_id', 'appid' ];
|
||||
let result = await this.init(params, 'gettransferinfo', properties);
|
||||
return result;
|
||||
}
|
||||
// 获取RSA加密公钥API
|
||||
async getpublickey(params) {
|
||||
const properties = [ 'mch_id', 'sign_type' ];
|
||||
this._otherParams(params);
|
||||
this._checkOptions(properties);
|
||||
this._xml = this._jsontoxml();
|
||||
|
||||
const result = await this._request2('getpublickey', this._xml);
|
||||
return result;
|
||||
}
|
||||
// 企业付款到银行卡
|
||||
async pay_bank(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'partner_trade_no', 'mch_id', 'enc_bank_no', 'enc_true_name', 'bank_code', 'amount' ];
|
||||
let result = await this.init(params, 'pay_bank', properties);
|
||||
return result;
|
||||
}
|
||||
// 查询企业付款到银行卡
|
||||
async query_bank(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'partner_trade_no', 'mch_id' ];
|
||||
let result = await this.init(params, 'query_bank', properties);
|
||||
return result;
|
||||
}
|
||||
// 发放代金券
|
||||
async send_coupon(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'coupon_stock_id', 'mch_id', 'openid_count', 'partner_trade_no', 'openid', 'appid' ];
|
||||
let result = await this.init(params, 'send_coupon', properties);
|
||||
return result;
|
||||
}
|
||||
// 查询代金券批次
|
||||
async query_coupon_stock(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'coupon_stock_id', 'mch_id', 'appid' ];
|
||||
let result = await this.init(params, 'query_coupon_stock', properties);
|
||||
return result;
|
||||
}
|
||||
// 查询代金券信息
|
||||
async querycouponsinfo(params) {
|
||||
delete params['sign_type']; // 必须移除 不然会报密钥错误
|
||||
const properties = [ 'coupon_id', 'mch_id', 'appid', 'openid', 'stock_id' ];
|
||||
let result = await this.init(params, 'querycouponsinfo', properties);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
module.exports = Withdraw;
|
||||
35
tool/node-wxpay/utils/refund_middleware.js
Normal file
35
tool/node-wxpay/utils/refund_middleware.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
const xml2js = require('xml2js');
|
||||
|
||||
module.exports = () => {
|
||||
return async (ctx, next) => {
|
||||
let paramsJson = null;
|
||||
let contentType = ctx.headers['content-type'] || 'application/json';
|
||||
if (contentType.indexOf('xml') !== -1) { // xml格式参数获取
|
||||
let data = '';
|
||||
ctx.req.setEncoding('utf8');
|
||||
ctx.req.on('data', function(chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
const getxml = await new Promise(function(resolve) {
|
||||
ctx.req.on('end', function() {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
const parseObj = await new Promise(function(resolve) {
|
||||
xml2js.parseString(getxml, {
|
||||
'explicitArray': false,
|
||||
}, function(err, json) {
|
||||
if (err) throw err;
|
||||
return resolve(json);
|
||||
});
|
||||
});
|
||||
if (parseObj.xml) delete parseObj.xml._;
|
||||
paramsJson = parseObj.xml;
|
||||
|
||||
}
|
||||
ctx.request.body = paramsJson;
|
||||
await next();
|
||||
};
|
||||
};
|
||||
7
tool/node-wxpay/utils/util.js
Normal file
7
tool/node-wxpay/utils/util.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
getNonceStr() {
|
||||
return Math.random().toString(36).substr(2, 15);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user