This commit is contained in:
张成
2025-11-21 16:53:49 +08:00
commit 8309808835
286 changed files with 32656 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;