npm package: @gulibs/wechat-pay-v3
Description: Wechat Pay V3 SDK for Node.js
Author: HectorGu
Latest version: 0.0.5

<p align="center">
<a href="https://www.npmjs.com/package/@gulibs/wechat-pay-v3"><img src="https://img.shields.io/npm/v/@gulibs/wechat-pay-v3.svg" alt="npm package"></a>
</p>

# 微信支付 V3

- 🛠️ 极易扩展

- 🛠️ typescript 编写且类型完备

- 🛠️ 自动更新平台证书

- 🛠️ 支持直连商户体系和服务商体系

- 🛠️ hook 请求过程

- 🛠️ 支持微信支付公钥验签

## 安装

> 项目使用了 node 自身的 crypto, 请确保运行的版本大于 15.6

```dash
npm install @gulibs/wechat-pay-v3
```

## 微信支付公钥配置

### 公钥 ID 和公钥文件的关系

- **微信支付公钥 ID（PUB_KEY_ID_xxxx）**：
  - 从微信商户平台获取，格式为 `PUB_KEY_ID_xxxx`
  - 当响应头 `Wechatpay-Serial` 是 `PUB_KEY_ID_xxxx` 时，SDK 会自动使用微信支付公钥进行验签
  - 使用公钥加密敏感字段时，SDK 会自动在请求头添加 `Wechatpay-Serial: PUB_KEY_ID_xxxx`
  - **注意**：公钥 ID 不需要单独配置，SDK 会从响应头自动识别

- **微信支付公钥文件（pub_key.pem）**：
  - 从微信商户平台下载的 PEM 格式公钥文件
  - 必须包含完整的 `-----BEGIN PUBLIC KEY-----` 和 `-----END PUBLIC KEY-----` 标记
  - 用于验签和加密敏感字段

### 获取微信支付公钥

1. 登录[微信商户平台](https://pay.weixin.qq.com/)
2. 进入【账户中心】->【API 安全】->【申请公钥】
3. 下载公钥文件（PEM 格式）
4. 在商户平台可以看到公钥 ID（格式：`PUB_KEY_ID_xxxx`）

### 配置方式

**方式 1：文件路径配置（推荐）**

```typescript
const config: ContainerOptions = {
  // ... 其他配置
  wechatPayPublicKeyPath: '/path/to/wechatpay_public_key.pem',
}
```

**方式 2：直接提供公钥内容**

```typescript
const config: ContainerOptions = {
  // ... 其他配置
  wechatPayPublicKey: readFileSync('/path/to/wechatpay_public_key.pem'),
  // 或者直接提供字符串
  wechatPayPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`,
}
```

### 常见错误和解决方案

#### 错误：`error:1E08010C:DECODER routines::unsupported`

**原因**：公钥格式不正确或文件内容损坏

**解决方案**：
1. 检查公钥文件是否包含完整的 `-----BEGIN PUBLIC KEY-----` 和 `-----END PUBLIC KEY-----` 标记
2. 确保公钥文件编码为 UTF-8
3. 确保公钥内容未被截断或修改
4. 重新从微信商户平台下载公钥文件

#### 错误：`无可用的平台证书` 或 `RESOURCE_NOT_EXISTS`

**原因**：公钥未配置或加载失败，导致证书自动更新失败

**解决方案**：
1. 正确配置微信支付公钥（`wechatPayPublicKey` 或 `wechatPayPublicKeyPath`）
2. 确保公钥格式正确（PEM 格式）
3. 如果不需要证书自动更新，可以设置 `autoUpdateCertificates: false`，并手动下载平台证书

#### 错误：`微信支付公钥未配置，无法进行验签`

**原因**：响应头 `Wechatpay-Serial` 是 `PUB_KEY_ID_xxxx` 格式，但公钥未配置

**解决方案**：
1. 配置微信支付公钥（`wechatPayPublicKey` 或 `wechatPayPublicKeyPath`）
2. 或者使用平台证书进行验签（需要手动下载平台证书）

### 注意事项

- ✅ **公钥是可选的**：如果未配置公钥，SDK 仍可使用平台证书进行验签
- ✅ **公钥 ID 自动识别**：SDK 会自动从响应头识别公钥 ID，无需单独配置
- ✅ **格式要求**：公钥必须是 PEM 格式，以 `-----BEGIN PUBLIC KEY-----` 开头
- ⚠️ **证书自动更新**：如果启用了 `autoUpdateCertificates`，建议配置公钥以确保证书自动更新功能正常工作

## 说明

sdk 公开工具函数，基础类和功能类。工具函数针对应用场景代码封装，基础类是 sdk 的核心，功能类为具体的功能实现。实现的功能类列表可在下方表格中查看。

base 类提供了验签方法`resVerify`, 但并没有给功能方法添加自动验签。除了封装的 handleCallback 方法，其他情况下您可以通过 hook 的方式在 onResponse 中进行验签，下方有实例代码。

大多数情况下，提供的方法对于加密参数都是自动的，部分过于复杂的接口，在 JSDOC 提示中会有 notAutoEncrypt 标注。

## 使用

### 配置选项

```typescript
import { apiContainer, ContainerOptions } from '@gulibs/wechat-pay-v3'
import { readFileSync } from 'fs'

const Config: ContainerOptions = {
  // 商户号
  mchid: '商户号',
  // pem证书
  apiclient_cert: readFileSync('/xx/apiclient_cert.pem'),
  // pem私钥
  apiclient_key: readFileSync('/xx/apiclient_key.pem'),
  // apiV3密钥
  apiV3Key: 'APIv3密钥',
  // 可选: header中的User-Agent，默认'wechatpay-sdk'
  userAgent: 'wechatpay-nodejs-sdk/1.0.0',
  // 可选: 自动更新平台证书，默认true
  // 更偏向于惰性更新,缓存证书并记录过期时间,在每次请求时,比对时间进行更新
  // 如果你需要自己管控,可以关闭此选项,调用updateCertificates(true)方法强制更新实例上的证书
  autoUpdateCertificates: true,
  // 可选: 下载文件文件夹，默认[systemTempDir]/wxpay-v3-downloads
  // 让部分接口更加方便,例如上传文件给微信接口只能从本地上传
  // 需要注意的是,sdk并不会保存此文件,于对应功能完毕后
  downloadDir: './tmpDownload',
  // 可选: 微信支付公钥文件路径（PEM格式）
  // 从商户平台下载的微信支付公钥文件路径
  // 如果提供了此选项，将支持使用微信支付公钥进行验签和加密敏感字段
  // 微信支付公钥ID格式为 PUB_KEY_ID_xxx，从响应头 Wechatpay-Serial 获取，不需要单独配置
  // 公钥文件必须是 PEM 格式，以 -----BEGIN PUBLIC KEY----- 开头
  wechatPayPublicKeyPath: '/path/to/wechatpay_public_key.pem',
  // 可选: 微信支付公钥内容（PEM格式），可以是字符串或Buffer
  // 如果同时提供了 wechatPayPublicKeyPath 和 wechatPayPublicKey，优先使用 wechatPayPublicKey
  // 公钥格式要求：必须是完整的 PEM 格式，包含 -----BEGIN PUBLIC KEY----- 和 -----END PUBLIC KEY-----
  wechatPayPublicKey: readFileSync('/path/to/wechatpay_public_key.pem'),
  // 可选: 沙箱配置，默认 production
  sandbox: {
    environment: 'production', // 设置为 'sandbox' 启用仿真系统
    // negativeTestCase: 'MICROPAY_FAIL', // 自动附加 Wechatpay-Negative-Test 请求头
    // allowInProduction: false, // 若需在 NODE_ENV=production 启用沙箱，需显式设置为 true
    // domainOverride: 'https://api.mch.weixin.qq.com', // 自定义沙箱域名
  },
  // 可选: 是否使用单例模式，默认true
  // 开启后同个商户号只会返回一个实例
  singleton: true,
}
```

#### 沙箱配置

- 将 `sandbox.environment` 设置为 `'sandbox'` 后，SDK 会自动把 `https://api.mch.weixin.qq.com/...` 重写为 `https://api.mch.weixin.qq.com/xdc/apiv2sandbox/...`，并在初始化时打印警示日志。
- 配置 `sandbox.negativeTestCase`（例如 `MICROPAY_FAIL`）后，请求会自动附加 `Wechatpay-Negative-Test` 头，触发负向测试用例。
- 默认情况下，如果 `NODE_ENV=production` 且启用了沙箱，SDK 会抛出错误以避免误用。显式设置 `sandbox.allowInProduction = true` 可以在预发 / 生产环境中复用沙箱配置。
- 若微信侧提供独立沙箱域名，可通过 `sandbox.domainOverride` 覆盖，SDK 会确保签名串与请求指向该域名。

#### 获取沙箱验签密钥

```typescript
const base = new WechatPayV3Base({
  /* 其他配置 */
  sandbox: {
    environment: 'sandbox',
  },
})

const sandboxKey = await base.getSandboxSignKey(process.env.WECHAT_API_V2_KEY!)
console.log('sandbox_signkey', sandboxKey)
```

> ⚠️ 沙箱验签密钥接口属于 V2 协议，需要提供 APIv2 密钥。SDK 仅返回密钥本身，不会持久化存储，请根据业务需要自行缓存。

### hook

请求流程：[onRequsetBefore] -> [sdkWork] -> [onRequsetAfter] -> [onResponse]

hook 方法传递的参数都是原始引用，请注意不要轻易修改，除非你知道你在做什么。

sdkWork：为请求的核心逻辑，在这个阶段会对参数进行加密，签名，更新证书等操作。

```typescript
apiContainer(
  {
    /* config */
  },
  {
    onRequsetBefore(config, instance) {
      console.log(config)
    },
    onRequsetAfter(config, instance) {
      console.log(config)
    },
    onResponse(res, instance) {
      console.log(res)
      //如果需要验签
      const verifyResult = instance.resVerify(res.headers, res.data)

      //部分接口是不需要验签的，不要轻易直接抛错
      //您可以将验签结果加入 res.data 中,大多数方法返回res.data
      res.data.verifyResult = verifyResult
    },
  },
)

//or

const base = new WechatPayV3Base(
  {
    /* config */
  },
  {
    /* hooks */
  },
)
```

### 调用方式

调用方式有两种，一种通过封装的容器调用，一种通过类调用。容器实现默认单例（容器调用的类均单例）和自动的依赖注入。
容器函数为`apiContainer`

```typescript
import { apiContainer, ContainerOptions, Applyment } from '@gulibs/wechat-pay-v3'
import { readFileSync } from 'fs'

const Config: ContainerOptions = {
  //证书
  apiclient_cert: readFileSync('/xx/apiclient_cert.pem'),
  //证书密钥
  apiclient_key: readFileSync('/xx/apiclient_key.pem'),
  //后台配置的key
  apiV3Key: 'APIv3密钥',
  //商户号
  mchid: '商户号',
  //默认单例模式,开启后同个商户号只会返回一个实例。
  singleton: true,
  //可选:默认系统的tmp目录
  downloadDir: './tmpDownload',
  //可选: 默认true。开启后会缓存证书12小时,12小时后惰性更新证书
  autoUpdateCertificates: true,
  //可选，默认'wechatpay-sdk'
  userAgent: 'wechatpay-nodejs-sdk/1.0.0',
}

//1 容器获取示例
const applyment = apiContainer(Config).use(Applyment)
//2 类直接new就好,不过请自行管理实例避免重复创建造成性能浪费
const applyment = new Applyment(new WechatPayV3Base(Config))
//Applyment 为特约商户的功能类
//上方两种方式都可以拿到 applyment 实例
//例如调用提交特约商户申请接口
applyment.submitApplications()
```

## 已实现功能

| 功能        | 官方链接                                                                             | 库名            | 服务商 | 直连商户 |
| ----------- | ------------------------------------------------------------------------------------ | --------------- | ------ | -------- |
| 核心类      | 加解密，管理证书，扩展功能使用的基础类                                                 | WechatPayV3Base | √      | √        |
| 特约商户    | [link](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/open/pay/chapter7_1_4.shtml) | Applyment       | √      |          |
| 基础支付    | 因除合单支付外，其余方式仅下单不同，BasePay 为支付基类                                 | BasePay         | √      | √        |
| JSAPI 支付  | [link](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)             | JSPay           | √      | √        |
| 小程序支付  | [link](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)             | MiniProgramPay  | √      | √        |
| APP 支付    | [link](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)             | AppPay          | √      | √        |
| H5 支付     | [link](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)             | H5Pay           | √      | √        |
| Native 支付 | [link](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml)             | NativePay       | √      | √        |

> sdk 满足大多数情况下的基本支付功能。扩展其余功能请参考<a href="#addClass">扩展功能类</a>

## TODO

- [ ] 国密支持

## 核心类 API

### WechatPayV3Base

#### Hook 事件

- **setEvents(events: WechatBaseEventOPtions)** - 设置请求事件钩子

#### 证书相关

- **getCertificates()** - 获取商户当前可用的平台证书列表
- **updateCertificates(forceUpdate?: boolean)** - 更新平台证书
  - `forceUpdate`: 是否强制更新，默认 false

#### 请求相关

- **request** - axios 请求实例
- **downloadFile(url: string, fileName?: string)** - 下载文件
  - `url`: 文件下载地址
  - `fileName`: 可选，文件名（包含后缀）
- **uploadImage(pathOrUrl: string, fileName?: string)** - 上传图片（最大 2M）
  - `pathOrUrl`: 图片路径（本地路径或网络路径）
  - `fileName`: 可选，文件名（必须以 jpg、bmp、png 为后缀）
- **uploadVideo(pathOrUrl: string, fileName?: string)** - 上传视频（最大 5M）
  - `pathOrUrl`: 视频路径（本地路径或网络路径）
  - `fileName`: 可选，文件名（必须以 avi、wmv、mpeg、mp4、mov、mkv、flv、f4v、m4v、rmvb 为后缀）

#### 加解密

- **publicEncrypt(data: string)** - 平台证书公钥加密
  - `data`: 待加密数据
  - 返回：base64 编码的加密数据
- **publicEncryptObjectPaths&lt;T&gt;(data: T, paths: string[])** - 平台证书公钥加密请求主体中指定的字段
  - `data`: 数据对象
  - `paths`: 需要加密的字段路径数组（如 `['idCard.number']`）
  - 返回：新的对象，不会修改原对象
- **aesGcmDecrypt(options)** - AES-GCM 解密平台响应
  - `options`: `{ ciphertext: string, nonce: string, associated_data: string }`
  - 返回：解密后的字符串
- **sha256WithRSA(data: string)** - SHA256 with RSA 私钥签名
  - `data`: 待签名数据
  - 返回：base64 编码的签名
- **sha256WithRsaVerify(serial: string, signature: string, data: string)** - SHA256 with RSA 公钥验签
  - `serial`: 证书序列号或微信支付公钥 ID（`PUB_KEY_ID_xxx` 格式）
  - `signature`: base64 编码的签名
  - `data`: 待验签数据
  - 返回：验签结果（boolean）
  - **说明**: 自动识别 `serial` 格式：
    - 如果 `serial` 以 `PUB_KEY_ID_` 开头，使用微信支付公钥验签（需要配置 `wechatPayPublicKeyPath` 或 `wechatPayPublicKey`）
    - 否则使用平台证书验签（原有逻辑）

#### 常用封装

- **resVerify&lt;H, B&gt;(headers: H, body?: B)** - 响应验签
  - `headers`: 响应头对象（包含 `wechatpay-timestamp`, `wechatpay-nonce`, `wechatpay-signature`, `wechatpay-serial`）
  - `body`: 可选，响应体对象
  - 返回：验签结果（boolean）
  - **说明**: 自动识别验签方式，支持平台证书和微信支付公钥两种方式

- **handleCallback&lt;H, B&gt;(headers: H, body: B)** - 处理回调（验证签名并使用 AEAD_AES_256_GCM 解密）
  - `headers`: 回调请求头
  - `body`: 回调请求体（必须包含 `resource` 字段）
  - 返回：解密后的回调数据（`resource` 字段已自动解析为对象）
  - **说明**: 自动识别验签方式，支持平台证书和微信支付公钥两种方式

## 基础支付类 API

### BasePay

所有支付方式（JSAPI、小程序、APP、H5、Native）都继承自 `BasePay`，提供以下通用方法：

#### 下单

- **order(data: JSAPIOder_Business)** - 下单（直连商户）
- **orderOnProvider(data: JSAPIOder_Provider)** - 下单（服务商）

#### 查询订单

- **transactionIdQueryOrder(data: JSAPI_QueryOrder_tid_Business)** - 通过微信订单号查询订单（直连商户）
- **transactionIdQueryOrderOnProvider(data: JSAPI_QueryOrder_tid_Provider)** - 通过微信订单号查询订单（服务商）
- **outTradeNoQueryOrder(data: JSAPI_QueryOrder_outTradeNo_Business)** - 通过商户订单号查询订单（直连商户）
- **outTradeNoQueryOrderOnProvider(data: JSAPI_QueryOrder_outTradeNo_Provider)** - 通过商户订单号查询订单（服务商）

#### 关闭订单

- **closeOrder(data: JSAPI_QueryOrder_outTradeNo_Business)** - 关闭订单（直连商户）
  - 返回：HTTP 状态码，如果为 204 则关闭成功
- **closeOrderOnProvider(data: JSAPI_QueryOrder_outTradeNo_Provider)** - 关闭订单（服务商）
  - 返回：HTTP 状态码，如果为 204 则关闭成功

#### 退款

- **refund(data: Refund_Business)** - 退款（直连商户）
- **refundOnProvider(data: Refund_Provider)** - 退款（服务商）

#### 查询退款

- **queryRefund(data: { out_refund_no: string })** - 查询退款（直连商户）
- **queryRefundOnProvider(data: { out_refund_no: string, sub_mchid: string })** - 查询退款（服务商）

#### 账单

- **applyTradeBill(data: Omit&lt;TradeBillParams, 'sub_mchid'&gt;)** - 申请交易账单（直连商户）
- **applyTradeBillOnProvider(data: TradeBillParams)** - 申请交易账单（服务商）
- **applyFundFlowBill(data: FundflowBillParams)** - 申请资金账单（直连商户）
- **applyFundFlowBillOnProvider(data: FundflowBillParams)** - 申请资金账单（服务商）
- **applySubMerchantFundFlowBill(data: SubMerchantFundflowBillParams)** - 申请单个子商户资金账单（仅限服务商）
- **downloadBill(download_url: string)** - 下载账单（通用）
  - `download_url`: 账单下载地址（从申请账单接口返回）

### 支付方式特定类

#### JSPay (JSAPI 支付）

继承自 `BasePay`，提供 `getPayParams()` 方法获取 JSAPI 支付参数。

#### MiniProgramPay （小程序支付）

继承自 `BasePay`，提供 `getPayParams()` 方法获取小程序支付参数。

#### AppPay (APP 支付）

继承自 `BasePay`，提供 `getPayParams()` 方法获取 APP 支付参数。

#### H5Pay (H5 支付）

继承自 `BasePay`，下单接口返回 `h5_url`。

#### NativePay (Native 支付）

继承自 `BasePay`，下单接口返回 `code_url`。

## 特约商户类 API

### Applyment

#### 提交申请单

- **submitApplications(body: SubmitApplicationBody)** - 提交申请单
  - **注意**: 此接口不自动加密，需要手动调用 `uploadImage()` 上传图片，使用 `publicEncryptObjectPaths()` 加密敏感字段
  - 返回：`{ applyment_id: string, business_code: string }`

#### 查询申请单状态

- **queryApplymentState(businessCode: string)** - 查询申请单状态
  - `businessCode`: 业务申请编号

#### 修改结算账户

- **modifySettlementAccount(sub_mchid: string, body: ModifySettlementAccountBody)** - 修改结算账户
  - `sub_mchid`: 子商户号（长度最小 8 个字节）
  - `body`: 请求主体（`account_name` 和 `account_number` 会自动加密）
  - 返回：是否成功（boolean）

#### 查询结算账户

- **querySettlementAccount(sub_mchid: string)** - 查询结算账户
  - `sub_mchid`: 子商户号（长度最小 8 个字节）

## 示例代码

### 下单接口示例

```typescript
import { apiContainer, MiniProgramPay } from '@gulibs/wechat-pay-v3'
import { readFileSync } from 'fs'
import { Router } from 'express'

const router = Router()
const appId = '小程序appid'

router.post('/pay/order', async (req, res, next) => {
  try {
    const miniPay = apiContainer({
      mchid: '商户号',
      apiclient_cert: readFileSync('/xx/apiclient_cert.pem'),
      apiclient_key: readFileSync('/xx/apiclient_key.pem'),
      apiV3Key: 'APIv3密钥',
    }).use(MiniProgramPay)

    const { prepay_id } = await miniPay.order({
      appid: appId,
      mchid: '商户号',
      description: '商品描述',
      out_trade_no: '商户订单号',
      notify_url: '支付回调地址',
      amount: {
        total: 100, // 金额，单位：分
        currency: 'CNY',
      },
      payer: {
        openid: '用户openid',
      },
    })

    //获取小程序支付参数
    const payParams = miniPay.getPayParams({
      appId,
      prepay_id,
    })
    /* 小程序可以调起支付直接传入payParams即可 */
    res.send(payParams)
  } catch (e) {
    next(e)
  }
})
```

### 通知接收

base 实例上封装了通用的 handleCallback, 他的功能是进行回调验签，通过后返回的 resource 对象会自动解密。

**注意**: `handleCallback` 会自动识别验签方式，支持平台证书和微信支付公钥两种方式。如果配置了微信支付公钥，当回调中的 `Wechatpay-Serial` 为 `PUB_KEY_ID_xxx` 格式时，会自动使用微信支付公钥验签。

```typescript
import { apiContainer } from '@gulibs/wechat-pay-v3'
import { readFileSync } from 'fs'
import { Router } from 'express'

const router = Router()

router.post('/notify', async (req, res) => {
  try {
    const wxapi = apiContainer({
      mchid: '商户号',
      apiclient_cert: readFileSync('/xx/apiclient_cert.pem'),
      apiclient_key: readFileSync('/xx/apiclient_key.pem'),
      apiV3Key: 'APIv3密钥',
      // 可选：配置微信支付公钥以支持公钥验签
      wechatPayPublicKeyPath: '/path/to/wechatpay_public_key.pem',
      // 或者直接提供公钥内容
      // wechatPayPublicKey: readFileSync('/path/to/wechatpay_public_key.pem'),
    })

    //handleCallback接收两个参数,第一个是请求头,第二个是请求体。
    //会自动识别验签方式（平台证书或微信支付公钥）
    const data = await wxapi.handleCallback(req.headers, req.body)
    // data.resource 已自动解密并解析为对象

    // 处理业务逻辑
    console.log('支付通知:', data)

    res.status(204).send()
  } catch (e) {
    res.status(400).send({
      message: e.message,
      code: 'FAIL',
    })
  }
})
```

### 微信支付公钥验签示例

如果您的系统需要支持微信支付公钥验签（当响应头中的 `Wechatpay-Serial` 为 `PUB_KEY_ID_xxx` 格式时），可以配置微信支付公钥：

```typescript
import { apiContainer } from '@gulibs/wechat-pay-v3'
import { readFileSync } from 'fs'

const wxapi = apiContainer({
  mchid: '商户号',
  apiclient_cert: readFileSync('/xx/apiclient_cert.pem'),
  apiclient_key: readFileSync('/xx/apiclient_key.pem'),
  apiV3Key: 'APIv3密钥',
  // 方式1: 从文件路径加载微信支付公钥
  wechatPayPublicKeyPath: '/path/to/wechatpay_public_key.pem',
  // 方式2: 直接提供公钥内容（优先使用）
  // wechatPayPublicKey: readFileSync('/path/to/wechatpay_public_key.pem'),
  // 或者直接提供字符串
  // wechatPayPublicKey: '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----',
})

// 在响应处理中，会自动识别验签方式
wxapi.setEvents({
  onResponse(res, instance) {
    // resVerify 会自动识别 serial 格式，选择对应的验签方式
    const verifyResult = instance.resVerify(res.headers, res.data)
    if (!verifyResult) {
      console.error('验签失败')
    }
  },
})
```

### 手动验签示例

```typescript
import { apiContainer } from '@gulibs/wechat-pay-v3'

const wxapi = apiContainer({
  /* config */
})

// 在响应处理中手动验签
wxapi.setEvents({
  onResponse(res, instance) {
    const {
      'wechatpay-timestamp': timestamp,
      'wechatpay-nonce': nonce,
      'wechatpay-signature': signature,
      'wechatpay-serial': serial,
    } = res.headers

    // 构建验签名串
    const bodyStr = JSON.stringify(res.data)
    const signStr = `${timestamp}\n${nonce}\n${bodyStr}\n`

    // sha256WithRsaVerify 会自动识别 serial 格式
    // 如果 serial 以 PUB_KEY_ID_ 开头，使用微信支付公钥验签
    // 否则使用平台证书验签
    const verifyResult = instance.sha256WithRsaVerify(serial, signature, signStr)

    if (!verifyResult) {
      console.error('验签失败')
    }
  },
})
```

### <a id="addClass">扩展功能类</a>

封装 sdk 的目的是解决现有项目的需求，所以优先保证的是架构的扩展性，而非接口完整。

当你遇到 sdk 未提供的接口时，可以注入 WechatPayV3Base 实例来完成。

```typescript
import { WechatPayV3Base, apiContainer } from '@gulibs/wechat-pay-v3'

class Others {
  //将WechatPayV3Base实例作为依赖
  constructor(public base: WechatPayV3Base) {}

  async test() {
    //调用base的request进行请求.自动签名满足大多数情况下的请求.
    //如果签名串并非data对象的内容,请自行计算
    //可以参照源码中_upload的实现
    return this.base.request({
      url: 'https://xxx.xxx.xxx/xxx', //需要为完整的url而非接口路径
      method: 'GET',
    })
  }
}

const baseIns = new WechatPayV3Base({
  /* xxx */
})
const others = new Others(baseIns)
//直接调用
others.test()
//或者通过容器调用
apiContainer({
  /* xxx */
})
  .use(Others)
  .test()
```