UNPKG

wechatpay-axios-plugin

Version:

微信支付APIv2及v3 NodeJS SDK,支持CLI模式请求OpenAPI,支持v3证书下载,v2付款码支付、企业付款、退款,企业微信-企业支付-企业红包/向员工付款,v2&v3 Native支付、扫码支付、H5支付、JSAPI/小程序支付、合单支付...

211 lines (179 loc) 6.88 kB
const assert = require('assert'); const https = require('https'); const { XMLParser, XMLBuilder } = require('fast-xml-parser'); const utils = require('./utils'); const Hash = require('./hash'); const Formatter = require('./formatter'); const SECRET = Symbol('SECRET'); const MCHID = Symbol('MCHID'); const SUCCESS = 'SUCCESS'; const NONCELESS = [ '/cgi-bin/mch/customs/customdeclareorder', '/cgi-bin/mch/customs/customdeclarequery', '/cgi-bin/mch/newcustoms/customdeclareredeclare', '/papay/deletecontract', '/papay/h5entrustweb', '/papay/preentrustweb', '/papay/querycontract', '/papay/partner/h5entrustweb', '/papay/partner/preentrustweb', '/papay/partner/querycontract', '/pay/queryexchagerate', '/secapi/mch/addInstitutionsub', '/secapi/mch/addsubdevconfig', '/secapi/mch/channelsetting', '/secapi/mch/modifyInstitutionsub', '/secapi/mch/modifymchinfo', '/secapi/mch/queryInstitutionsub', '/secapi/mch/querysubdevconfig', ]; const SIGNLESS = [ '/appauth/getaccesstoken', '/mchrisk/querymchrisk', '/mchrisk/setmchriskcallback', '/mchrisk/syncmchriskresult', '/mmpaymkttransfers/gethbinfo', '/mmpaymkttransfers/gettransferinfo', '/mmpaymkttransfers/pay_bank', '/mmpaymkttransfers/promotion/paywwsptrans2pocket', '/mmpaymkttransfers/promotion/querywwsptrans2pocket', '/mmpaymkttransfers/promotion/transfers', '/mmpaymkttransfers/query_bank', '/mmpaymkttransfers/sendminiprogramhb', '/mmpaymkttransfers/sendredpack', '/papay/entrustweb', '/papay/h5entrustweb', '/papay/partner/entrustweb', '/papay/partner/h5entrustweb', '/pay/downloadbill', '/pay/downloadfundflow', '/payitil/report', '/risk/getpublickey', '/risk/getviolation', '/secapi/mch/submchmanage', '/xdc/apiv2getsignkey/sign/getsignkey', ]; const parser = new XMLParser({ ignoreDeclaration: true, parseTagValue: false, processEntities: true, }); const builder = new XMLBuilder({ suppressEmptyNode: true, processEntities: false, tagValueProcessor(_, val) { return /[>'&"<]/.test(val) ? `<![CDATA[${val}]]>` : `${val}`; }, }); /** * Translation the XML string to the javascript's object. * @param {string} xml - XML string * @return {object} - Parsed as object for xml */ function parse(xml) { if (utils.isStream(xml) || xml === undefined) { return xml; } if (utils.isBuffer(xml) && !xml.byteLength) { return null; } if (utils.isString(this.url) && utils.absPath(this.url) === SIGNLESS[0]) { return JSON.parse(xml); } const obj = parser.parse(xml); const root = Object.keys(obj)[0]; return root && root !== '__proto__' ? obj[root] : undefined; } function isGetRequest(thing) { return utils.isObject(thing) && utils.isString(thing.method) && thing.method.toUpperCase() === 'GET'; } /** * Translation the javascript's object to the XML string * @param {object} data - The API request parameters * @return {string} - XML string */ function stringify(data) { if (utils.isBuffer(data) || utils.isStream(data) || isGetRequest(this)) { return data; } if (!utils.isObject(data) && !utils.isString(data)) { throw new TypeError('The input is required.'); } return builder.build({ xml: data }); } /** * An Axios customizaton transform. */ class Transformer { /** * @param {string} mchid - The merchant ID * @param {string|Buffer} secret - The merchant secret key */ constructor(mchid, secret) { this[MCHID] = mchid; this[SECRET] = secret; } get signer() { const key = this[SECRET]; const mch = this[MCHID]; return function signer(data) { if (this.security && utils.isObject(this.merchant) && utils.isString(this.baseURL) && this.baseURL.startsWith('https://')) { Reflect.set(this, 'httpsAgent', new https.Agent(this.merchant)); } if (utils.isObject(this.merchant)) { Reflect.deleteProperty(this, 'merchant'); } const methodIsGet = isGetRequest(this); if (!methodIsGet && data === undefined) { throw new TypeError('The payload(data) must be an object or Buffer or Stream.'); } if (!methodIsGet && !utils.isObject(data)) { return data; } const inter = methodIsGet ? new URLSearchParams(this.params) : new Map(Object.entries(data)); const mchid = inter.get('mch_id') || inter.get('mchid') || inter.get('combine_mch_id'); if (mch) { assert.ok(mch === mchid, `The ${methodIsGet ? 'params' : 'data'}'s merchant ID(${mchid}) doesn't matched the init one(${mch})`); } if (!(this.nonceless && NONCELESS.includes(utils.absPath(this.url))) && !inter.has('nonce_str')) { inter.set('nonce_str', Formatter.nonce()); } const type = inter.get('sign_type'); inter.set('sign', Hash.sign( [Hash.ALGO_MD5, Hash.ALGO_HMAC_SHA256].includes(type) ? type : Hash.ALGO_MD5, Object.fromEntries(inter.entries()), key, )); if (methodIsGet) { Reflect.set(this, 'params', inter); return data; } return Object.fromEntries(inter.entries()); }; } get verifier() { const key = this[SECRET]; return function verifier(data, headers, status) { const pathname = utils.isString(this.url) && utils.absPath(this.url); if (pathname === SIGNLESS[0] && utils.isObject(data) && data.retcode !== 0) { return utils.buildBusinessError( { data, headers, status }, 'Verify the response\'s data is failed because %s\'s value is %s.', 'EV2_RES_RESULT_CODE_NOT_MATCHED', 'retcode', data.retcode, ); } if (pathname !== SIGNLESS[0] && utils.isObject(data) && (data.return_code !== SUCCESS || (Object.prototype.hasOwnProperty.call(data, 'result_code') && data.result_code !== SUCCESS))) { return utils.buildBusinessError( { data, headers, status }, 'Verify the response\'s data is failed because %s\'s value is %s.', 'EV2_RES_STATUS_CODE_NOT_SUCESS', ...( data.return_code !== SUCCESS ? ['return_code', data.return_code] : ['result_code', data.result_code] ), ); } if (SIGNLESS.includes(pathname)) { return data; } const type = data && data.sign && data.sign.length === 64 ? Hash.ALGO_HMAC_SHA256 : Hash.ALGO_MD5; const sign = Hash.sign(type, data, key); if (!Hash.equals(sign, data.sign)) { return utils.buildBusinessError( { data, headers, status }, 'The response\'s sign(%s) doesn\'t matched the local calculated(%s).', 'EV2_RES_SIGNATURE_NOT_EQUAL', data.sign, sign, ); } return data; }; } get request() { return [this.signer, stringify]; } get response() { return [parse, this.verifier]; } static toObject = parse static toXml = stringify static get default() { return this; } } module.exports = Transformer;