UNPKG

@chenbz/wx_pay_v3

Version:
549 lines (548 loc) 21.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WxPayV3 = void 0; // 导入uuid const uuid_1 = require("uuid"); // 导入crypto const crypto = require("crypto"); // 导入工具类 const utils_1 = require("./utils"); /** * [官方文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml) */ class WxPayV3 { constructor(config) { this.config = { appId: '', mchId: '', apiKeyV3: '', serialNo: '', privateKey: '', publicKey: '', payNotifyUrl: '', refundNotifyUrl: '', // 退款对调地址 }; this.config = config; } /** * @desc 生成签名 * @param data {object} 需要加密的参数 * @return string * @author chenbz * @date 2022-09-28 */ createSignature(data) { const { privateKey } = this.config; let signatureStr = ''; for (const key in data) { signatureStr += `${data[key]}\n`; } ; const signatureBase64 = crypto.createSign('RSA-SHA256').update(signatureStr, 'utf-8').sign(privateKey, 'base64'); return signatureBase64; } /** * @desc 获取请求头token * @param method {string} 请求类型 * @param url {string} 请求路径 * @param body {object} 请求参数 * @return promise * @author chenbz * @date 2022-09-28 */ getAuthorization(method = 'GET', url, body = '') { const { mchId, serialNo } = this.config; const timeStamp = utils_1.date.getTimestamp10(); const nonceStr = utils_1.random.str(32); const signatureData = { method, url, timestamp: timeStamp, nonce_str: nonceStr }; if (method === 'GET') { signatureData.body = ''; } else { signatureData.body = JSON.stringify(body); } const signature = this.createSignature(signatureData); return `WECHATPAY2-SHA256-RSA2048 mchid="${mchId}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timeStamp}",serial_no="${serialNo}"`; } /** * @desc 验证签名 * @param signature {string} 签名 => http请求头['wechatpay-signature'] * @param timestamp {string} 时间戳 => http请求头['wechatpay-timestamp'] * @param nonce {string} 随机字符串 => http请求头['wechatpay-nonce'] * @param data {object} 回调数据 => 应答主体 * @return boolean * @author chenbz * @date 2022-09-28 */ verifySignature(signature, timestamp, nonce, data) { const { publicKey } = this.config; const signatureStr = `${timestamp}\n${nonce}\n${JSON.stringify(data)}\n`; return crypto.createVerify('RSA-SHA256') .update(signatureStr) .verify(publicKey, signature, 'base64'); } /** * @desc 解密AES * @param cipherText {string} 密文 * @param add {string} associated_data字符串 * @param iv {string} nonce字符串 * @return object * @author chenbz * @date 2022-09-28 */ decryptAES(cipherText, add, iv) { const { apiKeyV3 } = this.config; cipherText = decodeURIComponent(cipherText); const ciphertext = Buffer.from(cipherText, 'base64'); const authTag = ciphertext.slice(ciphertext.length - 16); const data = ciphertext.slice(0, ciphertext.length - 16); const decipher = crypto.createDecipheriv('aes-256-gcm', apiKeyV3, iv); decipher.setAuthTag(authTag); decipher.setAAD(Buffer.from(add)); let decryptedText = decipher.update(data, undefined, 'utf-8'); decryptedText += decipher.final(); try { return JSON.parse(decryptedText); } catch (e) { return decryptedText; } } /** * @desc 生成订单号(使用uuid确保唯一性) * @return string * @author chenbz * @date 2022-09-28 */ createOrderNo() { const newUuid = (0, uuid_1.v4)(); const outTradeNo = newUuid.replaceAll("-", ""); return outTradeNo; } /** * @desc jsAPI * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param payerOpenId {string} 用户在直连商户appId下的唯一标识 => openId * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ jsApi(outTradeNo, payerOpenId, amountTotal, description, options) { const { appId, mchId, payNotifyUrl } = this.config; const url = '/v3/pay/transactions/jsapi'; const data = { appid: appId, mchid: mchId, out_trade_no: outTradeNo, description, notify_url: payNotifyUrl, amount: { total: amountTotal, currency: 'CNY' }, payer: { openid: payerOpenId, } }; const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc jsApi支付 * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param payerOpenId {string} 用户在直连商户appId下的唯一标识 => openId * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ jsApiPay(outTradeNo, payerOpenId, amountTotal, description, options) { return __awaiter(this, void 0, void 0, function* () { const { appId } = this.config; const timeStamp = utils_1.date.getTimestamp10(); const nonceStr = utils_1.random.str(32); const { data: { prepay_id: prepayId } } = yield this.jsApi(outTradeNo, payerOpenId, amountTotal, description, options); const signatureData = { appId, timeStamp, nonceStr, package: `prepay_id=${prepayId}`, }; return { appId, timeStamp, nonceStr, package: `prepay_id=${prepayId}`, signType: 'RSA', paySign: this.createSignature(signatureData), }; }); } /** * @desc 微信小程序支付 * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param payerOpenId {string} 用户在直连商户appId下的唯一标识 => openId * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ wmpPay(outTradeNo, payerOpenId, amountTotal, description, options) { return __awaiter(this, void 0, void 0, function* () { return this.jsApiPay(outTradeNo, payerOpenId, amountTotal, description, options); }); } /** * @desc h5支付 * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param payerClientIp {string} 用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。 * @param options {any} 选项 => 可覆盖已有参数 * @return string * @author chenbz * @date 2022-09-28 */ h5Pay(outTradeNo, amountTotal, description, payerClientIp = '127.0.0.1', options) { const { appId, mchId, payNotifyUrl } = this.config; const url = '/v3/pay/transactions/h5'; const data = { appid: appId, mchid: mchId, out_trade_no: outTradeNo, description, notify_url: payNotifyUrl, amount: { total: amountTotal, currency: 'CNY' }, scene_info: { payer_client_ip: payerClientIp, h5_info: { type: 'Wap' } } }; const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc native支付 * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param options {any} 选项 => 可覆盖已有参数 * @return string * @author chenbz * @date 2022-09-28 */ nativePay(outTradeNo, amountTotal, description, options) { const { appId, mchId, payNotifyUrl } = this.config; const url = '/v3/pay/transactions/native'; const data = { appid: appId, mchid: mchId, out_trade_no: outTradeNo, description, notify_url: payNotifyUrl, amount: { total: amountTotal, currency: 'CNY' }, }; const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc app支付 * @param outTradeNo {string} 商户订单号 => 可调用"createOrderNo()"方法生成 * @param amountTotal {number} 订单总金额(单位:分) * @param description {string} 商品描述 * @param options {any} 选项 => 可覆盖已有参数 * @return string * @author chenbz * @date 2022-09-28 */ appPay(outTradeNo, amountTotal, description, options) { const { appId, mchId, payNotifyUrl } = this.config; const url = '/v3/pay/transactions/app'; const data = { appid: appId, mchid: mchId, out_trade_no: outTradeNo, description, notify_url: payNotifyUrl, amount: { total: amountTotal, currency: 'CNY' }, }; const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc 根据微信支付订单号查询 * @param transactionId {string} 微信支付订单号 * @return object * @author chenbz * @date 2022-09-28 */ getOrderByTransactionId(transactionId) { const { mchId } = this.config; const url = `/v3/pay/transactions/id/${transactionId}?mchid=${mchId}`; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } /** * @desc 根据商户订单号查询 * @param outTradeNo {string} 商户订单号 * @return object * @author chenbz * @date 2022-09-28 */ getOrderByOutTradeNo(outTradeNo) { const { mchId } = this.config; const url = `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${mchId}`; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } /** * @desc 关闭订单 * @param outTradeNo {string} 商户订单号 * @return object * @author chenbz * @date 2022-09-28 */ closeOrderByOutTradeNo(outTradeNo) { const { mchId } = this.config; const url = `/v3/pay/transactions/out-trade-no/${outTradeNo}/close`; const postData = { mchid: mchId }; const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, postData, headers); } /** * @desc 退款 * @param refundWay {IRefundWay} 退款方式 => 微信支付订单号 || 商户订单号 * @param outRefundNo {string} 商户退款单号 => 可调用"createOrderNo()"方法生成 * @param amountRefund {number} 退款金额 * @param amountTotal {number} 原订单金额 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ refundDomestic(refundWay, outRefundNo, amountTotal, amountRefund, options) { const { refundNotifyUrl } = this.config; const url = '/v3/refund/domestic/refunds'; const data = Object.assign(Object.assign({}, refundWay), { out_refund_no: outRefundNo, notify_url: refundNotifyUrl, amount: { refund: amountRefund, total: amountTotal, currency: 'CNY' } }); const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc 根据"微信支付订单号"退款 * @param transactionId {string} 微信支付订单号 * @param outRefundNo {string} 商户退款单号 => 可调用"createOrderNo()"方法生成 * @param amountRefund {number} 退款金额 * @param amountTotal {number} 原订单金额 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ refundDomesticByTransactionId(transactionId, outRefundNo, amountTotal, amountRefund, options) { return this.refundDomestic({ transaction_id: transactionId }, outRefundNo, amountRefund, amountTotal, options); } /** * @desc 根据"商户订单号"退款 * @param outTradeNo {string} 商户订单号 * @param outRefundNo {string} 商户退款单号 => 可调用"createOrderNo()"方法生成 * @param amountRefund {number} 退款金额 * @param amountTotal {number} 原订单金额 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-28 */ refundDomesticByOutTradeNo(outTradeNo, outRefundNo, amountTotal, amountRefund, options) { return this.refundDomestic({ out_trade_no: outTradeNo }, outRefundNo, amountRefund, amountTotal, options); } /** * @desc 查询单笔退款 * @param outRefundNo {string} 商户退款单号 * @return object * @author chenbz * @date 2022-09-28 */ getRefundDomesticByOutRefundNo(outRefundNo) { const url = `/v3/refund/domestic/refunds/${outRefundNo}`; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } /** * @desc 获取申请交易账单 * @param billDate {string} 账单日期 * @param billType {string} 账单类型 => [ALL:返回当日所有订单信息(不含充值退款订单)] [SUCCESS:返回当日成功支付的订单(不含充值退款订单)] [REFUND:返回当日退款订单(不含充值退款订单)] * @param tarType {string} 压缩类型 * @return object * @author chenbz * @date 2022-09-29 */ getTradeBill(billDate, billType = 'ALL', tarType = 'GZIP') { const url = `/v3/bill/tradebill?bill_date=${billDate}&bill_type=${billType}&tar_type=${tarType}`; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } /** * @desc 获取申请资金账单 * @param billDate {string} 账单日期 * @param accountType {string} 资金账户类型 => [BASIC:基本账户] [OPERATION:运营账户] [FEES:手续费账户] * @param tarType {string} 压缩类型 * @return object * @author chenbz * @date 2022-09-29 */ getFundFlowBill(billDate, accountType = 'BASIC', tarType = 'GZIP') { const url = `/v3/bill/fundflowbill?bill_date=${billDate}&account_type=${accountType}&tar_type=${tarType}`; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } /** * @desc 下载申请交易账单 * @param billDate {string} 账单日期 * @param billType {string} 账单类型 * @param tarType {string} 压缩类型 * @return 账单文件的数据流 * @author chenbz * @date 2022-09-29 */ downloadTradeBill(billDate, billType = 'ALL', tarType = 'GZIP') { return __awaiter(this, void 0, void 0, function* () { const tradeBill = yield this.getTradeBill(billDate, billType, tarType); const { data: { download_url: downloadUrl } } = tradeBill; const url = downloadUrl.replace('https://api.mch.weixin.qq.com', ''); const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); }); } /** * @desc 下载申请资金账单 * @param billDate {string} 账单日期 * @param accountType {string} 资金账户类型 => [BASIC:基本账户] [OPERATION:运营账户] [FEES:手续费账户] * @param tarType {string} 压缩类型 * @return 账单文件的数据流 * @author chenbz * @date 2022-09-29 */ downloadFundFlowBill(billDate, accountType = 'BASIC', tarType = 'GZIP') { return __awaiter(this, void 0, void 0, function* () { const fundFlowBill = yield this.getFundFlowBill(billDate, accountType, tarType); const { data: { download_url: downloadUrl } } = fundFlowBill; const url = downloadUrl.replace('https://api.mch.weixin.qq.com', ''); const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); }); } /** * @desc 发起商户转账 * @param outBatchNo {string} 商户批次单号 => 可调用"createOrderNo()"方法生成 * @param batchName {string} 批次名称 * @param batchRemark {string} 批次备注 * @param transferDetailList {ITransferDetailItem} 转账明细列表 * @param options {any} 选项 => 可覆盖已有参数 * @return object * @author chenbz * @date 2022-09-29 */ transferBatches(outBatchNo, batchName, batchRemark, transferDetailList, options) { const { appId, mchId, payNotifyUrl } = this.config; const url = '/v3/transfer/batches'; const totalNum = transferDetailList.length; let totalAmount = 0; transferDetailList.forEach((transferDetail) => { totalAmount += Number(transferDetail.transfer_amount); }); const data = { appid: appId, mchid: mchId, out_batch_no: outBatchNo, batch_name: batchName, batch_remark: batchRemark, total_amount: totalAmount, total_num: totalNum, notify_url: payNotifyUrl, transfer_detail_list: transferDetailList }; const postData = Object.assign({}, data, options); const headers = { Authorization: this.getAuthorization('POST', url, postData), }; return utils_1.request.post(url, data, headers); } /** * @desc 获取平台证书列表 * @return object * @author chenbz * @date 2022-09-30 */ getCertificates() { const url = '/v3/certificates'; const headers = { Authorization: this.getAuthorization('GET', url), }; return utils_1.request.get(url, {}, headers); } } exports.WxPayV3 = WxPayV3;