@gulibs/wechat-pay-v3
v0.0.5
Published
Wechat Pay V3 SDK for Node.js
Maintainers
Readme
微信支付 V3
🛠️ 极易扩展
🛠️ typescript 编写且类型完备
🛠️ 自动更新平台证书
🛠️ 支持直连商户体系和服务商体系
🛠️ hook 请求过程
🛠️ 支持微信支付公钥验签
安装
项目使用了 node 自身的 crypto, 请确保运行的版本大于 15.6
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-----标记 - 用于验签和加密敏感字段
获取微信支付公钥
- 登录微信商户平台
- 进入【账户中心】->【API 安全】->【申请公钥】
- 下载公钥文件(PEM 格式)
- 在商户平台可以看到公钥 ID(格式:
PUB_KEY_ID_xxxx)
配置方式
方式 1:文件路径配置(推荐)
const config: ContainerOptions = {
// ... 其他配置
wechatPayPublicKeyPath: '/path/to/wechatpay_public_key.pem',
}方式 2:直接提供公钥内容
const config: ContainerOptions = {
// ... 其他配置
wechatPayPublicKey: readFileSync('/path/to/wechatpay_public_key.pem'),
// 或者直接提供字符串
wechatPayPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`,
}常见错误和解决方案
错误:error:1E08010C:DECODER routines::unsupported
原因:公钥格式不正确或文件内容损坏
解决方案:
- 检查公钥文件是否包含完整的
-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----标记 - 确保公钥文件编码为 UTF-8
- 确保公钥内容未被截断或修改
- 重新从微信商户平台下载公钥文件
错误:无可用的平台证书 或 RESOURCE_NOT_EXISTS
原因:公钥未配置或加载失败,导致证书自动更新失败
解决方案:
- 正确配置微信支付公钥(
wechatPayPublicKey或wechatPayPublicKeyPath) - 确保公钥格式正确(PEM 格式)
- 如果不需要证书自动更新,可以设置
autoUpdateCertificates: false,并手动下载平台证书
错误:微信支付公钥未配置,无法进行验签
原因:响应头 Wechatpay-Serial 是 PUB_KEY_ID_xxxx 格式,但公钥未配置
解决方案:
- 配置微信支付公钥(
wechatPayPublicKey或wechatPayPublicKeyPath) - 或者使用平台证书进行验签(需要手动下载平台证书)
注意事项
- ✅ 公钥是可选的:如果未配置公钥,SDK 仍可使用平台证书进行验签
- ✅ 公钥 ID 自动识别:SDK 会自动从响应头识别公钥 ID,无需单独配置
- ✅ 格式要求:公钥必须是 PEM 格式,以
-----BEGIN PUBLIC KEY-----开头 - ⚠️ 证书自动更新:如果启用了
autoUpdateCertificates,建议配置公钥以确保证书自动更新功能正常工作
说明
sdk 公开工具函数,基础类和功能类。工具函数针对应用场景代码封装,基础类是 sdk 的核心,功能类为具体的功能实现。实现的功能类列表可在下方表格中查看。
base 类提供了验签方法resVerify, 但并没有给功能方法添加自动验签。除了封装的 handleCallback 方法,其他情况下您可以通过 hook 的方式在 onResponse 中进行验签,下方有实例代码。
大多数情况下,提供的方法对于加密参数都是自动的,部分过于复杂的接口,在 JSDOC 提示中会有 notAutoEncrypt 标注。
使用
配置选项
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 会确保签名串与请求指向该域名。
获取沙箱验签密钥
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:为请求的核心逻辑,在这个阶段会对参数进行加密,签名,更新证书等操作。
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
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 | Applyment | √ | | | 基础支付 | 因除合单支付外,其余方式仅下单不同,BasePay 为支付基类 | BasePay | √ | √ | | JSAPI 支付 | link | JSPay | √ | √ | | 小程序支付 | link | MiniProgramPay | √ | √ | | APP 支付 | link | AppPay | √ | √ | | H5 支付 | link | H5Pay | √ | √ | | Native 支付 | link | NativePay | √ | √ |
sdk 满足大多数情况下的基本支付功能。扩展其余功能请参考扩展功能类
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<T>(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<H, B>(headers: H, body?: B) - 响应验签
headers: 响应头对象(包含wechatpay-timestamp,wechatpay-nonce,wechatpay-signature,wechatpay-serial)body: 可选,响应体对象- 返回:验签结果(boolean)
- 说明: 自动识别验签方式,支持平台证书和微信支付公钥两种方式
handleCallback<H, B>(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<TradeBillParams, 'sub_mchid'>) - 申请交易账单(直连商户)
- 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 个字节)
示例代码
下单接口示例
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 格式时,会自动使用微信支付公钥验签。
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 格式时),可以配置微信支付公钥:
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('验签失败')
}
},
})手动验签示例
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('验签失败')
}
},
})扩展功能类
封装 sdk 的目的是解决现有项目的需求,所以优先保证的是架构的扩展性,而非接口完整。
当你遇到 sdk 未提供的接口时,可以注入 WechatPayV3Base 实例来完成。
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()