├── .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 |

wechatpay-node-v3

5 |

6 | 7 | [![OSCS Status](https://www.oscs1024.com/platform/badge/klover2/wechatpay-node-v3-ts.svg?size=small)](https://www.oscs1024.com/project/klover2/wechatpay-node-v3-ts?ref=badge_small) 8 | [![npm version](https://badgen.net/npm/v/wechatpay-node-v3)](https://www.npmjs.com/package/wechatpay-node-v3) 9 | [![npm downloads](https://badgen.net/npm/dm/wechatpay-node-v3)](https://www.npmjs.com/package/wechatpay-node-v3) 10 | [![contributors](https://img.shields.io/github/contributors/klover2/wechatpay-node-v3-ts)](https://github.com/klover2/wechatpay-node-v3-ts/graphs/contributors) 11 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 { 74 | try { 75 | const result = await request 76 | .post(url) 77 | .send(params) 78 | .set({ 79 | ...headers, 80 | Accept: 'application/json', 81 | 'Content-Type': 'application/json', 82 | 'User-Agent': this.userAgent, 83 | Authorization: authorization, 84 | 'Accept-Encoding': 'gzip', 85 | }); 86 | return { 87 | status: result.status, 88 | data: result.body, 89 | }; 90 | } catch (error) { 91 | const err = JSON.parse(JSON.stringify(error)); 92 | return { 93 | status: err.status as number, 94 | errRaw: err, 95 | error: err?.response?.text, 96 | }; 97 | } 98 | } 99 | /** 100 | * get 请求 101 | * @param url 请求接口 102 | * @param query 请求参数 103 | * @deprecated 弃用 104 | */ 105 | protected async getRequest(url: string, authorization: string, query: Record = {}): Promise> { 106 | try { 107 | const result = await request 108 | .get(url) 109 | .query(query) 110 | .set({ 111 | Accept: 'application/json', 112 | 'User-Agent': this.userAgent, 113 | Authorization: authorization, 114 | 'Accept-Encoding': 'gzip', 115 | }); 116 | 117 | let data = {}; 118 | switch (result.type) { 119 | case 'application/json': 120 | data = { 121 | status: result.status, 122 | ...result.body, 123 | }; 124 | break; 125 | case 'text/plain': 126 | data = { 127 | status: result.status, 128 | data: result.text, 129 | }; 130 | break; 131 | case 'application/x-gzip': 132 | data = { 133 | status: result.status, 134 | data: result.body, 135 | }; 136 | break; 137 | default: 138 | data = { 139 | status: result.status, 140 | ...result.body, 141 | }; 142 | } 143 | return data; 144 | } catch (error) { 145 | const err = JSON.parse(JSON.stringify(error)); 146 | return { 147 | status: err.status, 148 | errRaw: err, 149 | ...(err?.response?.text && JSON.parse(err?.response?.text)), 150 | }; 151 | } 152 | } 153 | /** 154 | * get 请求 v2 155 | * @param url 请求接口 156 | * @param query 请求参数 157 | * @deprecated 弃用 158 | */ 159 | protected async getRequestV2(url: string, authorization: string, query: Record = {}): Promise { 160 | try { 161 | const result = await request 162 | .get(url) 163 | .query(query) 164 | .set({ 165 | Accept: 'application/json', 166 | 'User-Agent': this.userAgent, 167 | Authorization: authorization, 168 | 'Accept-Encoding': 'gzip', 169 | }); 170 | 171 | let data: any = {}; 172 | if (result.type === 'text/plain') { 173 | data = { 174 | status: result.status, 175 | data: result.text, 176 | }; 177 | } else { 178 | data = { 179 | status: result.status, 180 | data: result.body, 181 | }; 182 | } 183 | 184 | return data; 185 | } catch (error) { 186 | const err = JSON.parse(JSON.stringify(error)); 187 | return { 188 | status: err.status, 189 | errRaw: err, 190 | error: err?.response?.text, 191 | }; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/combine_interface.ts: -------------------------------------------------------------------------------- 1 | // H5场景信息 2 | interface Ih5Info { 3 | type: string; 4 | app_name: string; 5 | app_url?: string; 6 | bundle_id?: string; 7 | package_name?: string; 8 | } 9 | interface IsceneInfoNative { 10 | device_id?: string; 11 | payer_client_ip: string; 12 | } 13 | interface IsceneInfoH5 { 14 | payer_client_ip: string; 15 | device_id: string; 16 | h5_info: Ih5Info; 17 | } 18 | interface Iamount { 19 | total_amount: number; 20 | currency: string; 21 | } 22 | interface IsettleInfo { 23 | profit_sharing?: boolean; 24 | subsidy_amount?: number; 25 | } 26 | interface IsubOrders { 27 | mchid: string; 28 | attach: string; 29 | amount: Iamount; 30 | out_trade_no: string; 31 | sub_mchid?: string; // 直连商户不用传二级商户号。 32 | description: string; 33 | settle_info?: IsettleInfo; 34 | } 35 | interface IcombinePayerInfo { 36 | openid: string; 37 | } 38 | 39 | // 抛出 40 | export interface IcombineH5 { 41 | combine_appid?: string; 42 | combine_mchid?: string; 43 | combine_out_trade_no: string; 44 | scene_info: IsceneInfoH5; 45 | time_start?: string; 46 | time_expire?: string; 47 | notify_url: string; 48 | sub_orders: IsubOrders[]; 49 | } 50 | export interface IcombineNative { 51 | combine_appid?: string; 52 | combine_mchid?: string; 53 | combine_out_trade_no: string; 54 | scene_info: IsceneInfoNative; 55 | time_start?: string; 56 | time_expire?: string; 57 | notify_url: string; 58 | sub_orders: IsubOrders[]; 59 | } 60 | export interface IcombineApp { 61 | combine_appid?: string; 62 | combine_mchid?: string; 63 | combine_out_trade_no: string; 64 | scene_info: IsceneInfoNative; 65 | time_start?: string; 66 | time_expire?: string; 67 | notify_url: string; 68 | sub_orders: IsubOrders[]; 69 | combine_payer_info?: IcombinePayerInfo; 70 | } 71 | export interface IcombineJsapi { 72 | combine_appid?: string; 73 | combine_mchid?: string; 74 | combine_out_trade_no: string; 75 | scene_info: IsceneInfoNative; 76 | time_start?: string; 77 | time_expire?: string; 78 | notify_url: string; 79 | sub_orders: IsubOrders[]; 80 | combine_payer_info: IcombinePayerInfo; 81 | } 82 | export interface IcloseSubOrders { 83 | mchid: string; 84 | out_trade_no: string; 85 | sub_mchid?: string; 86 | } 87 | -------------------------------------------------------------------------------- /lib/interface-v2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 统一返回格式 3 | */ 4 | export interface Output { 5 | /** http 状态 */ 6 | status: number; 7 | /** 报错时返回的 text */ 8 | error?: any; 9 | /** 原错误信息 */ 10 | errRaw?: any; 11 | /** body 返回参数 */ 12 | data?: any; 13 | } 14 | 15 | /** 16 | * 发起商家转账零钱 17 | */ 18 | export declare namespace BatchesTransfer { 19 | export interface TransferDetailList { 20 | /** 商家明细单号 */ 21 | out_detail_no: string; 22 | /** 转账金额 */ 23 | transfer_amount: number; 24 | /** 转账备注 */ 25 | transfer_remark: string; 26 | /** 用户在直连商户应用下的用户标示 */ 27 | openid: string; 28 | /** 收款用户姓名 */ 29 | user_name?: string; 30 | } 31 | 32 | /** 33 | * 发起商家转账API 请求参数 34 | */ 35 | export interface Input { 36 | /** 直连商户的appid -不传 默认使用初始化数据 */ 37 | appid?: string; 38 | /** 商家批次单号 */ 39 | out_batch_no: string; 40 | /** 批次名称 */ 41 | batch_name: string; 42 | /** 批次备注 */ 43 | batch_remark: string; 44 | /** 转账总金额 */ 45 | total_amount: number; 46 | /** 转账总笔数 */ 47 | total_num: number; 48 | /** 转账明细列表 */ 49 | transfer_detail_list: TransferDetailList[]; 50 | /** 转账场景ID */ 51 | transfer_scene_id?: string; 52 | /** 微信平台证书序列号-Wechatpay-Serial(当有敏感信息加密时,需要当前参数) */ 53 | wx_serial_no?: string; 54 | } 55 | 56 | export interface DataOutput { 57 | out_batch_no: string; 58 | batch_id: string; 59 | create_time: Date; 60 | } 61 | 62 | /** 63 | * 发起商家转账API 返回参数 64 | */ 65 | export interface IOutput extends Output { 66 | data?: DataOutput; 67 | } 68 | 69 | /** 70 | * 转账批次单 71 | */ 72 | export interface QueryTransferBatch { 73 | /** 微信支付分配的商户号 */ 74 | mchid: string; 75 | /** 商户系统内部的商家批次单号,在商户系统内部唯一 */ 76 | out_batch_no: string; 77 | /** 微信批次单号,微信商家转账系统返回的唯一标识 */ 78 | batch_id: string; 79 | /** 申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid) */ 80 | appid: string; 81 | /** 批次状态 */ 82 | batch_status: string; 83 | /** 批次类型 */ 84 | batch_type: string; 85 | /** 该笔批量转账的名称 */ 86 | batch_name: string; 87 | /** 转账说明,UTF8编码,最多允许32个字符 */ 88 | batch_remark: string; 89 | /** 批次关闭原因 */ 90 | close_reason?: string; 91 | /** 转账总金额 */ 92 | total_amount: number; 93 | /** 转账总笔数 */ 94 | total_num: number; 95 | /** 批次创建时间 */ 96 | create_time?: Date; 97 | /** 批次更新时间 */ 98 | update_time?: Date; 99 | /** 转账成功金额 */ 100 | success_amount?: number; 101 | /** 转账成功笔数 */ 102 | success_num?: number; 103 | /** 转账失败金额 */ 104 | fail_amount?: number; 105 | /** 转账失败笔数 */ 106 | fail_num?: number; 107 | } 108 | 109 | /** 110 | * 转账明细单列表 111 | */ 112 | export interface QueryTransferDetailList { 113 | /** 微信明细单号 */ 114 | detail_id: string; 115 | /** 商家明细单号 */ 116 | out_detail_no: string; 117 | /** 明细状态 */ 118 | detail_status: string; 119 | } 120 | 121 | /** 122 | * 商家批次单号查询批次单API 123 | */ 124 | export namespace QueryBatchesTransferList { 125 | /** 126 | * 商家批次单号查询参数 127 | */ 128 | export interface Input { 129 | /**商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 */ 130 | out_batch_no: string; 131 | /**商户可选择是否查询指定状态的转账明细单,当转账批次单状态为“FINISHED”(已完成)时,才会返回满足条件的转账明细单 */ 132 | need_query_detail: boolean; 133 | /**该次请求资源(转账明细单)的起始位置,从0开始,默认值为0 */ 134 | offset?: number; 135 | /**该次请求可返回的最大资源(转账明细单)条数,最小20条,最大100条,不传则默认20条。不足20条按实际条数返回 */ 136 | limit?: number; 137 | /**查询指定状态的转账明细单,当need_query_detail为true时,该字段必填 */ 138 | detail_status?: 'ALL' | 'SUCCESS' | 'FAIL'; 139 | } 140 | 141 | export interface IOutput extends Output { 142 | data?: { 143 | limit: number; 144 | offset: number; 145 | transfer_batch: QueryTransferBatch; 146 | transfer_detail_list: QueryTransferDetailList[]; 147 | }; 148 | } 149 | } 150 | 151 | /** 152 | * 微信批次单号查询批次单API 153 | */ 154 | export namespace QueryBatchesTransferByWx { 155 | export interface Input { 156 | /** 微信批次单号,微信商家转账系统返回的唯一标识 */ 157 | batch_id: string; 158 | /**商户可选择是否查询指定状态的转账明细单,当转账批次单状态为“FINISHED”(已完成)时,才会返回满足条件的转账明细单 */ 159 | need_query_detail: boolean; 160 | /**该次请求资源(转账明细单)的起始位置,从0开始,默认值为0 */ 161 | offset?: number; 162 | /**该次请求可返回的最大资源(转账明细单)条数,最小20条,最大100条,不传则默认20条。不足20条按实际条数返回 */ 163 | limit?: number; 164 | /**查询指定状态的转账明细单,当need_query_detail为true时,该字段必填 */ 165 | detail_status?: 'ALL' | 'SUCCESS' | 'FAIL'; 166 | } 167 | 168 | export interface IOutput extends Output { 169 | data?: { 170 | limit: number; 171 | offset: number; 172 | transfer_batch: QueryTransferBatch; 173 | transfer_detail_list: QueryTransferDetailList[]; 174 | }; 175 | } 176 | } 177 | 178 | /** 179 | * 微信明细单号查询明细单API 180 | */ 181 | export namespace QueryBatchesTransferDetailByWx { 182 | export interface Input { 183 | /** 微信批次单号 */ 184 | batch_id: string; 185 | /** 微信明细单号 */ 186 | detail_id: string; 187 | } 188 | 189 | export interface DetailOutput { 190 | /** 商户号 */ 191 | mchid: string; 192 | /** 商家批次单号 */ 193 | out_batch_no: string; 194 | /** 微信批次单号 */ 195 | batch_id: string; 196 | /** 直连商户的appid */ 197 | appid: string; 198 | /** 商家明细单号 */ 199 | out_detail_no: string; 200 | /** 微信明细单号 */ 201 | detail_id: string; 202 | /** 明细状态 */ 203 | detail_status: string; 204 | /** 转账金额 */ 205 | transfer_amount: number; 206 | /** 转账备注 */ 207 | transfer_remark: string; 208 | /** 明细失败原因 */ 209 | fail_reason?: string; 210 | /** 用户在直连商户应用下的用户标示 */ 211 | openid: string; 212 | /** 收款用户姓名 */ 213 | user_name?: string; 214 | /** 转账发起时间 */ 215 | initiate_time: Date; 216 | /** 明细更新时间 */ 217 | update_time: Date; 218 | } 219 | 220 | export interface IOutput extends Output { 221 | data?: DetailOutput; 222 | } 223 | } 224 | 225 | /** 226 | * 商家明细单号查询明细单API 227 | */ 228 | export namespace QueryBatchesTransferDetail { 229 | export interface Input { 230 | /** 商家明细单号 */ 231 | out_detail_no: string; 232 | /** 商家批次单号 */ 233 | out_batch_no: string; 234 | } 235 | 236 | export interface DetailOutput { 237 | /** 商户号 */ 238 | mchid: string; 239 | /** 商家批次单号 */ 240 | out_batch_no: string; 241 | /** 微信批次单号 */ 242 | batch_id: string; 243 | /** 直连商户的appid */ 244 | appid: string; 245 | /** 商家明细单号 */ 246 | out_detail_no: string; 247 | /** 微信明细单号 */ 248 | detail_id: string; 249 | /** 明细状态 */ 250 | detail_status: string; 251 | /** 转账金额 */ 252 | transfer_amount: number; 253 | /** 转账备注 */ 254 | transfer_remark: string; 255 | /** 明细失败原因 */ 256 | fail_reason?: string; 257 | /** 用户在直连商户应用下的用户标示 */ 258 | openid: string; 259 | /** 收款用户姓名 */ 260 | user_name?: string; 261 | /** 转账发起时间 */ 262 | initiate_time: Date; 263 | /** 明细更新时间 */ 264 | update_time: Date; 265 | } 266 | 267 | export interface IOutput extends Output { 268 | data?: DetailOutput; 269 | } 270 | } 271 | } 272 | 273 | /** 274 | * 分账 275 | */ 276 | export declare namespace ProfitSharing { 277 | export interface ProfitSharingOrdersReceiversOutput { 278 | /** 分账接收方类型 */ 279 | type: 'MERCHANT_ID' | 'PERSONAL_OPENID'; 280 | /** 分账接收方账号 */ 281 | account: string; 282 | /** 分账金额 */ 283 | amount: number; 284 | /** 分账描述 */ 285 | description: string; 286 | /** 分账结果 */ 287 | result: string; 288 | /** 分账失败原因 */ 289 | fail_reason: string; 290 | /** 分账创建时间 */ 291 | create_time: string; 292 | /** 分账完成时间 */ 293 | finish_time: string; 294 | /** 分账明细单号 */ 295 | detail_id: string; 296 | } 297 | 298 | export interface ProfitSharingOrderDetailOutput { 299 | /** 微信订单号 */ 300 | transaction_id: string; 301 | /** 商户分账单号 */ 302 | out_order_no: string; 303 | /** 微信分账单号 */ 304 | order_id: string; 305 | /** 分账单状态 */ 306 | state: string; 307 | /** 分账接收方列表 */ 308 | receivers?: ProfitSharingOrdersReceiversOutput[]; 309 | } 310 | 311 | /** 312 | * 请求分账 313 | */ 314 | export namespace CreateProfitSharingOrders { 315 | export interface Input { 316 | /** 微信分配的商户appid 不传 使用默认 */ 317 | appid?: string; 318 | /** 微信订单号 */ 319 | transaction_id: string; 320 | /** 商户分账单号 */ 321 | out_order_no: string; 322 | /** 分账接收方列表 */ 323 | receivers: CreateProfitSharingOrdersReceivers[]; 324 | /** 是否解冻剩余未分资金 */ 325 | unfreeze_unsplit: boolean; 326 | /** 当有敏感信息加密 必填 */ 327 | wx_serial_no?: string; 328 | } 329 | 330 | export interface CreateProfitSharingOrdersReceivers { 331 | /** 分账接收方类型 */ 332 | type: 'MERCHANT_ID' | 'PERSONAL_OPENID'; 333 | /** 分账接收方账号 */ 334 | account: string; 335 | /** 分账个人接收方姓名 */ 336 | name?: string; 337 | /** 分账金额 */ 338 | amount: number; 339 | /** 分账描述 */ 340 | description: string; 341 | } 342 | 343 | export interface IOutput extends Output { 344 | data?: ProfitSharingOrderDetailOutput; 345 | } 346 | } 347 | 348 | /** 349 | * 分账回退 350 | */ 351 | export namespace ProfitSharingReturnOrders { 352 | export interface Input { 353 | /** 微信分账单号 */ 354 | order_id?: string; 355 | /** 商户分账单号 */ 356 | out_order_no?: string; 357 | /** 商户回退单号 */ 358 | out_return_no: string; 359 | /** 回退商户号 */ 360 | return_mchid: string; 361 | /** 回退金额 */ 362 | amount: number; 363 | /** 回退描述 */ 364 | description: string; 365 | } 366 | 367 | export interface IOutput extends Output { 368 | data?: IDetail; 369 | } 370 | export interface IDetail { 371 | /** 微信分账单号 */ 372 | order_id: string; 373 | /** 商户分账单号 */ 374 | out_order_no: string; 375 | /** 商户回退单号 */ 376 | out_return_no: string; 377 | /** 微信回退单号 */ 378 | return_id: string; 379 | /** 回退商户号 */ 380 | return_mchid: string; 381 | /** 回退金额 */ 382 | amount: number; 383 | /** 回退描述 */ 384 | description: string; 385 | /** 回退结果 */ 386 | result: string; 387 | /** 失败原因 */ 388 | fail_reason: string; 389 | /** 创建时间 */ 390 | create_time: string; 391 | /** 完成时间 */ 392 | finish_time: string; 393 | } 394 | } 395 | 396 | /** 397 | * 解冻剩余资金 398 | */ 399 | export namespace ProfitsharingOrdersUnfreeze { 400 | export interface Input { 401 | /** 微信订单号 */ 402 | transaction_id: string; 403 | /** 商户分账单号 */ 404 | out_order_no: string; 405 | /** 分账描述 */ 406 | description: string; 407 | } 408 | 409 | export type IOutput = ProfitSharing.CreateProfitSharingOrders.IOutput; 410 | } 411 | 412 | export namespace QueryProfitSharingAmounts { 413 | export interface IOutput extends Output { 414 | data?: { 415 | transaction_id: string; 416 | unsplit_amount: number; 417 | }; 418 | } 419 | } 420 | 421 | export namespace ProfitSharingReceiversAdd { 422 | export interface Input { 423 | /** 应用ID */ 424 | appid?: string; 425 | /** 分账接收方类型 */ 426 | type: string; 427 | /** 分账接收方账号 */ 428 | account: string; 429 | /** 分账个人接收方姓名 */ 430 | name?: string; 431 | /** 与分账方的关系类型 */ 432 | relation_type: string; 433 | /** 自定义的分账关系 */ 434 | custom_relation?: string; 435 | /** 当有敏感信息加密 必填 */ 436 | wx_serial_no?: string; 437 | } 438 | 439 | export interface IOutput extends Output { 440 | data?: { 441 | /** 分账接收方类型 */ 442 | type: string; 443 | /** 分账接收方账号 */ 444 | account: number; 445 | /** 分账个人接收方姓名 */ 446 | name?: string; 447 | /** 与分账方的关系类型 */ 448 | relation_type: string; 449 | /** 自定义的分账关系 */ 450 | custom_relation?: string; 451 | }; 452 | } 453 | } 454 | 455 | export namespace ProfitSharingReceiversDelete { 456 | export interface Input { 457 | /** 应用ID */ 458 | appid?: string; 459 | /** 分账接收方类型 */ 460 | type: string; 461 | /** 分账接收方账号 */ 462 | account: string; 463 | } 464 | 465 | export interface IOutput extends Output { 466 | data?: { 467 | /** 分账接收方类型 */ 468 | type: string; 469 | /** 分账接收方账号 */ 470 | account: string; 471 | }; 472 | } 473 | } 474 | 475 | export namespace ProfitSharingBills { 476 | export interface IOutput extends Output { 477 | data?: { 478 | /** 哈希类型 */ 479 | hash_type: string; 480 | /** 哈希值 */ 481 | hash_value: string; 482 | /** 账单下载地址 */ 483 | download_url: string; 484 | }; 485 | } 486 | } 487 | } 488 | 489 | export declare namespace Refunds { 490 | export interface DataOutput { 491 | refund_id: string; 492 | out_refund_no: string; 493 | transaction_id: string; 494 | out_trade_no: string; 495 | channel: string; 496 | user_received_account: string; 497 | success_time: string; 498 | create_time: string; 499 | status: string; 500 | funds_account: string; 501 | amount: { 502 | total: number; 503 | refund: number; 504 | from: { account: string; amount: number }[]; 505 | payer_total: number; 506 | payer_refund: number; 507 | settlement_refund: number; 508 | settlement_total: number; 509 | discount_refund: number; 510 | currency: string; 511 | refund_fee: number; 512 | }; 513 | promotion_detail: { 514 | promotion_id: string; 515 | scope: string; 516 | type: string; 517 | amount: number; 518 | refund_amount: number; 519 | goods_detail: { 520 | merchant_goods_id: string; 521 | wechatpay_goods_id: string; 522 | goods_name: string; 523 | unit_price: number; 524 | refund_amount: number; 525 | refund_quantity: number; 526 | }[]; 527 | }[]; 528 | } 529 | 530 | export interface IOutput extends Output { 531 | data?: DataOutput; 532 | } 533 | } 534 | 535 | export declare namespace FindRefunds { 536 | export interface DataOutput { 537 | refund_id: string; 538 | out_refund_no: string; 539 | transaction_id: string; 540 | out_trade_no: string; 541 | channel: string; 542 | user_received_account: string; 543 | success_time: string; 544 | create_time: string; 545 | status: string; 546 | funds_account: string; 547 | amount: { 548 | total: number; 549 | refund: number; 550 | from: { account: string; amount: number }[]; 551 | payer_total: number; 552 | payer_refund: number; 553 | settlement_refund: number; 554 | settlement_total: number; 555 | discount_refund: number; 556 | currency: string; 557 | refund_fee: number; 558 | }; 559 | promotion_detail: { 560 | promotion_id: string; 561 | scope: string; 562 | type: string; 563 | amount: number; 564 | refund_amount: number; 565 | goods_detail: { 566 | merchant_goods_id: string; 567 | wechatpay_goods_id: string; 568 | goods_name: string; 569 | unit_price: number; 570 | refund_amount: number; 571 | refund_quantity: number; 572 | }[]; 573 | }[]; 574 | } 575 | 576 | export interface IOutput extends Output { 577 | data?: DataOutput; 578 | } 579 | } 580 | 581 | export declare namespace UploadImages { 582 | export interface IOutput extends Output { 583 | data?: { media_id: string }; 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /lib/interface.ts: -------------------------------------------------------------------------------- 1 | // 订单金额信息 2 | interface Iamount { 3 | total: number; 4 | currency?: string; 5 | } 6 | // 优惠功能 7 | interface Idetail { 8 | cost_price?: number; 9 | invoice_id?: string; 10 | goods_detail?: IgoodsDetail[]; 11 | } 12 | // 单品列表信息 13 | interface IgoodsDetail { 14 | merchant_goods_id: string; 15 | wechatpay_goods_id?: string; 16 | goods_name?: string; 17 | quantity: number; 18 | unit_price: number; 19 | } 20 | // 支付者 21 | interface Ipayer { 22 | openid: string; 23 | } 24 | // 支付场景描述 25 | interface IsceneInfoH5 { 26 | payer_client_ip: string; 27 | device_id?: string; 28 | store_info?: IstoreInfo; 29 | h5_info: Ih5Info; 30 | } 31 | interface IsceneInfoNative { 32 | payer_client_ip: string; 33 | device_id?: string; 34 | store_info?: IstoreInfo; 35 | } 36 | // 商户门店信息 37 | interface IstoreInfo { 38 | id: string; 39 | name?: string; 40 | area_code?: string; 41 | address?: string; 42 | } 43 | // H5场景信息 44 | interface Ih5Info { 45 | type: string; 46 | app_name: string; 47 | app_url?: string; 48 | bundle_id?: string; 49 | package_name?: string; 50 | } 51 | 52 | // 抛出 53 | export interface Ioptions { 54 | userAgent?: string; 55 | authType?: string; 56 | key?: string; 57 | serial_no?: string; 58 | } 59 | export interface Ipay { 60 | appid: string; // 直连商户申请的公众号或移动应用appid。 61 | mchid: string; // 商户号 62 | serial_no?: string; // 证书序列号 63 | publicKey: Buffer; // 公钥 64 | privateKey: Buffer; // 密钥 65 | authType?: string; // 认证类型,目前为WECHATPAY2-SHA256-RSA2048 66 | userAgent?: string; 67 | key?: string; 68 | } 69 | export interface Ih5 { 70 | appid?: string; 71 | mchid?: string; 72 | description: string; 73 | out_trade_no: string; 74 | time_expire?: string; 75 | attach?: string; 76 | notify_url: string; 77 | goods_tag?: string; 78 | amount: Iamount; 79 | detail?: Idetail; 80 | scene_info: IsceneInfoH5; 81 | } 82 | export interface Inative { 83 | appid?: string; 84 | mchid?: string; 85 | description: string; 86 | out_trade_no: string; 87 | time_expire?: string; 88 | attach?: string; 89 | notify_url: string; 90 | goods_tag?: string; 91 | support_fapiao?: boolean; 92 | amount: Iamount; 93 | detail?: Idetail; 94 | scene_info?: IsceneInfoNative; 95 | settle_info?: { profit_sharing?: boolean }; 96 | } 97 | export interface Ijsapi { 98 | appid?: string; 99 | mchid?: string; 100 | description: string; 101 | out_trade_no: string; 102 | time_expire?: string; 103 | attach?: string; 104 | notify_url: string; 105 | goods_tag?: string; 106 | amount: Iamount; 107 | payer: Ipayer; 108 | detail?: Idetail; 109 | scene_info?: IsceneInfoNative; 110 | } 111 | export interface Iapp { 112 | appid?: string; 113 | mchid?: string; 114 | description: string; 115 | out_trade_no: string; 116 | time_expire?: string; 117 | attach?: string; 118 | notify_url: string; 119 | goods_tag?: string; 120 | amount: Iamount; 121 | detail?: Idetail; 122 | scene_info?: IsceneInfoNative; 123 | } 124 | export interface Iquery1 { 125 | transaction_id: string; 126 | out_trade_no?: string; 127 | } 128 | export interface Iquery2 { 129 | transaction_id?: string; 130 | out_trade_no: string; 131 | } 132 | export interface Itradebill { 133 | bill_date: string; 134 | sub_mchid?: string; 135 | bill_type: string; 136 | tar_type?: string; 137 | } 138 | export interface Ifundflowbill { 139 | bill_date: string; 140 | account_type: string; 141 | tar_type?: string; 142 | } 143 | export interface Irefunds { 144 | out_refund_no: string; 145 | reason?: string; 146 | notify_url?: string; 147 | funds_account?: string; 148 | amount: IRamount; 149 | goods_detail?: IRgoodsDetail[]; 150 | } 151 | export interface Irefunds1 extends Irefunds { 152 | transaction_id: string; 153 | out_trade_no?: string; 154 | } 155 | export interface Irefunds2 extends Irefunds { 156 | transaction_id?: string; 157 | out_trade_no: string; 158 | } 159 | interface IRamount { 160 | total: number; 161 | currency: string; 162 | refund: number; 163 | } 164 | interface IRgoodsDetail { 165 | merchant_goods_id: string; 166 | wechatpay_goods_id?: string; 167 | goods_name?: string; 168 | refund_quantity: number; 169 | unit_price: number; 170 | refund_amount: number; 171 | } 172 | 173 | /** 174 | * 证书信息 175 | */ 176 | export interface ICertificates { 177 | effective_time: string; 178 | expire_time: string; 179 | serial_no: string; 180 | publicKey?: string; 181 | encrypt_certificate: { 182 | algorithm: string; 183 | associated_data: string; 184 | ciphertext: string; 185 | nonce: string; 186 | }; 187 | } 188 | -------------------------------------------------------------------------------- /lib/pay-request.interface.ts: -------------------------------------------------------------------------------- 1 | import { Output } from './interface-v2'; 2 | 3 | export interface IPayRequest { 4 | /** 5 | * post 请求 6 | * @param url url 7 | * @param params body 8 | * @param headers 请求头 9 | */ 10 | post(url: string, params: Record, headers: Record): Promise; 11 | /** 12 | * 上传文件 13 | * @param url url 14 | * @param params body 15 | * @param headers 请求头 16 | */ 17 | upload(url: string, params: Record, headers: Record): Promise; 18 | /** 19 | * get 请求 20 | * @param url url 21 | * @param headers 请求头 22 | */ 23 | get(url: string, headers: Record): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /lib/pay-request.ts: -------------------------------------------------------------------------------- 1 | import { Output } from './interface-v2'; 2 | import { IPayRequest } from './pay-request.interface'; 3 | import request from 'superagent'; 4 | 5 | export class PayRequest implements IPayRequest { 6 | async upload(url: string, params: Record, headers: Record): Promise { 7 | try { 8 | const result = await request 9 | .post(url) 10 | .send(params) 11 | .attach('file', params.pic_buffer, { 12 | filename: '72fe0092be0cf9dd8420579cc954fb4e.jpg', 13 | contentType: 'image/jpg', 14 | }) 15 | .field('meta', JSON.stringify(params.fileinfo)); 16 | return { 17 | status: result.status, 18 | data: result.body, 19 | }; 20 | } catch (error) { 21 | const err = JSON.parse(JSON.stringify(error)); 22 | return { 23 | status: err.status as number, 24 | errRaw: err, 25 | error: err?.response?.text, 26 | }; 27 | } 28 | } 29 | async post(url: string, params: Record, headers: Record): Promise { 30 | try { 31 | const result = await request 32 | .post(url) 33 | .send(params) 34 | .set(headers); 35 | return { 36 | status: result.status, 37 | data: result.body, 38 | }; 39 | } catch (error) { 40 | const err = JSON.parse(JSON.stringify(error)); 41 | return { 42 | status: err.status as number, 43 | errRaw: err, 44 | error: err?.response?.text, 45 | }; 46 | } 47 | } 48 | 49 | async get(url: string, headers: Record): Promise { 50 | try { 51 | const result = await request.get(url).set(headers); 52 | 53 | let data: any = {}; 54 | if (result.type === 'text/plain') { 55 | data = { 56 | status: result.status, 57 | data: result.text, 58 | }; 59 | } else { 60 | data = { 61 | status: result.status, 62 | data: result.body, 63 | }; 64 | } 65 | 66 | return data; 67 | } catch (error) { 68 | const err = JSON.parse(JSON.stringify(error)); 69 | return { 70 | status: err.status, 71 | errRaw: err, 72 | error: err?.response?.text, 73 | }; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechatpay-node-v3", 3 | "version": "2.2.1", 4 | "description": "微信支付文档v3", 5 | "keywords": [ 6 | "node", 7 | "wechat", 8 | "微信支付", 9 | "微信提现" 10 | ], 11 | "homepage": "https://github.com/klover2/wechatpay-node-v3-ts#readme", 12 | "bugs": { 13 | "url": "https://github.com/klover2/wechatpay-node-v3-ts/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/klover2/wechatpay-node-v3-ts.git" 18 | }, 19 | "license": "MIT", 20 | "author": "klover", 21 | "main": "dist/index.js", 22 | "types": "dist/index.d.ts", 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "build": "rm -rf ./dist && tsc", 28 | "release": "npm publish --registry=https://registry.npmjs.org/", 29 | "test": "echo \"Error: no test specified\" && exit 1" 30 | }, 31 | "dependencies": { 32 | "@fidm/x509": "1.2.1", 33 | "superagent": "8.0.6" 34 | }, 35 | "devDependencies": { 36 | "@nestjs/common": "8.2.4", 37 | "@types/node": "14.14.12", 38 | "@types/superagent": "4.1.10", 39 | "@typescript-eslint/eslint-plugin": "3.0.2", 40 | "@typescript-eslint/parser": "3.0.2", 41 | "eslint": "7.1.0", 42 | "eslint-config-prettier": "6.10.0", 43 | "eslint-plugin-import": "2.20.1", 44 | "prettier": "1.19.1", 45 | "ts-node": "9.1.1", 46 | "typescript": "4.1.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "dist", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "exclude": ["node_modules", "dist", "test"], 13 | } 14 | --------------------------------------------------------------------------------