├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── define.ts
├── docs
├── batches_transfer.md
├── close.md
├── combine.md
├── downloadbill.md
├── fundflowbill.md
├── profitsharing.md
├── query.md
├── tradebill.md
├── transactions_app.md
├── transactions_h5.md
├── transactions_jsapi.md
├── transactions_native.md
└── verifySign.md
├── index.ts
├── lib
├── base.ts
├── combine_interface.ts
├── interface-v2.ts
├── interface.ts
├── pay-request.interface.ts
└── pay-request.ts
├── package.json
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'prettier',
12 | 'prettier/@typescript-eslint',
13 | ],
14 | root: true,
15 | env: {
16 | node: true,
17 | jest: true,
18 | },
19 | rules: {
20 | '@typescript-eslint/no-namespace': 'off',
21 | '@typescript-eslint/interface-name-prefix': 'off',
22 | '@typescript-eslint/explicit-function-return-type': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | '@typescript-eslint/explicit-module-boundary-types': 'off',
25 | '@typescript-eslint/no-empty-function': 'off',
26 | '@typescript-eslint/no-unused-vars': 'off',
27 | '@typescript-eslint/no-var-requires': 'off',
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | test
4 | .vscode
5 | yarn.lock
6 | test.ts
7 | test/*
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | *.json
3 | *.md
4 | yarn.lock
5 | **/*.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 150
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 klover
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | [](https://www.oscs1024.com/project/klover2/wechatpay-node-v3-ts?ref=badge_small)
8 | [](https://www.npmjs.com/package/wechatpay-node-v3)
9 | [](https://www.npmjs.com/package/wechatpay-node-v3)
10 | [](https://github.com/klover2/wechatpay-node-v3-ts/graphs/contributors)
11 | [](https://opensource.org/licenses/MIT)
12 |
13 | # 微信支付v3 支持在ts和js中使用
14 |
15 | ## 欢迎大家加入一起完善这个api
16 | ## 前言
17 | 微信官方在2020-12-25正式开放了[v3](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)版本的接口,相比较旧版本[v2](https://pay.weixin.qq.com/wiki/doc/api/index.html)有了不少改变,例如:
18 | * 遵循统一的Restful的设计风格
19 | * 使用JSON作为数据交互的格式,不再使用XML
20 | * 使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256
21 | * 不再要求HTTPS客户端证书
22 | * 使用AES-256-GCM,对回调中的关键信息进行加密保护
23 |
24 | 由于官方文档只支持java和php,所以我在这里使用ts简单的封装了一个版本,支持在js或者ts中使用,后续会更加完善这个npm包,谢谢。
25 |
26 | ## 使用
27 | `yarn add wechatpay-node-v3@2.1.8`(也可以用npm,请加上版本号,使用正式版本)
28 |
29 | ```bash
30 | import WxPay from 'wechatpay-node-v3'; // 支持使用require
31 | import fs from 'fs';
32 | import request from 'superagent';
33 |
34 | const pay = new WxPay({
35 | appid: '直连商户申请的公众号或移动应用appid',
36 | mchid: '商户号',
37 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
38 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
39 | });
40 |
41 | # 这里以h5支付为例
42 | try {
43 | # 参数介绍请看h5支付文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml
44 | const params = {
45 | appid: '直连商户申请的公众号或移动应用appid',
46 | mchid: '商户号',
47 | description: '测试',
48 | out_trade_no: '订单号',
49 | notify_url: '回调url',
50 | amount: {
51 | total: 1,
52 | },
53 | scene_info: {
54 | payer_client_ip: 'ip',
55 | h5_info: {
56 | type: 'Wap',
57 | app_name: '网页名称 例如 百度',
58 | app_url: '网页域名 例如 https://www.baidu.com',
59 | },
60 | },
61 | };
62 | const nonce_str = Math.random().toString(36).substr(2, 15), // 随机字符串
63 | timestamp = parseInt(+new Date() / 1000 + '').toString(), // 时间戳 秒
64 | url = '/v3/pay/transactions/h5';
65 |
66 | # 获取签名
67 | const signature = pay.getSignature('POST', nonce_str, timestamp, url, params); # 如果是get 请求 则不需要params 参数拼接在url上 例如 /v3/pay/transactions/id/12177525012014?mchid=1230000109
68 | # 获取头部authorization 参数
69 | const authorization = pay.getAuthorization(nonce_str, timestamp, signature);
70 |
71 | const result = await request
72 | .post('https://api.mch.weixin.qq.com/v3/pay/transactions/h5')
73 | .send(params)
74 | .set({
75 | Accept: 'application/json',
76 | 'Content-Type': 'application/json',
77 | 'User-Agent':
78 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
79 | Authorization: authorization,
80 | });
81 |
82 | console.log('result==========>', result.body);
83 | } catch (error) {
84 | console.log(error);
85 | }
86 | ```
87 | 如果你使用的是nest框架,请结合`nest-wechatpay-node-v3`一起使用。
88 |
89 | ## 使用自定义 http 请求
90 | > import { IPayRequest, Output } from 'wechatpay-node-v3/dist/define';
91 |
92 | 自己实现 `IPayRequest` 接口,使用如下方法注入
93 |
94 | ```ts
95 | pay.createHttp(...);
96 | ```
97 |
98 |
99 | ## WxPay 介绍
100 | `import WxPay from 'wechatpay-node-v3';` 或者 `const WxPay = require('wechatpay-node-v3')`
101 |
102 | 参数介绍
103 | |参数名称 |参数介绍 |是否必须|
104 | |--|--|--|
105 | | appid| 直连商户申请的公众号或移动应用appid|是|
106 | |mchid|商户号|是
107 | |serial_no|证书序列号|否|
108 | |publicKey|公钥|是|
109 | |privateKey|密钥|是|
110 | |authType|认证类型,目前为WECHATPAY2-SHA256-RSA2048|否|
111 | |userAgent|自定义User-Agent|否|
112 | |key|APIv3密钥|否 有验证回调必须|
113 |
114 | ## 注意
115 | 1. serial_no是证书序列号 请在命令窗口使用 `openssl x509 -in apiclient_cert.pem -noout -serial` 获取
116 | 2. 头部参数需要添加 User-Agent 参数
117 | 3. 需要在商户平台设置APIv3密钥才会有回调,详情参看文档指引http://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html
118 |
119 | ## 使用介绍
120 | 以下函数是我针对微信相关接口进行进一步封装,可以直接使用。
121 | | api名称 | 函数名 |
122 | |--|--|
123 | | [h5支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml) |[transactions_h5](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md) |
124 | | [native支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml) |[transactions_native](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_native.md) |
125 | | [app支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml) |[transactions_app](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_app.md) |
126 | | [JSAPI支付 或者 小程序支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml) |[transactions_jsapi](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_jsapi.md) |
127 | | [查询订单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml) |[query](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/query.md) |
128 | | [关闭订单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml) |[close](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/close.md) |
129 | | [申请交易账单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_6.shtml) |[tradebill](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/tradebill.md) |
130 | | [申请资金账单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_7.shtml) |[fundflowbill](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/fundflowbill.md) |
131 | | [下载账单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml) |[downloadBill](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/downloadbill.md) |
132 | | [回调解密](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml) |[decipher_gcm](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md) |
133 | |[合单h5支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml)|[combine_transactions_h5](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
134 | |[合单native支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml)|[combine_transactions_native](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
135 | |[合单app支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml)|[combine_transactions_app](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
136 | |[合单JSAPI支付 或者 小程序支付](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml)|[combine_transactions_jsapi](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
137 | |[查询合单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_11.shtml)|[combine_query](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
138 | |[关闭合单](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml)|[combine_close](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/combine.md)|
139 | |[获取序列号]()|[getSN](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md)|
140 | |[申请退款](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_9.shtml)|[refunds](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md)|
141 | |[查询退款](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_10.shtml)|[find_refunds](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md)|
142 | |[签名验证](https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml)|[verifySign](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/verifySign.md)|
143 | |[微信提现到零钱](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml)|[batches_transfer](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/batches_transfer.md)|
144 | |[分账](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_1.shtml)|[create_profitsharing_orders](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/profitsharing.md)|
145 |
146 |
147 |
148 | ## 版本介绍
149 | | 版本号 | 版本介绍 |
150 | |--|--|
151 | | v0.0.1 | 仅支持签名和获取头部参数Authorization |
152 | |v1.0.0|增加了支付(不包括合单支付)、查询订单、关闭订单、申请交易账单、申请资金账单、下载账单|
153 | |v1.2.0|增加了回调解密,合单支付、合单关闭、合单查询|
154 | |v1.2.1|修改app、jsapi、native支付字段scene_info 改为可选|
155 | |v1.2.2|增加获取序列号方法|
156 | |v1.2.3|修改小程序支付签名错误和取消serial_no字段必填|
157 | |v1.3.0|增加普通订单的退款和查询|
158 | |v1.3.1|修复APP调起支付时出现“支付验证签名失败“的问题|
159 | |v1.3.2|增加请求成功后的签名验证|
160 | |v1.3.3|修复superagent post请求异常 Z_DATA_ERROR|
161 | |v1.3.4|修复superagent get请求异常 Z_DATA_ERROR|
162 | |v1.3.5|修复APP支付签名错误|
163 | |v2.0.0|增加提现到零钱和优化接口参数,规定返回参数格式,其他接口会后续优化|
164 | |v2.0.1|增加请求头Wechatpay-Serial和完善转账其他接口|
165 | |v2.0.2|增加敏感信息加密方法(publicEncrypt)|
166 | |v2.0.3|修复get请求无参时的签名|
167 | |v2.0.4|修复敏感信息加密方法(publicEncrypt)使用微信平台公钥|
168 | |v2.0.6|修复发起商家转账零钱参数wx_serial_no(自定义参数,即http请求头Wechatpay-Serial的值)为可选参数|
169 | |v2.1.0|升级superagent依赖6.1.0到8.0.6|
170 | |v2.1.1|商家转账API支持场景参数|
171 | |v2.1.2|基础支付接口支持传appid|
172 | |v2.1.3|支持分账相关接口|
173 | |v2.1.4|修复错误原因存在空值bug|
174 | |v2.1.5|修复动态 appid 签名错误|
175 | |v2.1.6|Native下单API支持support_fapiao字段|
176 | |v2.1.7|修复退款接口refunds和find_refunds返回结果中的http status会被业务status覆盖问题|
177 | |v2.1.8|修复回调签名key错误|
178 | |v2.2.0|修复回调解密报Unsupported state or unable to authenticate data |
179 | |v2.2.1|上传图片 |
180 |
181 | ## 文档
182 | [v2支付文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
183 | [v3支付文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)
184 |
185 |
186 | ## 贡献
187 |
188 |
189 |
190 |
191 | 欢迎提[存在的Bug或者意见](https://github.com/klover2/wechatpay-node-v3-ts/issues)。
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/define.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/pay-request.interface';
2 | export { Output } from './lib/interface-v2';
3 |
--------------------------------------------------------------------------------
/docs/batches_transfer.md:
--------------------------------------------------------------------------------
1 | ## 微信提现到零钱
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```js
15 | // 使用的同学可以自己增加定时器去维护这个微信平台公钥证书
16 | // 使用最新的平台证书(即:证书启用时间较晚的证书)
17 | const certificates = await pay.get_certificates("APIv3密钥");
18 | // 我这里取最后一个
19 | const certificate = certificates.pop();
20 |
21 | const res = await pay.batches_transfer({
22 | out_batch_no: 'plfk2020042013',
23 | batch_name: '2019年1月深圳分部报销单',
24 | batch_remark: '2019年1月深圳分部报销单',
25 | total_amount: 4000000,
26 | total_num: 200,
27 | wx_serial_no: certificate.serial_no, // 当你需要传user_name时 需要传当前参数
28 | transfer_detail_list: [
29 | {
30 | out_detail_no: 'x23zy545Bd5436',
31 | transfer_amount: 200000,
32 | transfer_remark: '2020年4月报销',
33 | openid: 'o-MYE42l80oelYMDE34nYD456Xoy',
34 | user_name: pay.publicEncrypt('张三', Buffer.from(certificate.publicKey)),
35 | }
36 | ],
37 | });
38 | console.log(res);
39 | ```
40 |
41 | ```js
42 | // 微信批次单号查询批次单API
43 | const res = await pay.query_batches_transfer_list_wx()
44 | // 微信明细单号查询明细单API
45 | const res = await pay.query_batches_transfer_detail_wx()
46 | // 商家批次单号查询批次单API
47 | const res = await pay.query_batches_transfer_list()
48 | // 商家明细单号查询明细单API
49 | const res = await pay.query_batches_transfer_detail()
50 | ```
--------------------------------------------------------------------------------
/docs/close.md:
--------------------------------------------------------------------------------
1 | ## 下载账单
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const reuslt = await pay.close(out_trade_no);
16 | console.log(reuslt);
17 | # {
18 | # 'status': 204
19 | # }
20 | ```
--------------------------------------------------------------------------------
/docs/combine.md:
--------------------------------------------------------------------------------
1 | ```bash
2 | import WxPay from 'wechatpay-node-v3';
3 | import fs from 'fs';
4 |
5 | const pay = new WxPay({
6 | appid: '直连商户申请的公众号或移动应用appid',
7 | mchid: '商户号',
8 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
9 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
10 | });
11 | ```
12 | ## 合单h5
13 | ```bash
14 | const params = {
15 | combine_out_trade_no: '主订单号',
16 | scene_info: {
17 | payer_client_ip: 'ip',
18 | device_id: '1',
19 | h5_info: {
20 | type: 'Wap',
21 | app_name: '网页名称 例如 百度',
22 | app_url: '网页域名 例如 https://www.baidu.com',
23 | },
24 | },
25 | sub_orders: [
26 | {
27 | mchid: '商户ID',
28 | amount: {
29 | total_amount: 1,
30 | currency: 'CNY',
31 | },
32 | out_trade_no: '子订单号',
33 | description: '测试1',
34 | attach: '测试',
35 | },
36 | ],
37 | notify_url: '回调url',
38 | };
39 | const result = await pay.combine_transactions_h5(params);
40 | console.log(result);
41 | ```
42 |
43 | ## 查询订单
44 | ```bash
45 | const result = await pay.combine_query(combine_out_trade_no);
46 | console.log(result);
47 | ```
48 |
49 | ## 关闭订单
50 | ```bash
51 | const result = await pay.combine_close(combine_out_trade_no, [
52 | {
53 | mchid: '商户号',
54 | out_trade_no: '子订单号',
55 | },
56 | ]);
57 |
58 | console.log(result);
59 | ```
--------------------------------------------------------------------------------
/docs/downloadbill.md:
--------------------------------------------------------------------------------
1 | ## 下载账单
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const data = await pay.downloadbill(download_url);
16 | ```
--------------------------------------------------------------------------------
/docs/fundflowbill.md:
--------------------------------------------------------------------------------
1 | ## 申请资金账单
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const result = await pay.fundflowbill({
16 | bill_date: '2020-11-11',
17 | account_type: 'BASIC',
18 | });
19 | console.log(result);
20 | # {
21 | # status: 200,
22 | # download_url: 'https://api.mch.weixin.qq.com/v3/billdownload/file?token=VThw-I-E1f2yVLIAzsYPKnkmoyR9f3oYZ',
23 | # hash_type: 'SHA1',
24 | # hash_value: '5f424569fc2adc7ee06531ec796b7bb9457df004'
25 | # }
26 | ```
--------------------------------------------------------------------------------
/docs/profitsharing.md:
--------------------------------------------------------------------------------
1 | ## 请求分账
2 | ```ts
3 | // 使用的同学可以自己增加定时器去维护这个微信平台公钥证书
4 | // 使用最新的平台证书(即:证书启用时间较晚的证书)
5 | const certificates = await pay.get_certificates('APIv3密钥');
6 | // 我这里取最后一个
7 | const certificate = certificates.pop();
8 |
9 | const res = await pay.create_profitsharing_orders({
10 | transaction_id: '4208450740201411110007820472',
11 | out_order_no: 'P20150806125346',
12 | receivers: [
13 | {
14 | type: 'MERCHANT_ID',
15 | account: '86693852',
16 | name: pay.publicEncrypt('张三', certificate.publicKey),
17 | amount: 888,
18 | description: '分给商户A',
19 | },
20 | ],
21 | unfreeze_unsplit: true,
22 | wx_serial_no: certificate.serial_no, // 当你需要传name时 需要传当前参数
23 | });
24 | ```
25 |
26 | ## 查询分账结果
27 |
28 | ```ts
29 | const res = await pay.query_profitsharing_orders('4208450740201411110007820472', 'P20150806125346');
30 | // https://api.mch.weixin.qq.com/v3/profitsharing/orders/P20150806125346?transaction_id=4208450740201411110007820472
31 | ```
32 |
33 | ## 请求分账回退API
34 |
35 | ```ts
36 | const res = await pay.profitsharing_return_orders(...)
37 | ```
38 |
39 | ## 查询分账回退结果API
40 |
41 | ```ts
42 | const res = await pay.query_profitsharing_return_orders(...)
43 | ```
44 |
45 | ## 解冻剩余资金API
46 |
47 | ```ts
48 | const res = await pay.profitsharing_orders_unfreeze(...)
49 | ```
50 |
51 | ## 查询剩余待分金额API
52 |
53 | ```ts
54 | const res = await pay.query_profitsharing_amounts(...)
55 | ```
56 |
57 | ## 添加分账接收方API
58 |
59 | ```ts
60 | // 使用的同学可以自己增加定时器去维护这个微信平台公钥证书
61 | // 使用最新的平台证书(即:证书启用时间较晚的证书)
62 | const certificates = await pay.get_certificates('APIv3密钥');
63 | // 我这里取最后一个
64 | const certificate = certificates.pop();
65 |
66 | const res = await pay.profitsharing_receivers_add({
67 | appid: 'wx8888888888888888',
68 | type: 'MERCHANT_ID',
69 | account: '86693852',
70 | name: pay.publicEncrypt('张三', certificate.publicKey),
71 | relation_type: 'STORE',
72 | custom_relation: '代理商',
73 | });
74 | ```
75 |
76 | ## 添加分账接收方API
77 |
78 | ```ts
79 | const res = await pay.profitsharing_receivers_delete(...)
80 | ```
81 |
82 | ## 分账动账通知API
83 |
84 | [参考回调解签](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/transactions_h5.md)
85 |
86 | ```ts
87 | const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
88 | ```
89 |
90 | ## 申请分账账单API
91 |
92 | ```ts
93 | const res = await pay.profitsharing_bills(...)
94 | ```
95 |
96 | ## 下载账单API
97 |
98 | [参考下载账单](https://github.com/klover2/wechatpay-node-v3-ts/blob/master/docs/downloadbill.md)
99 |
100 | ```ts
101 | const data = await pay.downloadbill(download_url);
102 | ```
--------------------------------------------------------------------------------
/docs/query.md:
--------------------------------------------------------------------------------
1 | ## 下载账单
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const result = await pay.query({out_trade_no: '1609914303237'});
16 | # 或者 const result = await pay.query({transaction_id: ''});
17 | console.log(result);
18 | {
19 | status: 200,
20 | appid: 'appid',
21 | attach: '',
22 | mchid: '商户号',
23 | out_trade_no: '1609899981750',
24 | payer: {},
25 | promotion_detail: [],
26 | trade_state: 'CLOSED',
27 | trade_state_desc: '订单已关闭'
28 | }
29 | ```
--------------------------------------------------------------------------------
/docs/tradebill.md:
--------------------------------------------------------------------------------
1 | ## 申请交易账单
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const result: any = await pay.tradebill({
16 | bill_date: '2020-11-11',
17 | bill_type: 'ALL',
18 | });
19 | console.log(result);
20 | # {
21 | # status: 200,
22 | # download_url: 'https://api.mch.weixin.qq.com/v3/billdownload/file?token=oMmUqPjv_pgVEbq41QjzZawTaL_HQeJu54e91h4q-Sq',
23 | # hash_type: 'SHA1',
24 | # hash_value: '8d185400ac11439e7352294bed99be36c621878c'
25 | # }
26 | ```
--------------------------------------------------------------------------------
/docs/transactions_app.md:
--------------------------------------------------------------------------------
1 | ## app支付
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const params = {
16 | description: '测试',
17 | out_trade_no: '订单号',
18 | notify_url: '回调url',
19 | amount: {
20 | total: 1,
21 | },
22 | scene_info: {
23 | payer_client_ip: 'ip',
24 | },
25 | };
26 | console.log(params);
27 | const result = await pay.transactions_app(params);
28 | console.log(result);
29 | # {
30 | # status: 200,
31 | # appid: 'appid',
32 | # partnerid: '商户号',
33 | # prepayid: 'wx061559014727156ae9554bb17af9d30000',
34 | # package: 'Sign=WXPay',
35 | # noncestr: 'm8dbyuytqul',
36 | # timestamp: '1609919941',
37 | # sign: 'PLENslMbldtSbtj5mDpX0N78vMMSw7CFPEptSpm+6YktXDa5Qso6KJ/uRCbNCmvM7z5adLoEdTmzjB/mjr5Ow=='
38 | # }
39 | ```
--------------------------------------------------------------------------------
/docs/transactions_h5.md:
--------------------------------------------------------------------------------
1 | ## h5 支付
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 |
14 | ## serial_no获取
15 | 1. 请在命令窗口使用 `openssl x509 -in apiclient_cert.pem -noout -serial` 获取
16 | 2. 调用方法`getSN()` // 不传入参数默认创建对象时的publicKey
17 | ```bash
18 | console.log('获取序列号', pay.getSN());
19 | 或者
20 | console.log('获取序列号', pay.getSN(fs.readFileSync('./apiclient_cert.pem')));
21 | ```
22 | ## 使用
23 | ```bash
24 | const params = {
25 | description: '测试',
26 | out_trade_no: '订单号',
27 | notify_url: '回调url',
28 | amount: {
29 | total: 1,
30 | },
31 | scene_info: {
32 | payer_client_ip: 'ip',
33 | h5_info: {
34 | type: 'Wap',
35 | app_name: '网页名称 例如 百度',
36 | app_url: '网页域名 例如 https://www.baidu.com',
37 | },
38 | },
39 | };
40 | console.log(params);
41 | const result = await pay.transactions_h5(params);
42 | console.log(result);
43 | # 返回
44 | # {
45 | # status: 200,
46 | # h5_url: 'https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx051840206120147833cf4bcfcef12b0000&package=2056162962'
47 | # }
48 | ```
49 |
50 | ## 回调解密 (支付成功通知 退款通知)
51 | ```bash
52 | # key 用商户平台上设置的APIv3密钥【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】,记为key;
53 | const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
54 | # 返回
55 | # {
56 | # mchid: '商户号',
57 | # appid: 'appid',
58 | # out_trade_no: '1610419296553',
59 | # transaction_id: '4200000848202101120290526543',
60 | # trade_type: 'NATIVE',
61 | # trade_state: 'SUCCESS',
62 | # trade_state_desc: '支付成功',
63 | # bank_type: 'OTHERS',
64 | # attach: '',
65 | # success_time: '2021-01-12T10:43:43+08:00',
66 | # payer: { openid: '' },
67 | # amount: { total: 1, payer_total: 1, currency: 'CNY', payer_currency: 'CNY' }
68 | # }
69 | ```
70 |
71 | ## 申请退款 (这里只是普通订单退款 合单支付商户号不支持 以后可以测试会补上)
72 | ```js
73 | const params = {
74 | out_trade_no: '1615171309328',
75 | out_refund_no: '1615171380622',
76 | reason: '测试',
77 | amount: {
78 | refund: 1,
79 | total: 1,
80 | currency: 'CNY',
81 | },
82 | };
83 | console.log(params);
84 | const result = await pay.refunds(params);
85 | console.log(result);
86 | ```
87 | ## 查询退款
88 | ```js
89 | const result = await pay.find_refunds('1615171380622');
90 | console.log(result);
91 | ```
92 |
--------------------------------------------------------------------------------
/docs/transactions_jsapi.md:
--------------------------------------------------------------------------------
1 | ## JSAPI支付
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const params = {
16 | description: '测试',
17 | out_trade_no: '订单号',
18 | notify_url: '回调url',
19 | amount: {
20 | total: 1,
21 | },
22 | payer: {
23 | openid: 'drEc8QfY',
24 | },
25 | scene_info: {
26 | payer_client_ip: 'ip',
27 | },
28 | };
29 | console.log(params);
30 | const result = await pay.transactions_jsapi(params);
31 | console.log(result);
32 | # {
33 | # appId: 'appid',
34 | # timeStamp: '1609918952',
35 | # nonceStr: 'y8aw9vrmx8c',
36 | # package: 'prepay_id=wx0615423208772665709493edbb4b330000',
37 | # signType: 'RSA',
38 | # paySign: 'JnFXsT4VNzlcamtmgOHhziw7JqdnUS9qJ5W6vmAluk3Q2nska7rxYB4hvcl0BTFAB1PBEnHEhCsUbs5zKPEig=='
39 | # }
40 | ```
--------------------------------------------------------------------------------
/docs/transactions_native.md:
--------------------------------------------------------------------------------
1 | ## native支付
2 | ```bash
3 | import WxPay from 'wechatpay-node-v3';
4 | import fs from 'fs';
5 |
6 | const pay = new WxPay({
7 | appid: '直连商户申请的公众号或移动应用appid',
8 | mchid: '商户号',
9 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
10 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
11 | });
12 | ```
13 | ## 使用
14 | ```bash
15 | const params = {
16 | description: '测试',
17 | out_trade_no: '订单号',
18 | notify_url: '回调url',
19 | amount: {
20 | total: 1,
21 | },
22 | scene_info: {
23 | payer_client_ip: 'ip',
24 | },
25 | };
26 | const result = await pay.transactions_native(params);
27 | console.log(result);
28 | # { status: 200, code_url: 'weixin://wxpay/bizpayurl?pr=9xFPmlUzz' }
29 | ```
--------------------------------------------------------------------------------
/docs/verifySign.md:
--------------------------------------------------------------------------------
1 | ```typescript
2 | import WxPay from 'wechatpay-node-v3';
3 | import fs from 'fs';
4 |
5 | const pay = new WxPay({
6 | appid: '直连商户申请的公众号或移动应用appid',
7 | mchid: '商户号',
8 | publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥
9 | privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥
10 | // key: 'APIv3密钥', // APIv3密钥,可选参数
11 | });
12 | ```
13 |
14 | ## 签名验证
15 | ```typescript
16 | const headers = req.headers; // 请求头信息
17 | const params = {
18 | apiSecret: 'APIv3密钥', // 如果在构造中传入了 key, 这里可以不传该值,否则需要传入该值
19 | body: req.body, // 请求体 body
20 | signature: headers['wechatpay-signature'],
21 | serial: headers['wechatpay-serial'],
22 | nonce: headers['wechatpay-nonce'],
23 | timestamp: headers['wechatpay-timestamp'],
24 | };
25 |
26 | const ret = await pay.verifySign(params);
27 |
28 | console.log('验签结果(bool类型):' + ret);
29 |
30 | return ret;
31 | ```
32 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import crypto from 'crypto';
3 | const x509_1 = require('@fidm/x509');
4 |
5 | import {
6 | Ipay,
7 | Ih5,
8 | Inative,
9 | Ijsapi,
10 | Iquery1,
11 | Iquery2,
12 | Itradebill,
13 | Ifundflowbill,
14 | Iapp,
15 | Ioptions,
16 | Irefunds1,
17 | Irefunds2,
18 | ICertificates,
19 | } from './lib/interface';
20 | import { IcombineH5, IcombineNative, IcombineApp, IcombineJsapi, IcloseSubOrders } from './lib/combine_interface';
21 | import { BatchesTransfer, FindRefunds, ProfitSharing, Refunds, UploadImages } from './lib/interface-v2';
22 | import { Base } from './lib/base';
23 | import { IPayRequest } from './lib/pay-request.interface';
24 | import { PayRequest } from './lib/pay-request';
25 |
26 | class Pay extends Base {
27 | protected appid: string; // 直连商户申请的公众号或移动应用appid。
28 | protected mchid: string; // 商户号
29 | protected serial_no = ''; // 证书序列号
30 | protected publicKey?: Buffer; // 公钥
31 | protected privateKey?: Buffer; // 密钥
32 | protected authType = 'WECHATPAY2-SHA256-RSA2048'; // 认证类型,目前为WECHATPAY2-SHA256-RSA2048
33 | protected httpService: IPayRequest;
34 |
35 | protected key?: string; // APIv3密钥
36 | protected static certificates: { [key in string]: string } = {}; // 微信平台证书 key 是 serialNo, value 是 publicKey
37 | /**
38 | * 构造器
39 | * @param appid 直连商户申请的公众号或移动应用appid。
40 | * @param mchid 商户号
41 | * @param publicKey 公钥
42 | * @param privateKey 密钥
43 | * @param options 可选参数 object 包括下面参数
44 | *
45 | * @param serial_no 证书序列号
46 | * @param authType 可选参数 认证类型,目前为WECHATPAY2-SHA256-RSA2048
47 | * @param userAgent 可选参数 User-Agent
48 | * @param key 可选参数 APIv3密钥
49 | */
50 | public constructor(appid: string, mchid: string, publicKey: Buffer, privateKey: Buffer, options?: Ioptions);
51 | /**
52 | * 构造器
53 | * @param obj object类型 包括下面参数
54 | *
55 | * @param appid 直连商户申请的公众号或移动应用appid。
56 | * @param mchid 商户号
57 | * @param serial_no 可选参数 证书序列号
58 | * @param publicKey 公钥
59 | * @param privateKey 密钥
60 | * @param authType 可选参数 认证类型,目前为WECHATPAY2-SHA256-RSA2048
61 | * @param userAgent 可选参数 User-Agent
62 | * @param key 可选参数 APIv3密钥
63 | */
64 | public constructor(obj: Ipay);
65 | public constructor(arg1: Ipay | string, mchid?: string, publicKey?: Buffer, privateKey?: Buffer, options?: Ioptions) {
66 | super();
67 |
68 | if (arg1 instanceof Object) {
69 | this.appid = arg1.appid;
70 | this.mchid = arg1.mchid;
71 | if (arg1.serial_no) this.serial_no = arg1.serial_no;
72 | this.publicKey = arg1.publicKey;
73 | if (!this.publicKey) throw new Error('缺少公钥');
74 | this.privateKey = arg1.privateKey;
75 | if (!arg1.serial_no) this.serial_no = this.getSN(this.publicKey);
76 |
77 | this.authType = arg1.authType || 'WECHATPAY2-SHA256-RSA2048';
78 | this.userAgent = arg1.userAgent || '127.0.0.1';
79 | this.key = arg1.key;
80 | } else {
81 | const _optipns = options || {};
82 | this.appid = arg1;
83 | this.mchid = mchid || '';
84 | this.publicKey = publicKey;
85 | this.privateKey = privateKey;
86 |
87 | this.authType = _optipns.authType || 'WECHATPAY2-SHA256-RSA2048';
88 | this.userAgent = _optipns.userAgent || '127.0.0.1';
89 | this.key = _optipns.key;
90 | this.serial_no = _optipns.serial_no || '';
91 | if (!this.publicKey) throw new Error('缺少公钥');
92 | if (!this.serial_no) this.serial_no = this.getSN(this.publicKey);
93 | }
94 |
95 | this.httpService = new PayRequest();
96 | }
97 | /**
98 | * 自定义创建http 请求
99 | */
100 | public createHttp(service: IPayRequest) {
101 | this.httpService = service;
102 | }
103 | /**
104 | * 获取微信平台key
105 | * @param apiSecret APIv3密钥
106 | * @returns
107 | */
108 | public async get_certificates(apiSecret: string): Promise {
109 | const url = 'https://api.mch.weixin.qq.com/v3/certificates';
110 | const authorization = this.buildAuthorization('GET', url);
111 | const headers = this.getHeaders(authorization);
112 |
113 | const result = await this.httpService.get(url, headers);
114 |
115 | if (result.status === 200) {
116 | const data = result?.data?.data as ICertificates[];
117 |
118 | for (const item of data) {
119 | const decryptCertificate = this.decipher_gcm(
120 | item.encrypt_certificate.ciphertext,
121 | item.encrypt_certificate.associated_data,
122 | item.encrypt_certificate.nonce,
123 | apiSecret,
124 | );
125 | item.publicKey = x509_1.Certificate.fromPEM(Buffer.from(decryptCertificate)).publicKey.toPEM();
126 | }
127 |
128 | return data;
129 | } else {
130 | throw new Error('拉取平台证书失败');
131 | }
132 | }
133 | /**
134 | * 拉取平台证书到 Pay.certificates 中
135 | * @param apiSecret APIv3密钥
136 | * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml
137 | */
138 | private async fetchCertificates(apiSecret?: string) {
139 | const url = 'https://api.mch.weixin.qq.com/v3/certificates';
140 | const authorization = this.buildAuthorization('GET', url);
141 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
142 | const result = await this.httpService.get(url, headers);
143 |
144 | if (result.status === 200) {
145 | const data = result?.data?.data as {
146 | effective_time: string;
147 | expire_time: string;
148 | serial_no: string;
149 | encrypt_certificate: {
150 | algorithm: string;
151 | associated_data: string;
152 | ciphertext: string;
153 | nonce: string;
154 | };
155 | }[];
156 |
157 | const newCertificates = {} as { [key in string]: string };
158 |
159 | data.forEach(item => {
160 | const decryptCertificate = this.decipher_gcm(
161 | item.encrypt_certificate.ciphertext,
162 | item.encrypt_certificate.associated_data,
163 | item.encrypt_certificate.nonce,
164 | apiSecret,
165 | );
166 |
167 | newCertificates[item.serial_no] = x509_1.Certificate.fromPEM(Buffer.from(decryptCertificate)).publicKey.toPEM();
168 | });
169 |
170 | Pay.certificates = {
171 | ...Pay.certificates,
172 | ...newCertificates,
173 | };
174 | } else {
175 | throw new Error('拉取平台证书失败');
176 | }
177 | }
178 | /**
179 | * 验证签名,提醒:node 取头部信息时需要用小写,例如:req.headers['wechatpay-timestamp']
180 | * @param params.timestamp HTTP头Wechatpay-Timestamp 中的应答时间戳
181 | * @param params.nonce HTTP头Wechatpay-Nonce 中的应答随机串
182 | * @param params.body 应答主体(response Body),需要按照接口返回的顺序进行验签,错误的顺序将导致验签失败。
183 | * @param params.serial HTTP头Wechatpay-Serial 证书序列号
184 | * @param params.signature HTTP头Wechatpay-Signature 签名
185 | * @param params.apiSecret APIv3密钥,如果在 构造器 中有初始化该值(this.key),则可以不传入。当然传入也可以
186 | */
187 | public async verifySign(params: {
188 | timestamp: string | number;
189 | nonce: string;
190 | body: Record | string;
191 | serial: string;
192 | signature: string;
193 | apiSecret?: string;
194 | }) {
195 | const { timestamp, nonce, body, serial, signature, apiSecret } = params;
196 |
197 | let publicKey = Pay.certificates[serial];
198 |
199 | if (!publicKey) {
200 | await this.fetchCertificates(apiSecret);
201 | }
202 |
203 | publicKey = Pay.certificates[serial];
204 |
205 | if (!publicKey) {
206 | throw new Error('平台证书序列号不相符,未找到平台序列号');
207 | }
208 |
209 | const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
210 | const data = `${timestamp}\n${nonce}\n${bodyStr}\n`;
211 | const verify = crypto.createVerify('RSA-SHA256');
212 | verify.update(data);
213 |
214 | return verify.verify(publicKey, signature, 'base64');
215 | }
216 | /**
217 | * 敏感信息加密
218 | * @param str 敏感信息字段(如用户的住址、银行卡号、手机号码等)
219 | * @returns
220 | */
221 | public publicEncrypt(str: string, wxPublicKey: Buffer, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
222 | if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
223 | throw new Error(`Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`);
224 | }
225 | const encrypted = crypto.publicEncrypt({ key: wxPublicKey, padding, oaepHash: 'sha1' }, Buffer.from(str, 'utf8')).toString('base64');
226 | return encrypted;
227 | }
228 | /**
229 | * 敏感信息解密
230 | * @param str 敏感信息字段(如用户的住址、银行卡号、手机号码等)
231 | * @returns
232 | */
233 | public privateDecrypt(str: string, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
234 | if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
235 | throw new Error(`Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`);
236 | }
237 | const decrypted = crypto.privateDecrypt({ key: this.privateKey as Buffer, padding, oaepHash: 'sha1' }, Buffer.from(str, 'base64'));
238 | return decrypted.toString('utf8');
239 | }
240 | /**
241 | * 构建请求签名参数
242 | * @param method Http 请求方式
243 | * @param url 请求接口 例如/v3/certificates
244 | * @param timestamp 获取发起请求时的系统当前时间戳
245 | * @param nonceStr 随机字符串
246 | * @param body 请求报文主体
247 | */
248 | public getSignature(method: string, nonce_str: string, timestamp: string, url: string, body?: string | Record): string {
249 | let str = method + '\n' + url + '\n' + timestamp + '\n' + nonce_str + '\n';
250 | if (body && body instanceof Object) body = JSON.stringify(body);
251 | if (body) str = str + body + '\n';
252 | if (method === 'GET') str = str + '\n';
253 | return this.sha256WithRsa(str);
254 | }
255 | // jsapi 和 app 支付参数签名 加密自动顺序如下 不能错乱
256 | // 应用id
257 | // 时间戳
258 | // 随机字符串
259 | // 预支付交易会话ID
260 | protected sign(str: string) {
261 | return this.sha256WithRsa(str);
262 | }
263 | // 获取序列号
264 | public getSN(fileData?: string | Buffer): string {
265 | if (!fileData && !this.publicKey) throw new Error('缺少公钥');
266 | if (!fileData) fileData = this.publicKey;
267 | if (typeof fileData == 'string') {
268 | fileData = Buffer.from(fileData);
269 | }
270 |
271 | const certificate = x509_1.Certificate.fromPEM(fileData);
272 | return certificate.serialNumber;
273 | }
274 | /**
275 | * SHA256withRSA
276 | * @param data 待加密字符
277 | * @param privatekey 私钥key key.pem fs.readFileSync(keyPath)
278 | */
279 | public sha256WithRsa(data: string): string {
280 | if (!this.privateKey) throw new Error('缺少秘钥文件');
281 | return crypto
282 | .createSign('RSA-SHA256')
283 | .update(data)
284 | .sign(this.privateKey, 'base64');
285 | }
286 | /**
287 | * 获取授权认证信息
288 | * @param nonceStr 请求随机串
289 | * @param timestamp 时间戳
290 | * @param signature 签名值
291 | */
292 | public getAuthorization(nonce_str: string, timestamp: string, signature: string): string {
293 | const _authorization =
294 | 'mchid="' +
295 | this.mchid +
296 | '",' +
297 | 'nonce_str="' +
298 | nonce_str +
299 | '",' +
300 | 'timestamp="' +
301 | timestamp +
302 | '",' +
303 | 'serial_no="' +
304 | this.serial_no +
305 | '",' +
306 | 'signature="' +
307 | signature +
308 | '"';
309 | return this.authType.concat(' ').concat(_authorization);
310 | }
311 | /**
312 | * 回调解密
313 | * @param ciphertext Base64编码后的开启/停用结果数据密文
314 | * @param associated_data 附加数据
315 | * @param nonce 加密使用的随机串
316 | * @param key APIv3密钥
317 | */
318 | public decipher_gcm(ciphertext: string, associated_data: string, nonce: string, key?: string): T {
319 | if (!key) key = this.key;
320 | if (!key) throw new Error('缺少key');
321 |
322 | const _ciphertext = Buffer.from(ciphertext, 'base64');
323 |
324 | // 解密 ciphertext字符 AEAD_AES_256_GCM算法
325 | const authTag: any = _ciphertext.slice(_ciphertext.length - 16);
326 | const data = _ciphertext.slice(0, _ciphertext.length - 16);
327 | const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
328 | decipher.setAuthTag(authTag);
329 | decipher.setAAD(Buffer.from(associated_data));
330 | const decoded = decipher.update(data, undefined, 'utf8');
331 |
332 | try {
333 | return JSON.parse(decoded);
334 | } catch (e) {
335 | return decoded as T;
336 | }
337 | }
338 | /**
339 | * 参数初始化
340 | */
341 | protected buildAuthorization(method: string, url: string, params?: Record) {
342 | const nonce_str = Math.random()
343 | .toString(36)
344 | .substr(2, 15),
345 | timestamp = parseInt(+new Date() / 1000 + '').toString();
346 |
347 | const signature = this.getSignature(method, nonce_str, timestamp, url.replace('https://api.mch.weixin.qq.com', ''), params);
348 | const authorization = this.getAuthorization(nonce_str, timestamp, signature);
349 | return authorization;
350 | }
351 | //#region 支付相关接口
352 | /**
353 | * h5支付
354 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml
355 | */
356 | public async transactions_h5(params: Ih5) {
357 | // 请求参数
358 | const _params = {
359 | appid: this.appid,
360 | mchid: this.mchid,
361 | ...params,
362 | };
363 | const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/h5';
364 |
365 | const authorization = this.buildAuthorization('POST', url, _params);
366 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
367 | return await this.httpService.post(url, _params, headers);
368 | }
369 | /**
370 | * 合单h5支付
371 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml
372 | */
373 | public async combine_transactions_h5(params: IcombineH5) {
374 | // 请求参数
375 | const _params = {
376 | combine_appid: this.appid,
377 | combine_mchid: this.mchid,
378 | ...params,
379 | };
380 | const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/h5';
381 |
382 | const authorization = this.buildAuthorization('POST', url, _params);
383 |
384 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
385 |
386 | return await this.httpService.post(url, _params, headers);
387 | }
388 | /**
389 | * native支付
390 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
391 | */
392 | public async transactions_native(params: Inative) {
393 | // 请求参数
394 | const _params = {
395 | appid: this.appid,
396 | mchid: this.mchid,
397 | ...params,
398 | };
399 | const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native';
400 |
401 | const authorization = this.buildAuthorization('POST', url, _params);
402 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
403 | return await this.httpService.post(url, _params, headers);
404 | }
405 | /**
406 | * 合单native支付
407 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml
408 | */
409 | public async combine_transactions_native(params: IcombineNative) {
410 | // 请求参数
411 | const _params = {
412 | combine_appid: this.appid,
413 | combine_mchid: this.mchid,
414 | ...params,
415 | };
416 | const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/native';
417 |
418 | const authorization = this.buildAuthorization('POST', url, _params);
419 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
420 | return await this.httpService.post(url, _params, headers);
421 | }
422 | /**
423 | * app支付
424 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
425 | */
426 | public async transactions_app(params: Iapp) {
427 | // 请求参数
428 | const _params = {
429 | appid: this.appid,
430 | mchid: this.mchid,
431 | ...params,
432 | };
433 | const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app';
434 |
435 | const authorization = this.buildAuthorization('POST', url, _params);
436 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
437 | const result = await this.httpService.post(url, _params, headers);
438 | if (result.status === 200 && result.data.prepay_id) {
439 | const data = {
440 | appid: _params.appid,
441 | partnerid: _params.mchid,
442 | package: 'Sign=WXPay',
443 | timestamp: parseInt(+new Date() / 1000 + '').toString(),
444 | noncestr: Math.random()
445 | .toString(36)
446 | .substr(2, 15),
447 | prepayid: result.data.prepay_id,
448 | sign: '',
449 | };
450 | const str = [data.appid, data.timestamp, data.noncestr, data.prepayid, ''].join('\n');
451 | data.sign = this.sign(str);
452 | result.data = data;
453 | }
454 | return result;
455 | }
456 | /**
457 | * 合单app支付
458 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml
459 | */
460 | public async combine_transactions_app(params: IcombineApp) {
461 | // 请求参数
462 | const _params = {
463 | combine_appid: this.appid,
464 | combine_mchid: this.mchid,
465 | ...params,
466 | };
467 | const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/app';
468 |
469 | const authorization = this.buildAuthorization('POST', url, _params);
470 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
471 | const result = await this.httpService.post(url, _params, headers);
472 | if (result.status === 200 && result.data.prepay_id) {
473 | const data = {
474 | appid: _params.combine_appid,
475 | partnerid: _params.combine_mchid,
476 | package: 'Sign=WXPay',
477 | timestamp: parseInt(+new Date() / 1000 + '').toString(),
478 | noncestr: Math.random()
479 | .toString(36)
480 | .substr(2, 15),
481 | prepayid: result.data.prepay_id,
482 | sign: '',
483 | };
484 | const str = [data.appid, data.timestamp, data.noncestr, data.prepayid, ''].join('\n');
485 | data.sign = this.sign(str);
486 | result.data = data;
487 | }
488 | return result;
489 | }
490 | /**
491 | * JSAPI支付 或者 小程序支付
492 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
493 | */
494 | public async transactions_jsapi(params: Ijsapi) {
495 | // 请求参数
496 | const _params = {
497 | appid: this.appid,
498 | mchid: this.mchid,
499 | ...params,
500 | };
501 | const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
502 |
503 | const authorization = this.buildAuthorization('POST', url, _params);
504 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
505 | const result = await this.httpService.post(url, _params, headers);
506 | if (result.status === 200 && result.data.prepay_id) {
507 | const data = {
508 | appId: _params.appid,
509 | timeStamp: parseInt(+new Date() / 1000 + '').toString(),
510 | nonceStr: Math.random()
511 | .toString(36)
512 | .substr(2, 15),
513 | package: `prepay_id=${result.data.prepay_id}`,
514 | signType: 'RSA',
515 | paySign: '',
516 | };
517 | const str = [data.appId, data.timeStamp, data.nonceStr, data.package, ''].join('\n');
518 | data.paySign = this.sign(str);
519 | result.data = data;
520 | }
521 | return result;
522 | }
523 | /**
524 | * 合单JSAPI支付 或者 小程序支付
525 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml
526 | */
527 | public async combine_transactions_jsapi(params: IcombineJsapi) {
528 | // 请求参数
529 | const _params = {
530 | combine_appid: this.appid,
531 | combine_mchid: this.mchid,
532 | ...params,
533 | };
534 | const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi';
535 |
536 | const authorization = this.buildAuthorization('POST', url, _params);
537 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
538 | const result = await this.httpService.post(url, _params, headers);
539 | if (result.status === 200 && result.data.prepay_id) {
540 | const data = {
541 | appId: _params.combine_appid,
542 | timeStamp: parseInt(+new Date() / 1000 + '').toString(),
543 | nonceStr: Math.random()
544 | .toString(36)
545 | .substr(2, 15),
546 | package: `prepay_id=${result.data.prepay_id}`,
547 | signType: 'RSA',
548 | paySign: '',
549 | };
550 | const str = [data.appId, data.timeStamp, data.nonceStr, data.package, ''].join('\n');
551 | data.paySign = this.sign(str);
552 | result.data = data;
553 | }
554 | return result;
555 | }
556 | /**
557 | * 查询订单
558 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_2.shtml
559 | */
560 | public async query(params: Iquery1 | Iquery2) {
561 | let url = '';
562 | if (params.transaction_id) {
563 | url = `https://api.mch.weixin.qq.com/v3/pay/transactions/id/${params.transaction_id}?mchid=${this.mchid}`;
564 | } else if (params.out_trade_no) {
565 | url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${params.out_trade_no}?mchid=${this.mchid}`;
566 | } else {
567 | throw new Error('缺少transaction_id或者out_trade_no');
568 | }
569 |
570 | const authorization = this.buildAuthorization('GET', url);
571 | const headers = this.getHeaders(authorization);
572 | return await this.httpService.get(url, headers);
573 | }
574 | /**
575 | * 合单查询订单
576 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_11.shtml
577 | */
578 | public async combine_query(combine_out_trade_no: string) {
579 | if (!combine_out_trade_no) throw new Error('缺少combine_out_trade_no');
580 | const url = `https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/${combine_out_trade_no}`;
581 |
582 | const authorization = this.buildAuthorization('GET', url);
583 | const headers = this.getHeaders(authorization);
584 | return await this.httpService.get(url, headers);
585 | }
586 | /**
587 | * 关闭订单
588 | * @param out_trade_no 请求参数 商户订单号 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_3.shtml
589 | */
590 | public async close(out_trade_no: string) {
591 | if (!out_trade_no) throw new Error('缺少out_trade_no');
592 |
593 | // 请求参数
594 | const _params = {
595 | mchid: this.mchid,
596 | };
597 | const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${out_trade_no}/close`;
598 | const authorization = this.buildAuthorization('POST', url, _params);
599 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
600 | return await this.httpService.post(url, _params, headers);
601 | }
602 | /**
603 | * 合单关闭订单
604 | * @param combine_out_trade_no 请求参数 总订单号 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml
605 | * @param sub_orders array 子单信息
606 | */
607 | public async combine_close(combine_out_trade_no: string, sub_orders: IcloseSubOrders[]) {
608 | if (!combine_out_trade_no) throw new Error('缺少out_trade_no');
609 |
610 | // 请求参数
611 | const _params = {
612 | combine_appid: this.appid,
613 | sub_orders,
614 | };
615 | const url = `https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/${combine_out_trade_no}/close`;
616 | const authorization = this.buildAuthorization('POST', url, _params);
617 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
618 | return await this.httpService.post(url, _params, headers);
619 | }
620 | /**
621 | * 申请交易账单
622 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_6.shtml
623 | */
624 | public async tradebill(params: Itradebill) {
625 | let url = 'https://api.mch.weixin.qq.com/v3/bill/tradebill';
626 | const _params: any = {
627 | ...params,
628 | };
629 | const querystring = Object.keys(_params)
630 | .filter(key => {
631 | return !!_params[key];
632 | })
633 | .sort()
634 | .map(key => {
635 | return key + '=' + _params[key];
636 | })
637 | .join('&');
638 | url = url + `?${querystring}`;
639 | const authorization = this.buildAuthorization('GET', url);
640 | const headers = this.getHeaders(authorization);
641 | return await this.httpService.get(url, headers);
642 | }
643 | /**
644 | * 申请资金账单
645 | * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_7.shtml
646 | */
647 | public async fundflowbill(params: Ifundflowbill) {
648 | let url = 'https://api.mch.weixin.qq.com/v3/bill/fundflowbill';
649 | const _params: any = {
650 | ...params,
651 | };
652 | const querystring = Object.keys(_params)
653 | .filter(key => {
654 | return !!_params[key];
655 | })
656 | .sort()
657 | .map(key => {
658 | return key + '=' + _params[key];
659 | })
660 | .join('&');
661 | url = url + `?${querystring}`;
662 | const authorization = this.buildAuthorization('GET', url);
663 | const headers = this.getHeaders(authorization);
664 | return await this.httpService.get(url, headers);
665 | }
666 | /**
667 | * 下载账单
668 | * @param download_url 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml
669 | */
670 | public async downloadBill(download_url: string) {
671 | const authorization = this.buildAuthorization('GET', download_url);
672 | const headers = this.getHeaders(authorization);
673 | return await this.httpService.get(download_url, headers);
674 | }
675 | /**
676 | * 申请退款
677 | * @param params 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_9.shtml
678 | */
679 | public async refunds(params: Irefunds1 | Irefunds2): Promise {
680 | const url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';
681 | // 请求参数
682 | const _params = {
683 | ...params,
684 | };
685 |
686 | const authorization = this.buildAuthorization('POST', url, _params);
687 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
688 | return await this.httpService.post(url, _params, headers);
689 | }
690 | /**
691 | * 查询单笔退款
692 | * @documentation 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_10.shtml
693 | */
694 | public async find_refunds(out_refund_no: string): Promise {
695 | if (!out_refund_no) throw new Error('缺少out_refund_no');
696 | const url = `https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/${out_refund_no}`;
697 |
698 | const authorization = this.buildAuthorization('GET', url);
699 | const headers = this.getHeaders(authorization);
700 | return await this.httpService.get(url, headers);
701 | }
702 | //#endregion 支付相关接口
703 | //#region 商家转账到零钱
704 | /**
705 | * 发起商家转账零钱
706 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
707 | */
708 | public async batches_transfer(params: BatchesTransfer.Input): Promise {
709 | const url = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
710 | // 请求参数
711 | const _params = {
712 | appid: this.appid,
713 | ...params,
714 | };
715 |
716 | const serial_no = _params?.wx_serial_no;
717 | delete _params.wx_serial_no;
718 | const authorization = this.buildAuthorization('POST', url, _params);
719 |
720 | const headers = this.getHeaders(authorization, { 'Wechatpay-Serial': serial_no || this.serial_no, 'Content-Type': 'application/json' });
721 | return await this.httpService.post(url, _params, headers);
722 | }
723 | /**
724 | * 微信批次单号查询批次单API
725 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_2.shtml
726 | */
727 | public async query_batches_transfer_list_wx(
728 | params: BatchesTransfer.QueryBatchesTransferByWx.Input,
729 | ): Promise {
730 | const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/${params.batch_id}`;
731 | const url = baseUrl + this.objectToQueryString(params, ['batch_id']);
732 | const authorization = this.buildAuthorization('GET', url);
733 | const headers = this.getHeaders(authorization);
734 | return await this.httpService.get(url, headers);
735 | }
736 | /**
737 | * 微信明细单号查询明细单API
738 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_3.shtml
739 | */
740 | public async query_batches_transfer_detail_wx(
741 | params: BatchesTransfer.QueryBatchesTransferDetailByWx.Input,
742 | ): Promise {
743 | const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/${params.batch_id}/details/detail-id/${params.detail_id}`;
744 | const url = baseUrl + this.objectToQueryString(params, ['batch_id', 'detail_id']);
745 | const authorization = this.buildAuthorization('GET', url);
746 | const headers = this.getHeaders(authorization);
747 | return await this.httpService.get(url, headers);
748 | }
749 | /**
750 | * 商家批次单号查询批次单API
751 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_5.shtml
752 | */
753 | public async query_batches_transfer_list(
754 | params: BatchesTransfer.QueryBatchesTransferList.Input,
755 | ): Promise {
756 | const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/${params.out_batch_no}`;
757 | const url = baseUrl + this.objectToQueryString(params, ['out_batch_no']);
758 | const authorization = this.buildAuthorization('GET', url);
759 | const headers = this.getHeaders(authorization);
760 | return await this.httpService.get(url, headers);
761 | }
762 | /**
763 | * 商家明细单号查询明细单API
764 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_6.shtml
765 | */
766 | public async query_batches_transfer_detail(
767 | params: BatchesTransfer.QueryBatchesTransferDetail.Input,
768 | ): Promise {
769 | const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/${params.out_batch_no}/details/out-detail-no/${params.out_detail_no}`;
770 | const url = baseUrl + this.objectToQueryString(params, ['out_batch_no', 'out_detail_no']);
771 | const authorization = this.buildAuthorization('GET', url);
772 | const headers = this.getHeaders(authorization);
773 | return await this.httpService.get(url, headers);
774 | }
775 | //#endregion 商家转账到零钱
776 | //#region 分账
777 | /**
778 | * 请求分账API
779 | * @param params
780 | * @returns
781 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_1.shtml
782 | */
783 | public async create_profitsharing_orders(
784 | params: ProfitSharing.CreateProfitSharingOrders.Input,
785 | ): Promise {
786 | const url = 'https://api.mch.weixin.qq.com/v3/profitsharing/orders';
787 | // 请求参数
788 | const _params = {
789 | appid: this.appid,
790 | ...params,
791 | };
792 |
793 | const serial_no = _params?.wx_serial_no;
794 | delete _params.wx_serial_no;
795 | const authorization = this.buildAuthorization('POST', url, _params);
796 |
797 | const headers = this.getHeaders(authorization, { 'Wechatpay-Serial': serial_no || this.serial_no, 'Content-Type': 'application/json' });
798 | return await this.httpService.post(url, _params, headers);
799 | }
800 | /**
801 | * 查询分账结果API
802 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_2.shtml
803 | */
804 | public async query_profitsharing_orders(transaction_id: string, out_order_no: string): Promise {
805 | if (!transaction_id) throw new Error('缺少transaction_id');
806 | if (!out_order_no) throw new Error('缺少out_order_no');
807 | let url = `https://api.mch.weixin.qq.com/v3/profitsharing/orders/${out_order_no}`;
808 | url = url + this.objectToQueryString({ transaction_id });
809 | const authorization = this.buildAuthorization('GET', url);
810 | const headers = this.getHeaders(authorization);
811 | return await this.httpService.get(url, headers);
812 | }
813 | /**
814 | * 请求分账回退API
815 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_3.shtml
816 | */
817 | public async profitsharing_return_orders(
818 | params: ProfitSharing.ProfitSharingReturnOrders.Input,
819 | ): Promise {
820 | const url = 'https://api.mch.weixin.qq.com/v3/profitsharing/return-orders';
821 | // 请求参数
822 | const _params = {
823 | ...params,
824 | };
825 |
826 | const authorization = this.buildAuthorization('POST', url, _params);
827 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
828 | return await this.httpService.post(url, _params, headers);
829 | }
830 | /**
831 | * 查询分账回退结果API
832 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_4.shtml
833 | */
834 | public async query_profitsharing_return_orders(
835 | out_return_no: string,
836 | out_order_no: string,
837 | ): Promise {
838 | if (!out_return_no) throw new Error('缺少out_return_no');
839 | if (!out_order_no) throw new Error('缺少out_order_no');
840 | let url = `https://api.mch.weixin.qq.com/v3/profitsharing/return-orders/${out_return_no}`;
841 | url = url + this.objectToQueryString({ out_order_no });
842 | const authorization = this.buildAuthorization('GET', url);
843 | const headers = this.getHeaders(authorization);
844 | return await this.httpService.get(url, headers);
845 | }
846 | /**
847 | * 解冻剩余资金API
848 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_5.shtml
849 | */
850 | public async profitsharing_orders_unfreeze(
851 | params: ProfitSharing.ProfitsharingOrdersUnfreeze.Input,
852 | ): Promise {
853 | const url = 'https://api.mch.weixin.qq.com/v3/profitsharing/orders/unfreeze';
854 | // 请求参数
855 | const _params = {
856 | ...params,
857 | };
858 |
859 | const authorization = this.buildAuthorization('POST', url, _params);
860 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
861 | return await this.httpService.post(url, _params, headers);
862 | }
863 | /**
864 | * 查询剩余待分金额API
865 | * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_6.shtml
866 | */
867 | public async query_profitsharing_amounts(transaction_id: string): Promise {
868 | if (!transaction_id) throw new Error('缺少transaction_id');
869 | const url = `https://api.mch.weixin.qq.com/v3/profitsharing/transactions/${transaction_id}/amounts`;
870 | const authorization = this.buildAuthorization('GET', url);
871 | const headers = this.getHeaders(authorization);
872 | return await this.httpService.get(url, headers);
873 | }
874 | /**
875 | * 添加分账接收方API
876 | * @documentation https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_8.shtml
877 | */
878 | public async profitsharing_receivers_add(
879 | params: ProfitSharing.ProfitSharingReceiversAdd.Input,
880 | ): Promise {
881 | const url = 'https://api.mch.weixin.qq.com/v3/profitsharing/receivers/add';
882 | // 请求参数
883 | const _params = {
884 | appid: this.appid,
885 | ...params,
886 | };
887 |
888 | const serial_no = _params?.wx_serial_no;
889 | delete _params.wx_serial_no;
890 | const authorization = this.buildAuthorization('POST', url, _params);
891 |
892 | const headers = this.getHeaders(authorization, { 'Wechatpay-Serial': serial_no || this.serial_no, 'Content-Type': 'application/json' });
893 | return await this.httpService.post(url, _params, headers);
894 | }
895 | /**
896 | * 删除分账接收方API
897 | * @documentation https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_9.shtml
898 | */
899 | public async profitsharing_receivers_delete(
900 | params: ProfitSharing.ProfitSharingReceiversDelete.Input,
901 | ): Promise {
902 | const url = 'https://api.mch.weixin.qq.com/v3/profitsharing/receivers/delete';
903 | // 请求参数
904 | const _params = {
905 | appid: this.appid,
906 | ...params,
907 | };
908 |
909 | const authorization = this.buildAuthorization('POST', url, _params);
910 |
911 | const headers = this.getHeaders(authorization, { 'Content-Type': 'application/json' });
912 | return await this.httpService.post(url, _params, headers);
913 | }
914 | /**
915 | * 申请分账账单API
916 | * @documentation https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_11.shtml
917 | */
918 | public async profitsharing_bills(bill_date: string, tar_type?: string): Promise {
919 | if (!bill_date) throw new Error('缺少bill_date');
920 | let url = `https://api.mch.weixin.qq.com/v3/profitsharing/bills`;
921 | url = url + this.objectToQueryString({ bill_date, ...(tar_type && { tar_type }) });
922 | const authorization = this.buildAuthorization('GET', url);
923 | const headers = this.getHeaders(authorization);
924 | return await this.httpService.get(url, headers);
925 | }
926 | //#endregion 分账
927 | public async upload_images(pic_buffer: Buffer, filename: string): Promise {
928 | //meta信息
929 | const fileinfo = {
930 | filename,
931 | sha256: '',
932 | };
933 | const sign = crypto.createHash('sha256');
934 | sign.update(pic_buffer);
935 | fileinfo.sha256 = sign.digest('hex');
936 |
937 | const url = '/v3/merchant/media/upload';
938 |
939 | const authorization = this.buildAuthorization('POST', url, fileinfo);
940 |
941 | const headers = this.getHeaders(authorization, { 'Content-Type': 'multipart/form-data;boundary=boundary' });
942 | return await this.httpService.post(
943 | url,
944 | {
945 | fileinfo,
946 | pic_buffer,
947 | },
948 | headers,
949 | );
950 | }
951 | }
952 |
953 | export = Pay;
954 |
--------------------------------------------------------------------------------
/lib/base.ts:
--------------------------------------------------------------------------------
1 | import request from 'superagent';
2 | import { Output } from './interface-v2';
3 |
4 | export class Base {
5 | protected userAgent = '127.0.0.1'; // User-Agent
6 |
7 | /**
8 | * get 请求参数处理
9 | * @param object query 请求参数
10 | * @param exclude 需要排除的字段
11 | * @returns
12 | */
13 | protected objectToQueryString(object: Record, exclude: string[] = []): string {
14 | let str = Object.keys(object)
15 | .filter(key => !exclude.includes(key))
16 | .map(key => {
17 | return encodeURIComponent(key) + '=' + encodeURIComponent(object[key]);
18 | })
19 | .join('&');
20 | if (str) str = '?' + str;
21 | return str || '';
22 | }
23 | /**
24 | * 获取请求头
25 | * @param authorization
26 | */
27 | protected getHeaders(authorization: string, headers = {}) {
28 | return {
29 | ...headers,
30 | Accept: 'application/json',
31 | 'User-Agent': this.userAgent,
32 | Authorization: authorization,
33 | 'Accept-Encoding': 'gzip',
34 | };
35 | }
36 | /**
37 | * post 请求
38 | * @param url 请求接口
39 | * @param params 请求参数
40 | * @deprecated 弃用
41 | */
42 | protected async postRequest(url: string, params: Record, authorization: string): Promise> {
43 | try {
44 | const result = await request
45 | .post(url)
46 | .send(params)
47 | .set({
48 | Accept: 'application/json',
49 | 'Content-Type': 'application/json',
50 | 'User-Agent': this.userAgent,
51 | Authorization: authorization,
52 | 'Accept-Encoding': 'gzip',
53 | });
54 | return {
55 | status: result.status,
56 | ...result.body,
57 | };
58 | } catch (error) {
59 | const err = JSON.parse(JSON.stringify(error));
60 | return {
61 | status: err.status,
62 | errRaw: err,
63 | ...(err?.response?.text && JSON.parse(err?.response?.text)),
64 | };
65 | }
66 | }
67 | /**
68 | * post 请求 V2
69 | * @param url 请求接口
70 | * @param params 请求参数
71 | * @deprecated 弃用
72 | */
73 | protected async postRequestV2(url: string, params: Record, authorization: string, headers = {}): Promise