UNPKG

tenpay-v2

Version:
713 lines (634 loc) 20.5 kB
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;