From 63167b22a8f362a6a455589c9c8a1e2032c63c4f Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Fri, 7 Dec 2018 19:41:52 +0800 Subject: [PATCH 1/7] chore(wechat): remove redundant logic --- src/modules/wechat/services/base.service.ts | 15 +-------------- src/modules/wechat/services/micro.pay.service.ts | 1 - src/modules/wechat/utils/request.util.ts | 13 +++++++++---- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/modules/wechat/services/base.service.ts b/src/modules/wechat/services/base.service.ts index 8e3546a..432755d 100644 --- a/src/modules/wechat/services/base.service.ts +++ b/src/modules/wechat/services/base.service.ts @@ -40,7 +40,7 @@ export class WeChatPayBaseService { protected readonly downloadFundFlowUrl = `${this.apiBase}/pay/downloadfundflow`; constructor( - @Inject(WeChatPayConfigProvider) private readonly config: WeChatPayConfig, + @Inject(WeChatPayConfigProvider) protected readonly config: WeChatPayConfig, @Inject(WeChatPayCertificateAgentProvider) protected readonly certificateAgent: https.Agent, @Inject(WeChatRequestUtil) protected readonly requestUtil: WeChatRequestUtil ) { } @@ -52,7 +52,6 @@ export class WeChatPayBaseService { */ public async queryOrder(params: WeChatBaseQueryOrderReqParam): Promise { if (!params.out_trade_no && !params.transaction_id) throw new Error('参数有误,out_trade_no 和 transaction_id 二选一'); - this.checkOverrideDefaultSignType(params); return await this.requestUtil.post(this.queryOrderUrl, params); } @@ -62,7 +61,6 @@ export class WeChatPayBaseService { * @param params 关闭订单请求参数 */ public async closeOrder(params: WeChatBaseCloseOrderReqParam): Promise { - this.checkOverrideDefaultSignType(params); return await this.requestUtil.post(this.closeOrderUrl, params); } @@ -73,7 +71,6 @@ export class WeChatPayBaseService { */ public async refund(params: WeChatBaseRefundReqParam): Promise { if (!params.out_trade_no && !params.transaction_id) throw new Error('参数有误,out_trade_no 和 transaction_id 二选一'); - this.checkOverrideDefaultSignType(params); return await this.requestUtil.post(this.refundUrl, params, { httpsAgent: this.certificateAgent }); } @@ -88,14 +85,4 @@ export class WeChatPayBaseService { } return await this.requestUtil.post(this.refundQueryUrl, params); } - - /** - * 检查是否覆盖默认的签名类型 - */ - protected checkOverrideDefaultSignType(params: any) { - const signType = this.config.sign_type; - if (signType) { - (params as any).sign_type = signType; - } - } } \ No newline at end of file diff --git a/src/modules/wechat/services/micro.pay.service.ts b/src/modules/wechat/services/micro.pay.service.ts index 68d8f4f..2011bf6 100644 --- a/src/modules/wechat/services/micro.pay.service.ts +++ b/src/modules/wechat/services/micro.pay.service.ts @@ -28,7 +28,6 @@ export class WeChatMicroPayService extends WeChatPayBaseService { */ async closeOrder(params: WeChatMicroPayReverseOrderReqParam): Promise { const url = `${this.apiBase}/pay/reverse`; - this.checkOverrideDefaultSignType(params); return await this.requestUtil.post(url, params, { httpsAgent: this.certificateAgent }); } } \ No newline at end of file diff --git a/src/modules/wechat/utils/request.util.ts b/src/modules/wechat/utils/request.util.ts index aca9ef6..c80f8d6 100644 --- a/src/modules/wechat/utils/request.util.ts +++ b/src/modules/wechat/utils/request.util.ts @@ -28,11 +28,16 @@ export class WeChatRequestUtil { * @param config AxiosRequestConfig */ async post(url: string, params: any, config?: axios.AxiosRequestConfig): Promise { - const wechatConfig = this.config; - params.appid = wechatConfig.appid; - params.mch_id = wechatConfig.mch_id; + // 现金红包特例 wxappid + if (Object.keys(params).includes('wxappid')) { + params.wxappid = this.config.appid; + } else { + params.appid = this.config.appid; + } + params.mch_id = this.config.mch_id; params.nonce_str = this.randomUtil.genRandomStr(); - params.sign = this.signUtil.sign(params, wechatConfig.secretKey, wechatConfig.sign_type); + params.sign_type = this.config.sign_type ? this.config.sign_type : 'MD5'; + params.sign = this.signUtil.sign(params, this.config.secretKey, this.config.sign_type); try { const { data } = await this.httpService.post(url, this.xmlUtil.convertObjToXml(params), config).toPromise(); if ((data as any).return_code === 'SUCCESS') { From de70ce5b78715b6bf82cca9bce74bb8fb4d4172d Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Fri, 7 Dec 2018 19:42:45 +0800 Subject: [PATCH 2/7] feat(wechat): add send redpack api --- .../wechat/interfaces/redpack.interface.ts | 73 +++++++++++++++++++ .../wechat/services/redpack.service.ts | 24 ++++++ 2 files changed, 97 insertions(+) create mode 100644 src/modules/wechat/interfaces/redpack.interface.ts create mode 100644 src/modules/wechat/services/redpack.service.ts diff --git a/src/modules/wechat/interfaces/redpack.interface.ts b/src/modules/wechat/interfaces/redpack.interface.ts new file mode 100644 index 0000000..bb8d54b --- /dev/null +++ b/src/modules/wechat/interfaces/redpack.interface.ts @@ -0,0 +1,73 @@ +/** 微信支付发放普通红包接口请求参数 */ +export interface WeChatRedpackReqParam { + /** 商户订单号 */ + mch_billno: string; + /** 商户名称 */ + send_name: string; + /** 用户openid */ + re_openid: string; + /** 付款金额 */ + total_amount: number; + /** 红包发放总人数 */ + total_num: number; + /** 红包祝福语 */ + wishing: string; + /** ip地址 */ + client_ip: string; + /** 活动名称 */ + act_name: string; + /** 备注 */ + remark: string; + /** + * 场景id + * + * 发放红包使用场景,红包金额大于200或者小于1元时必传 + * + * PRODUCT_1:商品促销 + * + * PRODUCT_2:抽奖 + * + * PRODUCT_3:虚拟物品兑奖 + * + * PRODUCT_4:企业内部福利 + * + * PRODUCT_5:渠道分润 + * + * PRODUCT_6:保险回馈 + * + * PRODUCT_7:彩票派奖 + * + * PRODUCT_8:税务刮奖 + */ + scene_id?: string; + /** 活动信息 */ + rish_info?: string; + /** 资金授权商户号 */ + consume_mch_id?: string; +} + +/** 微信支付发放普通红包接口返回结果 */ +export interface WeChatRedpackRes { + /** 返回状态码 */ + return_code: string; + /** 返回信息 */ + return_msg: string; + /** 业务结果 */ + result_code: string; + /** 错误代码 */ + err_code?: string; + /** 错误代码描述 */ + err_code_des?: string; + /** 商户订单号 */ + mch_billno: string; + /** 商户号 */ + mch_id: string; + /** 公众账号APPID */ + wxappid: string; + /** 用户openid */ + re_openid: string; + /** 付款金额 */ + total_amount: string; + /** 微信单号 */ + send_listid: string; +} \ No newline at end of file diff --git a/src/modules/wechat/services/redpack.service.ts b/src/modules/wechat/services/redpack.service.ts new file mode 100644 index 0000000..186b5e6 --- /dev/null +++ b/src/modules/wechat/services/redpack.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; + +import { WeChatRedpackReqParam, WeChatRedpackRes } from '../interfaces/redpack.interface'; +import { WeChatPayBaseService } from './base.service'; + +/** + * 微信支付-现金红包支付类 + */ +@Injectable() +export class WeChatRedpackService extends WeChatPayBaseService { + private readonly redpackApiBase = 'https://api.mch.weixin.qq.com' + (this.config.sandbox ? '/sandboxnew' : '') + '/mmpaymkttransfers'; + private readonly sendredpackUrl = `${this.redpackApiBase}/sendredpack`; + private readonly sendgroupredpackUrl = `${this.redpackApiBase}/sendgroupredpack`; + private readonly gethbinfoUrl = `${this.redpackApiBase}/gethbinfo`; + + /** + * 发放普通红包 + * + * @param params 发放普通红包请求参数 + */ + async sendRedpack(params: WeChatRedpackReqParam): Promise { + return await this.requestUtil.post(this.sendredpackUrl, params, { httpsAgent: this.certificateAgent }); + } +} \ No newline at end of file From f8082770df21fbd89d9df30a0e6b7f67d5f28e05 Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Mon, 10 Dec 2018 11:13:44 +0800 Subject: [PATCH 3/7] feat(wechat): add send group redpack api and query redpack record api --- .../wechat/interfaces/redpack.interface.ts | 98 ++++++++++++++++++- .../wechat/services/redpack.service.ts | 27 ++++- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/modules/wechat/interfaces/redpack.interface.ts b/src/modules/wechat/interfaces/redpack.interface.ts index bb8d54b..db9cb16 100644 --- a/src/modules/wechat/interfaces/redpack.interface.ts +++ b/src/modules/wechat/interfaces/redpack.interface.ts @@ -1,4 +1,4 @@ -/** 微信支付发放普通红包接口请求参数 */ +/** 微信支付发放红包接口请求参数 */ export interface WeChatRedpackReqParam { /** 商户订单号 */ mch_billno: string; @@ -6,7 +6,7 @@ export interface WeChatRedpackReqParam { send_name: string; /** 用户openid */ re_openid: string; - /** 付款金额 */ + /** 总金额 */ total_amount: number; /** 红包发放总人数 */ total_num: number; @@ -66,8 +66,98 @@ export interface WeChatRedpackRes { wxappid: string; /** 用户openid */ re_openid: string; - /** 付款金额 */ - total_amount: string; + /** 总金额 */ + total_amount: number; /** 微信单号 */ send_listid: string; +} + +/** 微信支付查询红包记录接口请求参数 */ +export interface WeChatQueryRedpackRecordReqParam { + /** 商户订单号 */ + mch_billno: string; +} + +/** 微信支付查询红包记录接口返回结果 */ +export interface WeChatQueryRedpackRecordRes { + /** 返回状态码 */ + return_code: string; + /** 返回信息 */ + return_msg: string; + /** 业务结果 */ + result_code: string; + /** 错误代码 */ + err_code?: string; + /** 错误代码描述 */ + err_code_des?: string; + /** 商户订单号 */ + mch_billno: string; + /** 商户号 */ + mch_id: string; + /** 红包单号 */ + detail_id: string; + /** + * 红包状态 + * + * SENDING:发放中 + * + * SENT:已发放待领取 + * + * FAILED:发放失败 + * + * RECEIVED:已领取 + * + * RFUND_ING:退款中 + * + * REFUND:已退款 + */ + status: string; + /** + * 发放类型 + * + * API:通过API接口发放 + * + * UPLOAD:通过上传文件方式发放 + * + * ACTIVITY:通过活动方式发放 + */ + send_type: string; + /** + * 红包类型 + * + * GROUP:裂变红包 + * + * NORMAL:普通红包 + */ + hb_type: string; + /** 红包个数 */ + total_num: number; + /** 红包总金额 */ + total_amount: number; + /** 失败原因 */ + reason?: string; + /** 红包发送时间 */ + send_time: string; + /** 红包退款时间 */ + refund_time?: string; + /** 红包退款金额 */ + refund_amount?: number; + /** 祝福语 */ + wishing?: string; + /** 活动描述 */ + remark?: string; + /** 活动名称 */ + act_name?: string; + /** 裂变红包领取列表 */ + hblist?: { hbinfo: WeChatGroupRedpackReceiveInfo[] }; +} + +/** 微信支付裂变红包接收记录信息 */ +interface WeChatGroupRedpackReceiveInfo { + /** 领取红包的openid */ + openid: string; + /** 领取金额 */ + amount: number; + /** 接收时间 */ + rcv_time: string; } \ No newline at end of file diff --git a/src/modules/wechat/services/redpack.service.ts b/src/modules/wechat/services/redpack.service.ts index 186b5e6..428a2bf 100644 --- a/src/modules/wechat/services/redpack.service.ts +++ b/src/modules/wechat/services/redpack.service.ts @@ -1,6 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { WeChatRedpackReqParam, WeChatRedpackRes } from '../interfaces/redpack.interface'; +import { + WeChatQueryRedpackRecordReqParam, + WeChatQueryRedpackRecordRes, + WeChatRedpackReqParam, + WeChatRedpackRes +} from '../interfaces/redpack.interface'; import { WeChatPayBaseService } from './base.service'; /** @@ -21,4 +26,24 @@ export class WeChatRedpackService extends WeChatPayBaseService { async sendRedpack(params: WeChatRedpackReqParam): Promise { return await this.requestUtil.post(this.sendredpackUrl, params, { httpsAgent: this.certificateAgent }); } + + /** + * 发放裂变红包 + * + * @param params 发放裂变红包请求参数 + */ + async sendGroupRedpack(params: WeChatRedpackReqParam): Promise { + (params as any).amt_type = 'ALL_RAND'; + return await this.requestUtil.post(this.sendgroupredpackUrl, params, { httpsAgent: this.certificateAgent }); + } + + /** + * 查询红包记录 + * + * @param params 查询红包记录请求参数 + */ + async queryRedpackRecord(params: WeChatQueryRedpackRecordReqParam): Promise { + (params as any).bill_type = 'MCHT'; + return await this.requestUtil.post(this.gethbinfoUrl, params, { httpsAgent: this.certificateAgent }); + } } \ No newline at end of file From 64339ad2d4a9e97eb6aa56d000dad24f6d3db964 Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Mon, 10 Dec 2018 11:44:37 +0800 Subject: [PATCH 4/7] fix(wechat): fix request params bug --- src/modules/wechat/services/redpack.service.ts | 2 ++ src/modules/wechat/utils/request.util.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/wechat/services/redpack.service.ts b/src/modules/wechat/services/redpack.service.ts index 428a2bf..f53fddd 100644 --- a/src/modules/wechat/services/redpack.service.ts +++ b/src/modules/wechat/services/redpack.service.ts @@ -24,6 +24,7 @@ export class WeChatRedpackService extends WeChatPayBaseService { * @param params 发放普通红包请求参数 */ async sendRedpack(params: WeChatRedpackReqParam): Promise { + (params as any).wxappid = ''; return await this.requestUtil.post(this.sendredpackUrl, params, { httpsAgent: this.certificateAgent }); } @@ -33,6 +34,7 @@ export class WeChatRedpackService extends WeChatPayBaseService { * @param params 发放裂变红包请求参数 */ async sendGroupRedpack(params: WeChatRedpackReqParam): Promise { + (params as any).wxappid = ''; (params as any).amt_type = 'ALL_RAND'; return await this.requestUtil.post(this.sendgroupredpackUrl, params, { httpsAgent: this.certificateAgent }); } diff --git a/src/modules/wechat/utils/request.util.ts b/src/modules/wechat/utils/request.util.ts index c80f8d6..214c6a6 100644 --- a/src/modules/wechat/utils/request.util.ts +++ b/src/modules/wechat/utils/request.util.ts @@ -41,7 +41,7 @@ export class WeChatRequestUtil { try { const { data } = await this.httpService.post(url, this.xmlUtil.convertObjToXml(params), config).toPromise(); if ((data as any).return_code === 'SUCCESS') { - if (params.sign !== (data as any).sign) throw new Error('微信支付接口返回签名有误'); + if (params.sign && params.sign !== (data as any).sign) throw new Error('微信支付接口返回签名有误'); } return this.xmlUtil.parseObjFromXml(data); } catch (error) { From 49a6b6618806344de166af149881bfc9c39ffb46 Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Mon, 10 Dec 2018 11:44:57 +0800 Subject: [PATCH 5/7] chore(wechat): add redpack exports --- src/modules/wechat/index.ts | 1 + src/modules/wechat/wechat.pay.module.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/modules/wechat/index.ts b/src/modules/wechat/index.ts index 5a8514d..4bd8134 100644 --- a/src/modules/wechat/index.ts +++ b/src/modules/wechat/index.ts @@ -4,5 +4,6 @@ export * from './services/jsapi.pay.service'; export * from './services/micro.pay.service'; export * from './services/native.pay.service'; export * from './services/wap.pay.service'; +export * from './services/redpack.service'; export * from './enums/trade-type.enum'; export * from './utils/notify-parser.util'; \ No newline at end of file diff --git a/src/modules/wechat/wechat.pay.module.ts b/src/modules/wechat/wechat.pay.module.ts index 2c92fd4..ce01c47 100644 --- a/src/modules/wechat/wechat.pay.module.ts +++ b/src/modules/wechat/wechat.pay.module.ts @@ -13,6 +13,7 @@ import { WeChatPayBaseService } from './services/base.service'; import { WeChatJSAPIPayService } from './services/jsapi.pay.service'; import { WeChatMicroPayService } from './services/micro.pay.service'; import { WeChatNativePayService } from './services/native.pay.service'; +import { WeChatRedpackService } from './services/redpack.service'; import { WeChatWapPayService } from './services/wap.pay.service'; import { WeChatNotifyParserUtil } from './utils/notify-parser.util'; import { WeChatRequestUtil } from './utils/request.util'; @@ -42,6 +43,7 @@ export class WeChatPayModule implements OnModuleInit { WeChatMicroPayService, WeChatNativePayService, WeChatWapPayService, + WeChatRedpackService, WeChatSignUtil, WeChatRequestUtil, WeChatNotifyParserUtil, @@ -55,6 +57,7 @@ export class WeChatPayModule implements OnModuleInit { WeChatMicroPayService, WeChatNativePayService, WeChatWapPayService, + WeChatRedpackService, WeChatNotifyParserUtil ] }; From 11f0875573213a442f8d0c44cd320f315629dc81 Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Mon, 10 Dec 2018 11:45:37 +0800 Subject: [PATCH 6/7] doc(readme): add more document for wechat --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf94ddf..064fb31 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,9 @@ import { PayAddon } from '@notadd/addon-pay'; appid: 'appid', // 公众号appi/应用appid/小程序appid mch_id: 'mch_id', // 商户号 secretKey: 'secretKey', // 商户交易秘钥 + sign_type: 'MD5', // 微信支付签名类型('MD5' | 'HMAC-SHA256'),默认MD5,配置后,所有接口参数均会使用这个签名类型 pfx: fs.readFileSync('path_to_p12_file'), // p12文件 - sandbox: true // 是否启用沙箱环境,默认不启用 + sandbox: true // 是否启用沙箱环境,默认不启用,用于商户支付验收测试 } }) ] @@ -32,9 +33,27 @@ export class ApplicationModule {} ### 微信支付 +接口使用前的必要声明: + +1. 所有的接口请求参数和接口返回结果中的属性名全部使用的是 **`snake_case`**,其中属性名结尾带 **`?`** 的代表这个属性是可选的(非必填)。 +2. 所有与金额相关的数据全部是 **`number`** 类型且单位为 **`分`**,需自行转换。 +3. 所有接口参数中的 `mch_id`、`appid/wxappid`、`nonce_str`、`sign_type`、`sign` 数据均由插件自动填入,无需手动传入。 +4. 所有请求的返回结果若有 `sign`,插件会自动验签。 +5. 支付通知结果插件会自动验签,退款通知结果插件会自动解密 `req_info` 数据。 + #### 使用 WeChat`XXX`PayService 调用 API -WeChat`XXX`PayService 类包含当前支付方式的支付、订单、退款相关 API,调用方式如下(扫码支付): +WeChat`XXX`PayService 类包含当前支付方式的支付、订单、退款相关 API,各支付类说明: + +- WeChatAppPayService —— APP支付 +- WeChatAppletPayService —— 小程序支付 +- WeChatJSAPIPayService —— JSAPI支付(用户通过微信扫码、关注公众号等方式进入商家H5页面,并在微信内调用JSSDK完成支付) +- WeChatMicroPayService —— 付款码支付(用户打开微信钱包-付款码的界面,商户扫码后提交完成支付) +- WeChatNativePayService —— Native支付(扫码支付) +- WeChatWapPayService —— H5支付(用户在微信以外的手机浏览器请求微信支付的场景唤起微信支付) +- WeChatRedpackService —— 现金红包 + +例子:Native支付(扫码支付)调用方式如下 ```typescript import { Injectable, Inject } from '@nestjs/common'; From 09807e4de151e2a88c4a614be1b18c2697cfaeab Mon Sep 17 00:00:00 2001 From: dzzzzzy Date: Mon, 10 Dec 2018 11:47:15 +0800 Subject: [PATCH 7/7] chore(release): publish v0.4.0 --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 064fb31..91724a7 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ export class PaymentNotifyController { - [x] **0.1.0** 微信-普通商户版-APP支付 - [x] **0.2.0** 微信-普通商户版-JSAPI支付、微信-普通商户版-Native支付、微信-普通商户版-H5支付、微信-普通商户版-小程序支付 - [x] **0.3.0** 微信-普通商户版-付款码支付 -- [ ] **0.4.0** 微信-普通商户版-现金红包 +- [x] **0.4.0** 微信-普通商户版-现金红包 - [ ] **0.5.0** 微信-普通商户版-企业付款 - [ ] **0.6.0** 支付宝-APP支付 - [ ] **0.7.0** 支付宝-当面付 diff --git a/package.json b/package.json index 71c705f..2ac3c2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nt-addon-pay", - "version": "0.3.2", + "version": "0.4.0", "description": "The pay addon for notadd application", "scripts": { "start": "ts-node -r tsconfig-paths/register starter/main.ts",