tenpay-v2
Version:
713 lines (634 loc) • 20.5 kB
JavaScript
const urllib = require("urllib");
const util = require("./util");
const replyData = msg =>
util.buildXML(
msg ? { return_code: "FAIL", return_msg: msg } : { return_code: "SUCCESS" }
);
class Payment {
constructor(
{
appid,
mchid,
partnerKey,
pfx,
notify_url,
refund_url,
spbill_create_ip,
sandbox
} = {},
debug = false
) {
if (!appid) throw new Error("appid fail");
if (!mchid) throw new Error("mchid fail");
if (!partnerKey) throw new Error("partnerKey fail");
this.appid = appid;
this.mchid = mchid;
this.partnerKey = partnerKey;
this.pfx = pfx;
this.notify_url = notify_url;
this.refund_url = refund_url;
this.spbill_create_ip = spbill_create_ip || "127.0.0.1";
this.urls = sandbox
? {
micropay: "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay",
reverse:
"https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/reverse",
unifiedorder:
"https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder",
orderquery: "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery",
closeorder: "https://api.mch.weixin.qq.com/sandboxnew/pay/closeorder",
refund: "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund",
refundquery:
"https://api.mch.weixin.qq.com/sandboxnew/pay/refundquery",
downloadbill:
"https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill",
downloadfundflow:
"https://api.mch.weixin.qq.com/sandboxnew/pay/downloadfundflow",
send_coupon:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/send_coupon",
query_coupon_stock:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/query_coupon_stock",
querycouponsinfo:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/querycouponsinfo",
transfers:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/promotion/transfers",
gettransferinfo:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/gettransferinfo",
sendredpack:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/sendredpack",
sendgroupredpack:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/sendgroupredpack",
gethbinfo:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/gethbinfo",
paybank:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaysptrans/pay_bank",
querybank:
"https://api.mch.weixin.qq.com/sandboxnew/mmpaysptrans/query_bank"
}
: {
micropay: "https://api.mch.weixin.qq.com/pay/micropay",
reverse: "https://api.mch.weixin.qq.com/secapi/pay/reverse",
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",
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",
transfers:
"https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers",
gettransferinfo:
"https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo",
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",
paybank: "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank",
querybank: "https://api.mch.weixin.qq.com/mmpaysptrans/query_bank",
getpublickey: "https://fraud.mch.weixin.qq.com/risk/getpublickey",
getsignkey: "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey",
combinedorder: "https://api.mch.weixin.qq.com/pay/combinedorder"
};
this.debug = debug;
}
log(...args) {
if (this.debug) console.log(...args);
}
static init(...args) {
return new Payment(...args);
}
static async sandbox(config, debug) {
let { sandbox_signkey } = await Payment.init(config).getSignkey();
return new Payment(
{
...config,
partnerKey: sandbox_signkey,
sandbox: true
},
debug
);
}
async _parse(xml, type, signType) {
let json = {};
if (typeof xml === "string") {
json = await util.parseXML(xml);
} else {
json = xml;
}
switch (type) {
case "middleware_nativePay":
break;
default:
if (json.return_code !== "SUCCESS")
throw new Error(json.return_msg || "XMLDataError");
}
switch (type) {
case "middleware_refund":
case "middleware_nativePay":
case "getsignkey":
break;
default:
if (json.result_code !== "SUCCESS")
throw new Error(json.err_code || "XMLDataError");
}
switch (type) {
case "getsignkey":
break;
case "middleware_refund": {
if (json.appid !== this.appid) throw new Error("appid不匹配");
if (json.mch_id !== this.mchid) throw new Error("mch_id不匹配");
let key = util.md5(this.partnerKey).toLowerCase();
let info = util.decrypt(json.req_info, key);
json.req_info = await util.parseXML(info);
break;
}
case "transfers":
if (json.mchid !== this.mchid) throw new Error("mchid不匹配");
break;
case "sendredpack":
case "sendgroupredpack":
if (json.wxappid !== this.appid) throw new Error("wxappid不匹配");
if (json.mch_id !== this.mchid) throw new Error("mchid不匹配");
break;
case "gethbinfo":
case "gettransferinfo":
if (json.mch_id !== this.mchid) throw new Error("mchid不匹配");
break;
case "send_coupon":
case "query_coupon_stock":
case "querycouponsinfo":
if (json.appid !== this.appid) throw new Error("appid不匹配");
if (json.mch_id !== this.mchid) throw new Error("mch_id不匹配");
break;
case "getpublickey":
break;
case "paybank":
if (json.mch_id !== this.mchid) throw new Error("mchid不匹配");
break;
case "querybank":
if (json.mch_id !== this.mchid) throw new Error("mchid不匹配");
break;
case "combinedorder":
if (json.combine_appid !== this.appid) throw new Error("appid不匹配");
if (json.combine_mch_id !== this.mchid) throw new Error("mch_id不匹配");
if (json.sign !== this._getSign(json, "HMAC-SHA256"))
throw new Error("sign签名错误");
break;
default:
if (json.appid !== this.appid) throw new Error("appid不匹配");
if (json.mch_id !== this.mchid) throw new Error("mch_id不匹配");
if (json.sign !== this._getSign(json, json.sign_type || signType))
throw new Error("sign签名错误");
}
return json;
}
async _parseBill(xml, format = false) {
if (util.checkXML(xml)) {
let json = await util.parseXML(xml);
throw new Error(json.err_code || json.return_msg || "XMLDataError");
}
if (!format) return xml;
let arr = xml
.trim()
.split(/\r?\n/)
.filter(item => item.trim());
let total_data = arr
.pop()
.substr(1)
.split(",`");
let total_title = arr.pop().split(",");
let list_title = arr.shift().split(",");
let list_data = arr.map(item => item.substr(1).split(",`"));
return { total_title, total_data, list_title, list_data };
}
_getSign(params, type = "MD5") {
let str = util.toQueryString(params) + "&key=" + this.partnerKey;
switch (type) {
case "MD5":
return util.md5(str).toUpperCase();
case "HMAC-SHA256":
return util.sha256(str, this.partnerKey).toUpperCase();
default:
throw new Error("signType Error");
}
}
async _request(params, type, cert = false) {
// 安全签名
params.sign = this._getSign(params, params.sign_type);
// 创建请求参数
let pkg = {
method: "POST",
dataType: "text",
data: util.buildXML(params),
timeout: [10000, 15000]
};
if (cert) {
pkg.pfx = this.pfx;
pkg.passphrase = this.mchid;
}
this.log("post data =>\r\n%s\r\n", pkg.data);
let { status, data } = await urllib.request(this.urls[type], pkg);
if (status !== 200) throw new Error("request fail");
this.log("receive data =>\r\n%s\r\n", data);
return ["downloadbill", "downloadfundflow"].indexOf(type) < 0
? this._parse(data, type, params.sign_type)
: data;
}
// Express中间件
middlewareForExpress(type = "pay") {
return async (req, res, next) => {
res.reply = msg => {
res.header("Content-Type", "application/xml; charset=utf-8");
res.send(replyData(msg));
};
res.replyNative = (prepay_id, err_code_des) => {
res.header("Content-Type", "application/xml; charset=utf-8");
res.send(this._getNativeReply(prepay_id, err_code_des));
};
try {
if (typeof req.body !== "string") throw new Error("XMLDataError");
req.weixin = await this._parse(req.body, "middleware_" + type);
} catch (err) {
return res.reply(err.message);
}
next();
};
}
// Koa中间件
middleware(type = "pay") {
return async (ctx, next) => {
ctx.reply = msg => {
ctx.type = "application/xml; charset=utf-8";
ctx.body = replyData(msg);
};
ctx.replyNative = (prepay_id, err_code_des) => {
ctx.type = "application/xml; charset=utf-8";
ctx.body = this._getNativeReply(prepay_id, err_code_des);
};
try {
const xmlData = ctx.request.xmlBody.xml;
Object.keys(xmlData).forEach(key => {
xmlData[key] = xmlData[key][0];
});
ctx.request.weixin = await this._parse(xmlData, "middleware_" + type);
} catch (err) {
return ctx.reply(err.message);
}
await next();
};
}
// 获取沙盒密钥
getSignkey() {
let pkg = {
mch_id: this.mchid,
nonce_str: util.generate()
};
return this._request(pkg, "getsignkey");
}
// 获取RSA公钥
getPublicKey(params) {
let pkg = {
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5"
};
return this._request(pkg, "getpublickey", true);
}
// 获取JS支付参数(自动下单)
async getPayParams(params) {
params.trade_type = params.trade_type || "JSAPI";
let order = await this.unifiedOrder(params);
return this.getPayParamsByPrepay(order, params.sign_type);
}
// 获取JS支付参数(通过预支付会话标志)
getPayParamsByPrepay(params, signType) {
let pkg = {
appId: params.sub_appid || this.appid,
timeStamp: "" + ((Date.now() / 1000) | 0),
nonceStr: util.generate(),
package: "prepay_id=" + params.prepay_id,
signType: signType || "MD5"
};
pkg.paySign = this._getSign(pkg, signType);
pkg.timestamp = pkg.timeStamp;
return pkg;
}
// 获取APP支付参数(自动下单)
async getAppParams(params) {
params.trade_type = params.trade_type || "APP";
let order = await this.unifiedOrder(params);
return this.getAppParamsByPrepay(order, params.sign_type);
}
// 获取APP支付参数(通过预支付会话标志)
getAppParamsByPrepay(params, signType) {
let pkg = {
appid: params.sub_appid || this.appid,
partnerid: params.sub_mch_id || this.mchid,
prepayid: params.prepay_id,
package: "Sign=WXPay",
noncestr: util.generate(),
timestamp: "" + ((Date.now() / 1000) | 0)
};
pkg.sign = this._getSign(pkg, signType);
return pkg;
}
// 扫码支付, 生成URL(模式一)
getNativeUrl(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
time_stamp: "" + ((Date.now() / 1000) | 0),
nonce_str: util.generate()
};
let url =
"weixin://wxpay/bizpayurl" +
"?sign=" +
this._getSign(pkg) +
"&appid=" +
pkg.appid +
"&mch_id=" +
pkg.mch_id +
"&product_id=" +
encodeURIComponent(pkg.product_id) +
"&time_stamp=" +
pkg.time_stamp +
"&nonce_str=" +
pkg.nonce_str;
return url;
}
// 拼装扫码模式一返回值
_getNativeReply(prepay_id, err_code_des) {
let pkg = {
return_code: "SUCCESS",
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
result_code: "SUCCESS",
prepay_id
};
if (err_code_des) {
pkg.result_code = "FAIL";
pkg.err_code_des = err_code_des;
}
pkg.sign = this._getSign(pkg);
return util.buildXML(pkg);
}
// 刷卡支付
micropay(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5",
spbill_create_ip: params.spbill_create_ip || this.spbill_create_ip
};
return this._request(pkg, "micropay");
}
// 撤销订单
reverse(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5"
};
return this._request(pkg, "reverse", true);
}
// 统一下单
unifiedOrder(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5",
notify_url: params.notify_url || this.notify_url,
spbill_create_ip: params.spbill_create_ip || this.spbill_create_ip,
trade_type: params.trade_type || "JSAPI"
};
return this._request(pkg, "unifiedorder");
}
// 订单查询
orderQuery(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5"
};
return this._request(pkg, "orderquery");
}
// 关闭订单
closeOrder(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5"
};
return this._request(pkg, "closeorder");
}
// 申请退款
refund(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5",
op_user_id: params.op_user_id || this.mchid,
notify_url: params.notify_url || this.refund_url
};
if (!pkg.notify_url) delete pkg.notify_url;
return this._request(pkg, "refund", true);
}
// 查询退款
refundQuery(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5"
};
return this._request(pkg, "refundquery");
}
// 合单支付
combinedOrder(params) {
let pkg = {
...params,
combine_appid: this.appid,
combine_mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: "HMAC-SHA256",
notify_url: params.notify_url || this.notify_url,
spbill_create_ip: params.spbill_create_ip || this.spbill_create_ip,
trade_type: params.trade_type || "JSAPI"
};
return this._request(pkg, "combinedorder");
}
// 下载对帐单
async downloadBill(params, format = false) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "MD5",
bill_type: params.bill_type || "ALL"
};
let xml = await this._request(pkg, "downloadbill");
return this._parseBill(xml, format);
}
// 下载资金帐单
async downloadFundflow(params, format = false) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
sign_type: params.sign_type || "HMAC-SHA256",
account_type: params.account_type || "Basic"
};
let xml = await this._request(pkg, "downloadfundflow", true);
return this._parseBill(xml, format);
}
// 发放代金券
sendCoupon(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
openid_count: params.openid_count || 1
};
return this._request(pkg, "send_coupon", true);
}
// 查询代金券批次
queryCouponStock(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate()
};
return this._request(pkg, "query_coupon_stock");
}
// 查询代金券信息
queryCouponInfo(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate()
};
return this._request(pkg, "querycouponsinfo");
}
// 企业付款
transfers(params) {
let pkg = {
...params,
mch_appid: this.appid,
mchid: this.mchid,
nonce_str: util.generate(),
check_name: params.check_name || "FORCE_CHECK",
spbill_create_ip: params.spbill_create_ip || this.spbill_create_ip
};
return this._request(pkg, "transfers", true);
}
// 查询企业付款
transfersQuery(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate()
};
return this._request(pkg, "gettransferinfo", true);
}
// 企业付款到银行卡
async payBank(params) {
const data = await this.getPublicKey(params);
const pub_key = data && data.result_code === "SUCCESS" ? data.pub_key : "";
if (pub_key === "") throw new Error("get publickey fail");
let pkg = {
...params,
mch_id: this.mchid,
nonce_str: util.generate(),
enc_bank_no: util.encryptRSA(pub_key, params.enc_bank_no),
enc_true_name: util.encryptRSA(pub_key, params.enc_true_name)
};
return this._request(pkg, "paybank", true);
}
// 查询企业付款到银行卡
queryBank(params) {
let pkg = {
...params,
mch_id: this.mchid,
nonce_str: util.generate()
};
return this._request(pkg, "querybank", true);
}
// 发送普通红包
sendRedpack(params) {
let pkg = {
...params,
wxappid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
client_ip: params.client_ip || this.spbill_create_ip,
mch_billno:
params.mch_billno ||
(params.mch_autono
? this.mchid + util.getFullDate() + params.mch_autono
: ""),
total_num: params.total_num || 1
};
delete pkg.mch_autono;
return this._request(pkg, "sendredpack", true);
}
// 发送裂变红包
sendGroupRedpack(params) {
let pkg = {
...params,
wxappid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
mch_billno:
params.mch_billno ||
(params.mch_autono
? this.mchid + util.getFullDate() + params.mch_autono
: ""),
total_num: params.total_num || 3,
amt_type: params.amt_type || "ALL_RAND"
};
delete pkg.mch_autono;
return this._request(pkg, "sendgroupredpack", true);
}
// 查询红包记录
redpackQuery(params) {
let pkg = {
...params,
appid: this.appid,
mch_id: this.mchid,
nonce_str: util.generate(),
bill_type: params.bill_type || "MCHT"
};
return this._request(pkg, "gethbinfo", true);
}
}
module.exports = Payment;