UNPKG

wechatpay-axios-plugin

Version:

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

734 lines (689 loc) 29 kB
/// <reference types="node" /> import { AgentOptions } from "https"; import { CipherKey, BinaryLike, KeyLike, KeyObject } from 'crypto' import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; import { default as Base } from '@thenorthmemory/multipart'; /** * Wechatpay Axios Plugin */ export namespace WechatpayAxiosPlugin { /** * Aes - Advanced Encryption Standard */ class Aes { /** * @property {object} pkcs7 - The PKCS7 padding/unpadding container */ static get pkcs7(): { /** * padding, 32 bytes/256 bits `secret key` may optional need the last block. * @memberof Aes.pkcs7# * * @param {BinaryLike} thing - The input * @param {boolean} [optional = true] - The flag for the last padding, default `true` * * @return {Buffer} - The PADDING tailed payload */ padding(thing: BinaryLike, optional?: boolean): Buffer; /** * unpadding * @memberof Aes.pkcs7# * * @param {BinaryLike} thing - The input * @return {Buffer} - The PADDING wiped payload */ unpadding(thing: BinaryLike): Buffer; }; } /** * Aes - Advanced Encryption Standard */ namespace Aes { /** * Aes encrypt/decrypt using `aes-256-gcm` algorithm with `AAD`. */ class AesGcm { /** * Encrypts plaintext. * * @param {string} plaintext - Text to encode. * @param {CipherKey} key - The secret key. * @param {BinaryLike} iv - The initialization vector. * @param {string} aad - The additional authenticated data, maybe empty string. * * @returns {string} Base64-encoded ciphertext. */ static encrypt(plaintext: string, key: CipherKey, iv: BinaryLike, aad?: string): string; /** * Decrypts ciphertext. * * @param {string} ciphertext - Base64-encoded ciphertext. * @param {CipherKey} key - The secret key. * @param {BinaryLike} iv - The initialization vector. * @param {string} aad - The additional authenticated data, maybe empty string. * * @returns {string} Utf-8 plaintext. */ static decrypt(ciphertext: string, key: CipherKey, iv: BinaryLike, aad?: string): string; } /** * Aes encrypt/decrypt using `aes-256-ecb` algorithm with pkcs7padding. */ class AesEcb { /** * Encrypts plaintext. * * @param {string} plaintext - Text to encode. * @param {CipherKey} key - The secret key. * * @returns {string} Base64-encoded ciphertext. */ static encrypt(plaintext: string, key: CipherKey): string; /** * Decrypts ciphertext. * * @param {string} ciphertext - Base64-encoded ciphertext. * @param {CipherKey} key - The secret key. * * @returns {string} Utf-8 plaintext. */ static decrypt(ciphertext: string, key: CipherKey): string; } /** * Aes encrypt/decrypt using `aes-128-cbc` algorithm with pkcs7padding. */ class AesCbc { /** * Encrypts plaintext. * * @param {string} plaintext - Text to encode. * @param {CipherKey} key - The secret key. * @param {BinaryLike} [iv] - The initialization vector. * * @returns {string} Base64-encoded ciphertext. */ static encrypt(plaintext: string, key: CipherKey, iv: BinaryLike): string; /** * Decrypts ciphertext. * * @param {string} ciphertext - Base64-encoded ciphertext. * @param {CipherKey} key - The secret key. * @param {BinaryLike} [iv] - The initialization vector. * * @returns {string} Utf-8 plaintext. */ static decrypt(ciphertext: string, key: CipherKey, iv: BinaryLike): string; } } /** * Crypto hash functions utils. */ class Hash { static ALGO_MD5: 'MD5'; static ALGO_HMAC_SHA256: 'HMAC-SHA256'; static isKeyObject(thing: any): boolean; static keyObjectFrom(thing: BinaryLike|KeyObject): KeyObject; /** * Calculate the input string with an optional secret `key` in MD5, * when the `key` is Falsey, this method works as normal `MD5`. * * @since v0.9.2 - The first argument is supported a `KeyObject`. * * @param {BinaryLike|KeyObject} thing - The input. * @param {CipherKey} [key] - The secret key. * @param {boolean|number|string} [agency = false] - `true` or better of the `AgentId` value. * * @return {string} - data signature */ static md5(thing: BinaryLike|KeyObject, key?: CipherKey, agency?: boolean | number | string): string; /** * Calculate the input string with a secret `key` as of `algorithm` string which is one of the 'sha256', 'sha512' etc. * @param {BinaryLike} thing - The input. * @param {CipherKey} key - The secret key. * @param {string} [algorithm = 'sha256'] - The algorithm string, default is `sha256`. * @return {string} - data signature */ static hmac(thing: BinaryLike, key: CipherKey, algorithm?: string): string; /** * Calculate the input in SHA1. * @param {BinaryLike} thing - The input. * @return {string} - data signature */ static sha1(thing: BinaryLike): string; /** * Calculate the input in SHA256. * @param {BinaryLike} thing - The input. * @return {string} - data signature */ static sha256(thing: BinaryLike): string; /** * Wrapping the builtins `crypto.timingSafeEqual` function. * @param {BinaryLike} known - The string of known length to compare against. * @param {BinaryLike} [user] - The user-supplied string. * @return {boolean} - Returns true when the two are equal, false otherwise. */ static equals(known: BinaryLike, user?: BinaryLike): boolean; /** * Utils of the data signature calculation. * @param {'MD5'|'HMAC-SHA256'} type - The sign type, one of the MD5 or HMAC-SHA256. * @param {object} data - The input data. * @param {CipherKey} key - The secret key string. * @return {string} - The data signature. */ static sign(type: 'MD5' | 'HMAC-SHA256', data: object, key: CipherKey): string; } /** * An Axios customizaton transform. */ class Transformer { constructor(mchid?: string, secret?: KeyLike); /** * Compose the pre-request data signature * * Note here: While the [MCHID] is set, then checking the input data matching with it. * * @param {object} data - The API request parameters * @return {object} - With data signature */ signer(data: object): object; /** * Translation the javascript's object to the XML string * @param {object} data - The API request parameters * @return {string} - XML string */ static toXml(data: object): string; /** * @property {array} request - @see {import('axios').AxiosTransformer} */ get request(): (typeof this.signer | typeof Transformer.toXml)[]; /** * Translation the XML string to the javascript's object. * @param {string} xml - XML string * @return {object} - Parsed as object */ static toObject(xml: string): object; /** * Validation the response data with the `sign` string. * @param {object} data - The API response data * @return {object} - The API response data */ verifier(data: object): object; /** * @property {array} response - @see {import('axios').AxiosTransformer} */ get response(): (typeof Transformer.toObject | typeof this.verifier)[]; } /** * Provides easy used methods using in this project. */ class Formatter { /** * Cast the `CSV` bill. * * @param {string|Buffer} buffer - CSV file content. * * @returns {object} - The casted source intputs as {rows, summary} Object */ static castCsvBill(buffer: string | Buffer): object; /** * Cast the `CSV` line string by the keys named object. * * @param {string} row - CSV line. * @param {array} [keys] - CSV headers. * @param {boolean} [skipFirstChar = true] - Skip the first character of the CSV line, default is true. * @param {string} [separator = ',`'] - Split separator, default is ',`' (two chars). * * @returns {object} - The casted source line as Object */ static castCsvLine(row: string, keys?: string[], skipFirstChar?: boolean, separator?: string): object; /** * Generate a Base62 random string aka `nonce`, similar as `crypto.randomBytes`. * * @param {number} [size = 32] - Nonce string length, default is 32 bytes. * * @returns {string} Base62 random string. */ static nonce(size?: number): string; /** * Retrieve the current `Unix` timestamp. * * @returns {number} Epoch timestamp. */ static timestamp(): number; /** * Formatting for the heading `Authorization` value. * * @param {string} mchid - The merchant ID. * @param {string} nonce - The Nonce string. * @param {string} signature - The base64-encoded `Rsa.sign` ciphertext. * @param {string|number} timestamp - The `Unix` timestamp. * @param {string} serial - The serial number of the merchant public certification. * * @returns {string} - The APIv3 Authorization `header` value */ static authorization(mchid: string, nonce: string, signature: string, timestamp: string | number, serial: string): string; /** * Formatting this `HTTP.request` for `Rsa.sign` input. * * @param {string} method - The merchant ID. * @param {string} uri - Combined string with `URL.pathname` and `URL.search`. * @param {string|number} timestamp - The `Unix` timestamp, should be the one used in `authorization`. * @param {string} nonce - The `Nonce` string, should be the one used in `authorization`. * @param {string} [body = ''] - The playload string, HTTP `GET` should be an empty string. * * @returns {string} - The content for `Rsa.sign` */ static request(method: string, uri: string, timestamp: string | number, nonce: string, body?: string): string; /** * Formatting this `HTTP.response` for `Rsa.verify` input. * * @param {string|number} timestamp - The `Unix` timestamp, should be the one from `response.headers[wechatpay-timestamp]`. * @param {string} nonce - The `Nonce` string, should be the one from `response.headers[wechatpay-nonce]`. * @param {string} [body = ''] - The response payload string, HTTP status(`204`) should be an empty string. * * @returns {string} - The content for `Rsa.verify` */ static response(timestamp: string | number, nonce: string, body?: string): string; /** * Joined this inputs by for `Line Feed`(LF) char. * * @param {string[]} pieces - The string(s) joined by line feed. * * @returns {string} - The joined string. */ static joinedByLineFeed(...pieces: string[]): string; /** * Sorts an Object by key. * * @param {object} thing - The input object. * * @returns {object} - The sorted object. */ static ksort(thing: object): object; /** * Like `queryString` does but without the `sign` and `empty value` entities. * * @param {object} thing - The input object. * * @returns {string} - The sorted object. */ static queryStringLike(thing: object): string; } /** * This SDK mandatory configuration */ type apiConfig = { /** The merchant ID */ mchid: string, /** The serial number of the merchant certificate, only for APIv3 */ serial: string, /** The merchant private key, only for APIv3 */ privateKey: string | Buffer, /** The wechatpay platform certificates in {serial: publicKey} format, only for APIv3 */ certs: platformCertificates, /** The merchant secret key string, only for APIv2 */ secret?: string, /** The merchant private key and certificate configuration for APIv2, while there were required in secure communication. */ merchant?: AgentOptions } type platformCertificates = { [key: string]: KeyLike } /** * Signature for the extra request config such as the `uri_template` parameter(s). */ type ExtraRequestConfig<T> = Omit<T, keyof AxiosRequestConfig>; /** * Simple and lite of `multipart/form-data` implementation, most similar to `form-data`. * * @since v0.7.0 * @example * // buffer style(Synchronous) * (new Multipart()) * .append('a', 1) * .append('b', '2') * .append('c', Buffer.from('31')) * .append('d', JSON.stringify({}), 'any.json') * .append('e', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg') * .getBuffer(); * // stream style(Asynchronous) * .pipe(require('fs').createWriteStream('./file3.jpg')); */ class Multipart extends Base { static get [Symbol.toStringTag](): "FormData"; /** * @returns {string} - FormData string */ get [Symbol.toStringTag](): "FormData"; /** * The WeChatPay APIv3' specific, the `meta` JSON * * @return {object<string, string>|null} - The `meta` information. */ toJSON(): Multipart.MetaField | null; } namespace Multipart { type FileMetaGeneral = { filename: string sha1: string } type FileMetaSpecial = { file_name: string file_digest: string } type FileMetaWithBankType = { filename: string sha256: string bank_type: string } type FileMetaWithTransaction = { transaction_id: string transaction_mchid: string transaction_sub_mchid?: string out_trade_no: string openid: string sha256: string upload_time: string merchant_contact_information: { consultation_phone_number: string } } type FileMetaWithFapiao = { sub_mchid?: string file_type: 'PDF' | 'OFD' digest_alogrithm: 'SM3' digest: string } type FileMetaWithTaxiFapiao = { company_mchid: string region_id: number digest_algorithm: 'DIGEST_ALGORITHM_SM3' digest: string } type MetaField = FileMetaGeneral | FileMetaSpecial | FileMetaWithBankType | FileMetaWithTransaction | FileMetaWithFapiao | FileMetaWithTaxiFapiao class FormData extends Multipart {} } /** * Provides some methods for the RSA `sha256WithRSAEncryption` with `RSA_PKCS1_OAEP_PADDING`. */ class Rsa { /** * Alias of the `RSA_PKCS1_OAEP_PADDING` mode */ static RSA_PKCS1_OAEP_PADDING: 4; /** * Alias of the `RSA_PKCS1_PADDING` mode * @deprecated see [CVE-2023-46809](https://nodejs.org/en/blog/vulnerability/february-2024-security-releases) */ static RSA_PKCS1_PADDING: 1; static KEY_TYPE_PUBLIC: 'public'; static KEY_TYPE_PRIVATE: 'private'; static isKeyObject(thing: any): boolean; static fromPkcs8(str: string): KeyObject; static fromPkcs1(str: string, type: 'public' | 'private'): KeyObject; static fromSpki(str: string): KeyObject; static from(thing: KeyLike, type: 'public' | 'private'): KeyObject; /** * Encrypts text with sha256WithRSAEncryption/RSA_PKCS1_OAEP_PADDING. * * @param {string} plaintext - Cleartext to encode. * @param {KeyLike} publicKey - A public key. * @param {number} [padding] - Supporting `RSA_PKCS1_OAEP_PADDING`, default is `RSA_PKCS1_OAEP_PADDING`. * * @returns {string} Base64-encoded ciphertext. */ static encrypt(plaintext: string, publicKey: KeyLike, padding?: number): string; /** * Decrypts base64 encoded string with `privateKey`. * * @param {string} ciphertext - Was previously encrypted string using the corresponding public certificate. * @param {KeyLike} privateKey - A public key. * @param {number} [padding] - Supporting `RSA_PKCS1_OAEP_PADDING`, default is `RSA_PKCS1_OAEP_PADDING`. * * @returns {string} Utf-8 plaintext. */ static decrypt(ciphertext: string, privateKey: KeyLike, padding?: number): string; /** * Creates and returns a `Sign` string that uses `sha256WithRSAEncryption`. * * @param {string} message - Content will be `crypto.Sign`. * @param {KeyLike} privateKey - A PEM encoded private key certificate. * * @returns {string} Base64-encoded signature. */ static sign(message: string, privateKey: KeyLike): string; /** * Verifying the `message` with given `signature` string that uses `sha256WithRSAEncryption`. * * @param {string} message - Content will be `crypto.Verify`. * @param {string} signature - The base64-encoded ciphertext. * @param {KeyLike} publicKey - A PEM encoded public certificate. * * @returns {boolean} True is passed, false is failed. */ static verify(message: string, signature: string, publicKey: KeyLike): boolean; } /** * Decorate the `Axios` instance */ class Decorator { /** * @property {object} defaults - The defaults configuration whose pased in `Axios`. */ static get defaults(): { baseURL: string; headers: { Accept: string, 'Content-Type': string, 'User-Agent': string, }; }; /** * Deep merge the input with the defaults * * @param {object} config - The configuration. * * @returns {object} - With the built-in configuration. */ static withDefaults(config?: object): object; /** * Create an APIv2's client * * @param {object} config - configuration * @param {string} [config.mchid] - The merchant ID * @param {KeyLike} [config.secret] - The merchant secret key string * @param {object} [config.merchant] - The merchant private key and certificate AKA {@link AgentOptions} for APIv2, while there were required in secure communication. * @param {BinaryLike} [config.merchant.cert] - The merchant certificate in PEM format * @param {KeyLike} [config.merchant.key] - The merchant private key in PEM format * @param {BinaryLike} [config.merchant.pfx] - The merchant private key and certificate chain in PFX or PKCS12 format. * @param {BinaryLike} [config.merchant.passphrase] - The merchant shared passphrase used for a single private key and/or a PFX. * * @returns {AxiosInstance} - The axios instance */ static xmlBased(config?: { mchid?: string; secret?: string; merchant?: AgentOptions; }): AxiosInstance; /** * APIv3's requestInterceptor * * @return {function} Named `signer` function */ static requestInterceptor(): Function; /** * APIv3's responseVerifier * @param {object} certs The wechatpay platform serial and certificate(s), `{serial: publicKey}` pair * @return {function} Named as `verifier` function */ static responseVerifier(certs?: platformCertificates): Function; /** * Create an APIv3's client * * @param {object} config - configuration * @param {string} config.mchid - The merchant ID * @param {string} config.serial - The serial number of the merchant certificate * @param {KeyLike} config.privateKey - The merchant private key * @param {object} config.certs - The wechatpay provider size configuration, `{serial: publicKey}` pair * * @returns {AxiosInstance} - The axios instance */ static jsonBased(config?: { mchid: string; serial: string; privateKey: KeyLike; certs: platformCertificates; }): AxiosInstance; /** * Decorate factory * @param {object} config - configuration * @param {string} config.mchid - The merchant ID * @param {string} config.serial - The serial number of the merchant certificate * @param {KeyLike} config.privateKey - The merchant private key * @param {object} config.certs - The wechatpay provider size configuration, `{serial: publicKey}` pair * @param {KeyLike} [config.secret] - The merchant secret key for APIv2 * @param {object} [config.merchant] - The merchant private key and certificate AKA {@link AgentOptions} for APIv2, while there were required in secure communication. * @param {BinaryLike} [config.merchant.cert] - The merchant certificate in PEM format * @param {KeyLike} [config.merchant.key] - The merchant private key in PEM format * @param {BinaryLike} [config.merchant.pfx] - The merchant private key and certificate chain in PFX or PKCS12 format. * @param {BinaryLike} [config.merchant.passphrase] - The merchant shared passphrase used for a single private key and/or a PFX. * @constructor */ constructor(config: apiConfig & AxiosRequestConfig); /** * Getter APIv2's client (xmlBased) * * @returns {AxiosInstance} - The axios instance */ get v2(): AxiosInstance; /** * Getter APIv3's client (jsonBased) * * @returns {AxiosInstance} - The axios instance */ get v3(): AxiosInstance; /** * Request the remote `pathname` by a HTTP `method` verb * * @param {string} [pathname] - The pathname string. * @param {string} [method] - The method string. * @param {any} [data] - The data. * @param {any} [config] - The config. * * @returns {PromiseLike} - The `AxiosPromise` */ request<T = any, R = AxiosResponse<T>, D = any>(pathname?: string | undefined, method?: string | undefined, data?: object | any, config?: ExtraRequestConfig<D> & AxiosRequestConfig): PromiseLike<R>; } /** * Promise based and chained WeChatPay OpenAPI v2&v3's client. * * @example * const {Wechatpay,Rsa} = require('wechatpay-axios-plugin'); * const wxpay = new Wechatpay({ * mchid, * serial, * privateKey: Rsa.from('file:///path/to/wechatpay/apiclient_key.pem', Rsa.KEY_TYPE_PRIVATE), * certs: { * [`${pubkey_id}`]: Rsa.from('file:///path/to/wechatpay/publickey.pem', Rsa.KEY_TYPE_PUBLIC), * }, * secret, * merchant: { * cert, * key, * // pfx, * // passphase, * } * }); * * wxpay.v2.pay.micropay({}).then(console.info).catch(console.error); * * wxpay.v2.secapi.pay.refund.POST({}).then(console.info).catch(console.error); * * wxpay.v3.marketing.busifavor.stocks.post({}) * .then(({data}) => console.info(data)) * .catch(({response: {data}}) => console.error(data)); * * wxpay.v3.pay.transactions.native.post({}) * .then(({data: {code_url}}) => console.info(code_url)) * .catch(({ response: {data}}) => console.error(data)); * * (async () => { * try { * const {data: detail} = await wxpay.v3.pay.transactions.id._transaction_id_ * .get({params: {mchid: '1230000109'}, transaction_id: '1217752501201407033233368018'}); * // or simple like this * // const {data: detail} = await wxpay.v3.pay.transactions.id['{transaction_id}'] * // .get({params: {mchid: '1230000109'}, transaction_id: '1217752501201407033233368018'}); * console.info(detail); * } catch({response: {status, statusText, data}}) { * console.error(status, statusText, data); * } * })(); */ class Wechatpay { // @ts-ignore: FIXEME, needs contributing get client(): Decorator; /** * Constructor of the magic APIv2&v3's `chain`. * @param {object} config - @see {apiConfig} * @constructor */ constructor(config: apiConfig & AxiosRequestConfig) /** * @property {function} get - The alias of the HTTP `GET` request * @param {any} config - The request configuration * @returns {PromiseLike} - The `AxiosPromise` */ // @ts-ignore: FIXEME, needs contributing async get<T = any, R = AxiosResponse<T>>(config?: ExtraRequestConfig & AxiosRequestConfig): Promise<R>; /** * @property {function} post - The alias of the HTTP `POST` request * @param {any} data - The request post body * @param {any} config - The request configuration * @returns {PromiseLike} - The `AxiosPromise` */ // @ts-ignore: FIXEME, needs contributing async post<T = any, R = AxiosResponse<T>>(data?: T, config?: ExtraRequestConfig & AxiosRequestConfig): Promise<R>; /** * @property {function} put - The alias of the HTTP 'PUT' request * @param {any} data - The request post body * @param {any} config - The request configuration * @returns {PromiseLike} - The `AxiosPromise` */ // @ts-ignore: FIXEME, needs contributing async put<T = any, R = AxiosResponse<T>>(data?: T, config?: ExtraRequestConfig & AxiosRequestConfig): Promise<R>; /** * @property {function} patch - The alias of the HTTP 'PATCH' request * @param {any} data - The request post body * @param {any} config - The request configuration * @returns {PromiseLike} - The `AxiosPromise` */ // @ts-ignore: FIXEME, needs contributing async patch<T = any, R = AxiosResponse<T>>(data?: T, config?: ExtraRequestConfig & AxiosRequestConfig): Promise<R>; /** * @property {function} delete - The alias of the HTTP 'DELETE' request * @param {any} config - The request configuration * @returns {PromiseLike} - The `AxiosPromise` */ // @ts-ignore: FIXEME, needs contributing async delete<T = any, R = AxiosResponse<T>>(config?: ExtraRequestConfig & AxiosRequestConfig): Promise<R>; // @ts-ignore: FIXEME, needs contributing chain(thing: string): this [key: string]: this } } export class Hash extends WechatpayAxiosPlugin.Hash{} export class Transformer extends WechatpayAxiosPlugin.Transformer{} export class Formatter extends WechatpayAxiosPlugin.Formatter{} export class Aes extends WechatpayAxiosPlugin.Aes{} export class Multipart extends WechatpayAxiosPlugin.Multipart {} export class FormData extends Multipart {} export class Rsa extends WechatpayAxiosPlugin.Rsa{} export class Decorator extends WechatpayAxiosPlugin.Decorator{} export class Wechatpay extends WechatpayAxiosPlugin.Wechatpay {} export default Wechatpay;