diff --git a/README.md b/README.md index 25b3630..bf94ddf 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,10 @@ export class PaymentNotifyController { - [x] **0.1.0** 微信-普通商户版-APP支付 - [x] **0.2.0** 微信-普通商户版-JSAPI支付、微信-普通商户版-Native支付、微信-普通商户版-H5支付、微信-普通商户版-小程序支付 - [x] **0.3.0** 微信-普通商户版-付款码支付 -- [ ] **0.4.0** 支付宝-APP支付 -- [ ] **0.5.0** 支付宝-当面付 -- [ ] **0.6.0** 支付宝-手机网站支付 -- [ ] **0.7.0** 支付宝-电脑网站支付 +- [ ] **0.4.0** 微信-普通商户版-现金红包 +- [ ] **0.5.0** 微信-普通商户版-企业付款 +- [ ] **0.6.0** 支付宝-APP支付 +- [ ] **0.7.0** 支付宝-当面付 +- [ ] **0.8.0** 支付宝-手机网站支付 +- [ ] **0.9.0** 支付宝-电脑网站支付 - [ ] **1.0.0** 完善使用说明、发布正式版v1.0.0 \ No newline at end of file diff --git a/package.json b/package.json index 7fcc14e..71c705f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nt-addon-pay", - "version": "0.3.1", + "version": "0.3.2", "description": "The pay addon for notadd application", "scripts": { "start": "ts-node -r tsconfig-paths/register starter/main.ts", diff --git a/src/common/interfaces/addon.config.interface.ts b/src/common/interfaces/addon.config.interface.ts index 685aea4..874b2ea 100644 --- a/src/common/interfaces/addon.config.interface.ts +++ b/src/common/interfaces/addon.config.interface.ts @@ -1,5 +1,5 @@ /** 微信支付初始化配置 */ -interface PayAddonWeChatConfig { +export interface WeChatPayConfig { /** 公众账号APPID或应用APPID */ appid: string; /** 微信支付商户号 */ @@ -15,14 +15,14 @@ interface PayAddonWeChatConfig { } /** 支付宝支付初始化配置 */ -interface PayAddonAliConfig { +export interface AliPayConfig { } /** 支付插件初始化配置 */ export interface PayAddonConfig { /** 微信支付初始化配置 */ - wechatConfig?: PayAddonWeChatConfig; + wechatConfig?: WeChatPayConfig; /** 支付宝支付初始化配置 */ - aliConfig?: PayAddonAliConfig; + aliConfig?: AliPayConfig; } \ No newline at end of file diff --git a/src/modules/wechat/constants/wechat.constant.ts b/src/modules/wechat/constants/wechat.constant.ts index ecb4dfa..e368804 100644 --- a/src/modules/wechat/constants/wechat.constant.ts +++ b/src/modules/wechat/constants/wechat.constant.ts @@ -1 +1,2 @@ -export const WeChatCertificateAgentProvider = 'WeChatCertificateAgentProvider'; \ No newline at end of file +export const WeChatPayCertificateAgentProvider = 'WeChatPayCertificateAgentProvider'; +export const WeChatPayConfigProvider = 'WeChatPayConfigProvider'; \ No newline at end of file diff --git a/src/modules/wechat/services/base.service.ts b/src/modules/wechat/services/base.service.ts index 5d5aef6..8e3546a 100644 --- a/src/modules/wechat/services/base.service.ts +++ b/src/modules/wechat/services/base.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import * as https from 'https'; -import { PayAddonConfig, PayAddonConfigProvider } from '../../../common'; -import { WeChatCertificateAgentProvider } from '../constants/wechat.constant'; +import { WeChatPayConfig } from '../../../common'; +import { WeChatPayCertificateAgentProvider, WeChatPayConfigProvider } from '../constants/wechat.constant'; import { WeChatBaseCloseOrderReqParam, WeChatBaseCloseOrderRes, @@ -23,7 +23,7 @@ import { WeChatRequestUtil } from '../utils/request.util'; @Injectable() export class WeChatPayBaseService { /** API 接口域名 */ - protected apiBase = 'https://api.mch.weixin.qq.com' + (this.payAddonConfig.wechatConfig.sandbox ? '/sandboxnew' : ''); + protected apiBase = 'https://api.mch.weixin.qq.com' + (this.config.sandbox ? '/sandboxnew' : ''); /** 统一下单接口地址 */ protected readonly unifiedOrderUrl = `${this.apiBase}/pay/unifiedorder`; /** 查询订单接口地址 */ @@ -40,8 +40,8 @@ export class WeChatPayBaseService { protected readonly downloadFundFlowUrl = `${this.apiBase}/pay/downloadfundflow`; constructor( - @Inject(PayAddonConfigProvider) protected readonly payAddonConfig: PayAddonConfig, - @Inject(WeChatCertificateAgentProvider) protected readonly certificateAgent: https.Agent, + @Inject(WeChatPayConfigProvider) private readonly config: WeChatPayConfig, + @Inject(WeChatPayCertificateAgentProvider) protected readonly certificateAgent: https.Agent, @Inject(WeChatRequestUtil) protected readonly requestUtil: WeChatRequestUtil ) { } @@ -93,7 +93,7 @@ export class WeChatPayBaseService { * 检查是否覆盖默认的签名类型 */ protected checkOverrideDefaultSignType(params: any) { - const signType = this.payAddonConfig.wechatConfig.sign_type; + const signType = this.config.sign_type; if (signType) { (params as any).sign_type = signType; } diff --git a/src/modules/wechat/utils/notify-parser.util.ts b/src/modules/wechat/utils/notify-parser.util.ts index fbd652d..2e55add 100644 --- a/src/modules/wechat/utils/notify-parser.util.ts +++ b/src/modules/wechat/utils/notify-parser.util.ts @@ -2,8 +2,9 @@ import { Inject, Injectable } from '@nestjs/common'; import * as crypto from 'crypto'; import { IncomingMessage } from 'http'; -import { PayAddonConfig, PayAddonConfigProvider } from '../../../common'; +import { WeChatPayConfig } from '../../../common'; import { XmlUtil } from '../../../shared/utils/xml.util'; +import { WeChatPayConfigProvider } from '../constants/wechat.constant'; import { WeChatPayNotifyRes, WeChatRefundNotifyRes } from '../interfaces/notify.interface'; import { WeChatSignUtil } from './sign.util'; @@ -13,7 +14,7 @@ import { WeChatSignUtil } from './sign.util'; @Injectable() export class WeChatNotifyParserUtil { constructor( - @Inject(PayAddonConfigProvider) protected readonly payAddonConfig: PayAddonConfig, + @Inject(WeChatPayConfigProvider) protected readonly config: WeChatPayConfig, @Inject(XmlUtil) private readonly xmlUtil: XmlUtil, @Inject(WeChatSignUtil) private readonly signUtil: WeChatSignUtil ) { } @@ -29,8 +30,8 @@ export class WeChatNotifyParserUtil { return undefined; } - const secretKey = this.payAddonConfig.wechatConfig.secretKey; - const signType = this.payAddonConfig.wechatConfig.sign_type; + const secretKey = this.config.secretKey; + const signType = this.config.sign_type; const result = await this.xmlUtil.parseObjFromXml(data); if (result.return_code !== 'SUCCESS') { @@ -55,7 +56,7 @@ export class WeChatNotifyParserUtil { } const result = await this.xmlUtil.parseObjFromXml(data); - const secretKey = this.payAddonConfig.wechatConfig.secretKey; + const secretKey = this.config.secretKey; const cryptedBase64Str = Buffer.from(result.req_info).toString('base64'); const secretKeyMD5 = crypto.createHash('md5').update(secretKey).digest('hex').toLocaleLowerCase(); diff --git a/src/modules/wechat/utils/request.util.ts b/src/modules/wechat/utils/request.util.ts index 4110b3b..aca9ef6 100644 --- a/src/modules/wechat/utils/request.util.ts +++ b/src/modules/wechat/utils/request.util.ts @@ -1,9 +1,10 @@ import { HttpService, Inject, Injectable } from '@nestjs/common'; import * as axios from 'axios'; -import { PayAddonConfig, PayAddonConfigProvider } from '../../../common'; +import { WeChatPayConfig } from '../../../common'; import { RandomUtil } from '../../../shared/utils/random.util'; import { XmlUtil } from '../../../shared/utils/xml.util'; +import { WeChatPayConfigProvider } from '../constants/wechat.constant'; import { WeChatSignUtil } from './sign.util'; /** @@ -13,7 +14,7 @@ import { WeChatSignUtil } from './sign.util'; export class WeChatRequestUtil { constructor( @Inject(HttpService) private readonly httpService: HttpService, - @Inject(PayAddonConfigProvider) private readonly payAddonConfig: PayAddonConfig, + @Inject(WeChatPayConfigProvider) private readonly config: WeChatPayConfig, @Inject(XmlUtil) private readonly xmlUtil: XmlUtil, @Inject(WeChatSignUtil) private readonly signUtil: WeChatSignUtil, @Inject(RandomUtil) private readonly randomUtil: RandomUtil @@ -27,7 +28,7 @@ export class WeChatRequestUtil { * @param config AxiosRequestConfig */ async post(url: string, params: any, config?: axios.AxiosRequestConfig): Promise { - const wechatConfig = this.payAddonConfig.wechatConfig; + const wechatConfig = this.config; params.appid = wechatConfig.appid; params.mch_id = wechatConfig.mch_id; params.nonce_str = this.randomUtil.genRandomStr(); diff --git a/src/modules/wechat/wechat.pay.module.ts b/src/modules/wechat/wechat.pay.module.ts index 32db8ce..2c92fd4 100644 --- a/src/modules/wechat/wechat.pay.module.ts +++ b/src/modules/wechat/wechat.pay.module.ts @@ -1,8 +1,11 @@ -import { Inject, Module, OnModuleInit } from '@nestjs/common'; +import { DynamicModule, Inject, Module, OnModuleInit } from '@nestjs/common'; import * as fs from 'fs'; +import * as https from 'https'; import * as path from 'path'; -import { PayAddonConfig, PayAddonConfigProvider } from '../../common'; +import { WeChatPayConfig } from '../../common'; +import { SharedModule } from '../../shared/shared.module'; +import { WeChatPayCertificateAgentProvider, WeChatPayConfigProvider } from './constants/wechat.constant'; import { WeChatSandboxResponse } from './interfaces/sandbox.interface'; import { WeChatAppPayService } from './services/app.pay.service'; import { WeChatAppletPayService } from './services/applet.pay.service'; @@ -15,30 +18,7 @@ import { WeChatNotifyParserUtil } from './utils/notify-parser.util'; import { WeChatRequestUtil } from './utils/request.util'; import { WeChatSignUtil } from './utils/sign.util'; -@Module({ - imports: [], - providers: [ - WeChatPayBaseService, - WeChatAppPayService, - WeChatAppletPayService, - WeChatJSAPIPayService, - WeChatMicroPayService, - WeChatNativePayService, - WeChatWapPayService, - WeChatSignUtil, - WeChatRequestUtil, - WeChatNotifyParserUtil - ], - exports: [ - WeChatAppPayService, - WeChatAppletPayService, - WeChatJSAPIPayService, - WeChatMicroPayService, - WeChatNativePayService, - WeChatWapPayService, - WeChatNotifyParserUtil - ] -}) +@Module({}) export class WeChatPayModule implements OnModuleInit { /** 沙箱环境获取验签秘钥接口地址 */ private readonly sandboxGetSignKeyUrl = 'https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey'; @@ -46,16 +26,47 @@ export class WeChatPayModule implements OnModuleInit { constructor( @Inject(WeChatRequestUtil) private readonly wechatRequestUtil: WeChatRequestUtil, - @Inject(PayAddonConfigProvider) private readonly payAddonConfig: PayAddonConfig + @Inject(WeChatPayConfigProvider) private readonly config: WeChatPayConfig ) { } + static forRoot(config: WeChatPayConfig): DynamicModule { + const certificateAgent = this.createCertificateAgent(config.pfx, config.mch_id); + return { + module: WeChatPayModule, + imports: [SharedModule], + providers: [ + WeChatPayBaseService, + WeChatAppPayService, + WeChatAppletPayService, + WeChatJSAPIPayService, + WeChatMicroPayService, + WeChatNativePayService, + WeChatWapPayService, + WeChatSignUtil, + WeChatRequestUtil, + WeChatNotifyParserUtil, + { provide: WeChatPayCertificateAgentProvider, useValue: certificateAgent }, + { provide: WeChatPayConfigProvider, useValue: config }, + ], + exports: [ + WeChatAppPayService, + WeChatAppletPayService, + WeChatJSAPIPayService, + WeChatMicroPayService, + WeChatNativePayService, + WeChatWapPayService, + WeChatNotifyParserUtil + ] + }; + } + async onModuleInit() { - if (!this.payAddonConfig.wechatConfig.sandbox) return; + if (!this.config.sandbox) return; const sandboxSignKeyExipre = this.checkSandboxSignKeyExpire(); if (!sandboxSignKeyExipre) { const fileContent = fs.readFileSync(path.join(__dirname, this.sandboxSignKeyFileName)).toString(); - this.payAddonConfig.wechatConfig.secretKey = JSON.parse(fileContent).key; + this.config.secretKey = JSON.parse(fileContent).key; } else { const data = await this.getSandboxSignKey(); if (data.return_code === 'FAIL') { @@ -64,7 +75,7 @@ export class WeChatPayModule implements OnModuleInit { } const fileContent = JSON.stringify({ key: data.sandbox_signkey, createdAt: +new Date() }); fs.writeFileSync(path.join(__dirname, this.sandboxSignKeyFileName), fileContent); - this.payAddonConfig.wechatConfig.secretKey = data.sandbox_signkey; + this.config.secretKey = data.sandbox_signkey; } } @@ -84,4 +95,17 @@ export class WeChatPayModule implements OnModuleInit { const fileContent = fs.readFileSync(path.join(__dirname, this.sandboxSignKeyFileName)).toString(); return (+new Date()) - JSON.parse(fileContent).createdAt > (3600 * 24 * 1000); } + + /** + * 创建请求证书代理 + * + * 此 agent 仅用于微信支付的申请退款、撤销订单和下载资金账单接口 + */ + private static createCertificateAgent(pfx: Buffer, mchId: string): https.Agent { + if (!pfx) throw Error('读取商户证书失败'); + return new https.Agent({ + pfx, + passphrase: mchId + }); + } } \ No newline at end of file diff --git a/src/pay.addon.ts b/src/pay.addon.ts index 9dbbd30..23bfa91 100644 --- a/src/pay.addon.ts +++ b/src/pay.addon.ts @@ -1,27 +1,24 @@ -import { DynamicModule, Inject, Module, OnModuleInit } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; -import { PayAddonConfig, PayAddonConfigProvider } from './common'; +import { PayAddonConfig } from './common'; import { AliPayModule } from './modules/ali/ali.pay.module'; import { WeChatPayModule } from './modules/wechat/wechat.pay.module'; import { SharedModule } from './shared/shared.module'; @Module({}) -export class PayAddon implements OnModuleInit { - constructor( - @Inject(PayAddonConfigProvider) private readonly payAddonConfig: PayAddonConfig - ) { } - +export class PayAddon { static forRoot(config: PayAddonConfig): DynamicModule { + this.checkConfig(config); return { module: PayAddon, - imports: [SharedModule.forFeature(config), WeChatPayModule, AliPayModule], + imports: [SharedModule, WeChatPayModule.forRoot(config.wechatConfig), AliPayModule], exports: [WeChatPayModule, AliPayModule] }; } - async onModuleInit() { - const wechatConfig = this.payAddonConfig.wechatConfig; - const aliConfig = this.payAddonConfig.aliConfig; + private static checkConfig(config: PayAddonConfig) { + const wechatConfig = config.wechatConfig; + const aliConfig = config.aliConfig; if (!wechatConfig && !aliConfig) { throw Error('请至少指定一种支付方式的配置'); } diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts index 1a60654..8f8256e 100644 --- a/src/shared/shared.module.ts +++ b/src/shared/shared.module.ts @@ -1,39 +1,11 @@ -import { DynamicModule, Global, HttpModule, Module } from '@nestjs/common'; -import * as https from 'https'; +import { HttpModule, Module } from '@nestjs/common'; -import { PayAddonConfig, PayAddonConfigProvider } from '../common'; -import { WeChatCertificateAgentProvider } from '../modules/wechat/constants/wechat.constant'; import { RandomUtil } from './utils/random.util'; import { XmlUtil } from './utils/xml.util'; -@Global() -@Module({}) -export class SharedModule { - static forFeature(config: PayAddonConfig): DynamicModule { - const pfx = this.createCertificateAgent(config.wechatConfig.pfx, config.wechatConfig.mch_id); - return { - module: SharedModule, - imports: [HttpModule], - providers: [ - XmlUtil, - RandomUtil, - { provide: PayAddonConfigProvider, useValue: config }, - { provide: WeChatCertificateAgentProvider, useValue: pfx } - ], - exports: [HttpModule, RandomUtil, XmlUtil, PayAddonConfigProvider, WeChatCertificateAgentProvider] - }; - } - - /** - * 创建请求证书代理 - * - * 此 agent 仅用于微信支付的申请退款、撤销订单和下载资金账单接口 - */ - private static createCertificateAgent(pfx: Buffer, mchId: string): https.Agent { - if (!pfx) throw Error('读取商户证书失败'); - return new https.Agent({ - pfx, - passphrase: mchId - }); - } -} \ No newline at end of file +@Module({ + imports: [HttpModule], + providers: [XmlUtil, RandomUtil], + exports: [HttpModule, RandomUtil, XmlUtil] +}) +export class SharedModule { } \ No newline at end of file