@qzsy/unipay-sdk
v0.1.3
Published
统一支付网关服务端 TypeScript SDK
Downloads
385
Keywords
Readme
@qzsy/unipay-sdk
统一支付网关的服务端 TypeScript SDK。其他应用只放 应用密钥标识 + 服务端密钥,由本包处理访问令牌、防重放键和 application/problem+json。
业务对接步骤、参数、出入包和 Agent 集成指令见 docs/业务对接.md,控制台可交互版在 /user/docs。
金额单位是 CNY 分。密钥和通知签名密钥只能放业务服务端,不要进浏览器或小程序。
安装
npm i @qzsy/unipay-sdk或 bun add @qzsy/unipay-sdk。
两条接入路径
托管收银台: 业务只建单,用户跳到网关页面选方式并付款。适合网站、多业务共用一个公众号支付目录。
直连调起: 业务自己的 H5 / 小程序 / App 调起支付。网关返回 next_action(微信 JSAPI / 小程序 / App、支付宝 trade_no / order_string)。这是微信统一下单的等价物,不必跳收银台。
import { UnifiedPay } from '@qzsy/unipay-sdk'
const pay = new UnifiedPay({
baseUrl: process.env.UNIFIEDPAY_BASE_URL!, // 例如 http://127.0.0.1:3000
clientId: process.env.UNIFIEDPAY_CLIENT_ID!,
clientSecret: process.env.UNIFIEDPAY_CLIENT_SECRET!,
})
// 路径 A:打开托管收银台
const { checkout } = await pay.createHostedCheckout({
intent: {
merchant_order_no: 'ord_1001',
amount: { value: 199 },
subject: '会员充值',
return_url: `https://shop.example.com/pay/result?order=ord_1001`,
notify_url: 'https://shop.example.com/webhooks/pay',
},
})
// 把 checkout.checkout_url 发给用户打开
// 路径 B:业务前端自己调起(小程序 / JSAPI / App)
const { attempt } = await pay.createDirectPayment({
intent: {
merchant_order_no: 'ord_1002',
amount: { value: 199 },
subject: '会员充值',
notify_url: 'https://shop.example.com/webhooks/pay',
},
payer: {
provider: 'WECHAT',
identifier: openid, // 业务后端用 code 换到的身份
},
attempt: {
provider: 'WECHAT',
interaction: 'MINI_PROGRAM',
},
})
// 把 attempt.next_action 交给前端:executeAction 或 wx.requestPayment / my.tradePay先在控制台登记回跳和通知的站点前缀。下单传完整 return_url / notify_url(可带查询参数),须与已登记地址同协议、同主机,路径落在前缀下。有 notify_url 时只投这一单指定地址,签名仍用匹配端点那把密钥。不传 URL 且应用只有一条登记时,网关用那一条。币种默认 CNY,订单默认 30 分钟过期。
写操作会自动带 Idempotency-Key。同一笔业务重试时请自己传入同一个键(不要含 :):
import { createIdempotencyKey } from '@qzsy/unipay-sdk'
await pay.createIntent(input, { idempotencyKey: createIdempotencyKey() })通知验签
网关请求头:webhook-id / webhook-timestamp / webhook-signature(v1, + HMAC-SHA256 base64)。
import { verifyWebhook } from '@qzsy/unipay-sdk'
const event = verifyWebhook({
secret: process.env.UNIFIEDPAY_WEBHOOK_SECRET!,
headers: request.headers,
rawBody: await request.text(),
})
if (event.type === 'com.unifiedpay.payment.succeeded.v1') {
// 先按 event.id 去重,再查自己的订单并履约
}验签只证明报文真实完整,不代替业务去重和查单。notify_url 即使带了查询参数,密钥也不变。
createRefund 也可传 notify_url;不传则用原单地址。公众号授权回跳用 oauth_return_url,规则与 return_url 相同。
其它常用方法
| 方法 | 对应接口 |
| --- | --- |
| capabilities | GET /api/v1/payment-capabilities |
| createIntent / getIntent / listIntents | /api/v1/payment-intents |
| createDirectPayment | 建意图 + 可选导入身份 + 发起尝试,返回 next_action |
| createCheckoutSession | POST /api/v1/checkout-sessions |
| createAttempt / getAttempt | 支付尝试 |
| cancelIntent / reverseAttempt | 关单 / 付款码撤销 |
| createRefund / getRefund | 退款 |
| readCheckout | 用收银台只读令牌查状态 |
浏览器 / 小程序侧用 @qzsy/unipay-sdk/browser:openCheckout 打开托管页;executeAction(next_action) 按 sdk 调起微信 JSAPI、wx.requestPayment、支付宝 H5 / my.tradePay。App 参数(WECHAT_APP / ALIPAY_APP)交给原生 SDK,浏览器里会返回 UNSUPPORTED。不要把 clientSecret 带到前端。
微信内 H5 若要在业务自己的页面调 JSAPI,该页必须在微信支付授权目录里;否则请走托管收银台。小程序和 App 没有这个限制。
错误
失败会抛 GatewayError:code、detail、retryable、requestId。retryable === true 时只能用同一防重放键 / 同一业务单号重试,不能另开一笔扣款。
本地试跑
开发服务起来后:
UNIFIEDPAY_CLIENT_SECRET=演示服务端密钥 bun packages/sdk/examples/checkout.ts
UNIFIEDPAY_CLIENT_SECRET=演示服务端密钥 bun packages/sdk/examples/direct-pay.ts密钥在管理端「支付网关 → 开发者中心」复制。本机优先 http://127.0.0.1:3000;若只监听了 IPv6,改用 http://localhost:3000。
