@h-ai/payment 0.1.0-alpha8 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +244 -0
- package/README.ja.md +244 -0
- package/README.md +172 -52
- package/dist/chunk-YQD4KPT5.js +103 -0
- package/dist/chunk-YQD4KPT5.js.map +1 -0
- package/dist/client/index.d.ts +26 -25
- package/dist/client/index.js +61 -60
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +17 -19
- package/dist/index.js +719 -175
- package/dist/index.js.map +1 -1
- package/dist/payment-types-D02t-Pc9.d.ts +500 -0
- package/package.json +17 -15
- package/dist/api/index.d.ts +0 -211
- package/dist/api/index.js +0 -96
- package/dist/api/index.js.map +0 -1
- package/dist/chunk-4IUARFWH.js +0 -67
- package/dist/chunk-4IUARFWH.js.map +0 -1
- package/dist/payment-types-D1GvLKHg.d.ts +0 -209
package/README.md
CHANGED
|
@@ -1,93 +1,200 @@
|
|
|
1
1
|
# @h-ai/payment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[框架首页](../../README.md) · 相关模块:[@h-ai/audit](../audit/README.md) · [@h-ai/core](../core/README.md)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
| -------- | -------------------------------- |
|
|
9
|
-
| 微信支付 | JSAPI / H5 / App / Native (扫码) |
|
|
10
|
-
| 支付宝 | JSAPI / H5 / App / Native |
|
|
11
|
-
| Stripe | Checkout Session(国际支付) |
|
|
7
|
+
## 简介
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
统一支付模块,支持微信支付、支付宝和 Stripe,提供 Provider 模式的后端支付处理与浏览器支付调起能力。
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
## 核心功能清单
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
| Provider | 支付场景 |
|
|
14
|
+
| -------- | -------------------------------------- |
|
|
15
|
+
| 微信支付 | `jsapi`:微信公众号内置浏览器 |
|
|
16
|
+
| 支付宝 | `h5`:WAP 签名表单 POST |
|
|
17
|
+
| Stripe | h5:托管 Checkout 页面跳转(国际支付) |
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
内置创建模式仅保留上表组合;公开 `TradeType` 为 `jsapi | h5`,App、Native、小程序及其它组合均在请求渠道前返回 `UNSUPPORTED_TRADE_TYPE`。微信 JSAPI 必须提供当前公众号的 `userId`(openid),微信与支付宝仅接受 CNY。微信下单缺失有效 `prepay_id` 时失败。支付宝 `createOrder` 只生成已签名的页面请求参数,不表示渠道已受理或已付款;返回 `{ gateway, fields }`,浏览器向固定正式/沙箱网关 POST 全部签名字段,不解析 HTML,也不接收任意 action。配置 `alipay.returnUrl` 必填且为 HTTPS,`HAI_PAYMENT_ALIPAY_RETURNURL` 可覆盖;回跳不代表付款成功。客户端先检查 `success`,再检查 `data.invoked`;微信桥接对象未就绪时 `invoked=false`,已调起后的取消回调也不代表付款。
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
import { payment } from '@h-ai/payment'
|
|
21
|
+
协议依据:[微信 JSAPI 下单](https://pay.wechatpay.cn/doc/v3/merchant/4012791897)、[JSAPI 调起](https://pay.wechatpay.cn/doc/v3/merchant/4012791857)、[支付宝官方 Node SDK pageExecute](https://github.com/alipay/alipay-sdk-nodejs-all#pageexecute-示例代码)。定向测试验证请求/签名形状、真实 RSA 验签、jsdom 表单消费及 Chrome 实际 POST(网关请求由测试拦截);未使用真实商户凭据、渠道沙箱账户或手机设备,不能据此宣称已完成真实支付验收。
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
await payment.init({
|
|
26
|
-
wechat: { mchId: '...', apiV3Key: '...', serialNo: '...', privateKey: '...', appId: '...' },
|
|
27
|
-
alipay: { appId: '...', privateKey: '...', alipayPublicKey: '...' },
|
|
28
|
-
})
|
|
23
|
+
## 使用入门
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
const result = await payment.createOrder('wechat', {
|
|
32
|
-
orderNo: 'ORD001',
|
|
33
|
-
amount: 100,
|
|
34
|
-
description: '测试商品',
|
|
35
|
-
tradeType: 'jsapi',
|
|
36
|
-
userId: 'openid-xxx',
|
|
37
|
-
notifyUrl: 'https://api.example.com/payment/notify/wechat',
|
|
38
|
-
})
|
|
25
|
+
### 依赖
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
- `@h-ai/audit` — 审计日志(支付操作自动审计),可选;已初始化时自动写入审计日志
|
|
28
|
+
- `@h-ai/reldb` — 启用 Stripe 时必需,先初始化共享数据库;持久化渠道映射和幂等结果。仅微信/支付宝时无此硬依赖。
|
|
29
|
+
|
|
30
|
+
### 快速开始
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
#### Node.js 服务端
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import type { PaymentConfigInput } from '@h-ai/payment'
|
|
36
|
+
import { payment } from '@h-ai/payment'
|
|
37
|
+
|
|
38
|
+
async function paymentExample(trustedConfig: PaymentConfigInput, trustedOpenid: string) {
|
|
39
|
+
// 常驻服务在启动/停机时分别 init/close;此函数演示完整生命周期。
|
|
40
|
+
// 如启用 Stripe,宿主已先初始化共享 reldb 并检查 success。
|
|
41
|
+
const initialized = await payment.init(trustedConfig)
|
|
42
|
+
if (!initialized.success)
|
|
43
|
+
throw new Error(initialized.error.message)
|
|
44
|
+
try {
|
|
45
|
+
const created = await payment.createOrder('wechat', {
|
|
46
|
+
orderNo: 'ORD001',
|
|
47
|
+
amount: 100,
|
|
48
|
+
description: '测试商品',
|
|
49
|
+
tradeType: 'jsapi',
|
|
50
|
+
userId: trustedOpenid,
|
|
51
|
+
notifyUrl: 'https://api.example.com/payment/notify/wechat',
|
|
52
|
+
})
|
|
53
|
+
if (!created.success)
|
|
54
|
+
throw new Error(created.error.message)
|
|
55
|
+
const query = await payment.queryOrder('wechat', 'ORD001')
|
|
56
|
+
if (!query.success)
|
|
57
|
+
throw new Error(query.error.message)
|
|
58
|
+
return { order: created.data, status: query.data.status }
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await payment.close() // 宿主随后才关闭其 reldb。
|
|
62
|
+
}
|
|
63
|
+
}
|
|
44
64
|
```
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
#### 浏览器客户端
|
|
47
67
|
|
|
48
68
|
```typescript
|
|
49
69
|
import { invokePayment } from '@h-ai/payment/client'
|
|
50
70
|
|
|
51
71
|
// 使用服务端返回的订单数据调起支付
|
|
52
|
-
await invokePayment(orderData)
|
|
72
|
+
const invoked = await invokePayment(orderData)
|
|
73
|
+
if (!invoked.success)
|
|
74
|
+
throw new Error(invoked.error.message)
|
|
75
|
+
if (!invoked.data.invoked)
|
|
76
|
+
throw new Error(invoked.data.message)
|
|
53
77
|
```
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
### HTTP API 契约
|
|
56
80
|
|
|
57
|
-
|
|
81
|
+
支付 HTTP API 由 `@h-ai/api-contract` 的 `apiContract.payment` 定义。组合进应用 contract 后,可通过 typed client 调用:
|
|
58
82
|
|
|
59
83
|
```typescript
|
|
60
|
-
|
|
84
|
+
const order = await api.payment.orders.create({
|
|
85
|
+
provider: 'wechat',
|
|
86
|
+
amount: 100,
|
|
87
|
+
description: '商品',
|
|
88
|
+
tradeType: 'jsapi',
|
|
89
|
+
userId: 'trusted-public-account-openid',
|
|
90
|
+
})
|
|
61
91
|
```
|
|
62
92
|
|
|
63
|
-
|
|
93
|
+
### 配置
|
|
64
94
|
|
|
65
95
|
```typescript
|
|
66
|
-
|
|
96
|
+
import type { PaymentConfigInput } from '@h-ai/payment'
|
|
97
|
+
import { PaymentConfigSchema } from '@h-ai/payment'
|
|
98
|
+
|
|
99
|
+
// 仅为配置字段示意;真实密钥、证书和商户身份由私有部署注入。
|
|
100
|
+
const input: PaymentConfigInput = {
|
|
67
101
|
wechat: {
|
|
68
102
|
mchId: '商户号',
|
|
69
103
|
apiV3Key: 'API v3 密钥',
|
|
70
104
|
serialNo: '证书序列号',
|
|
71
105
|
privateKey: '私钥内容',
|
|
72
106
|
appId: '应用 ID',
|
|
107
|
+
platformKeys: { PUB_KEY_ID_123: '平台公钥 PEM', CERT_SERIAL: '平台证书 PEM' },
|
|
73
108
|
},
|
|
74
109
|
alipay: {
|
|
75
110
|
appId: '应用 ID',
|
|
111
|
+
sellerId: '卖家支付宝用户 ID',
|
|
76
112
|
privateKey: '私钥',
|
|
77
113
|
alipayPublicKey: '支付宝公钥',
|
|
114
|
+
returnUrl: 'https://app.example/payment/return', // 必填 HTTPS
|
|
78
115
|
signType: 'RSA2', // 可选,默认 RSA2
|
|
79
116
|
sandbox: false, // 可选,默认 false
|
|
80
117
|
},
|
|
81
118
|
stripe: {
|
|
82
|
-
secretKey: '
|
|
119
|
+
secretKey: 'sk_test_xxx',
|
|
83
120
|
webhookSecret: 'whsec_xxx',
|
|
121
|
+
successUrl: 'https://app.example/payment/success?session_id={CHECKOUT_SESSION_ID}', // 必填
|
|
122
|
+
cancelUrl: 'https://app.example/payment/cancel', // 可选
|
|
123
|
+
webhookToleranceSeconds: 300, // 可选,默认 300 秒
|
|
84
124
|
},
|
|
85
|
-
}
|
|
125
|
+
}
|
|
126
|
+
const parsed = PaymentConfigSchema.safeParse(input)
|
|
127
|
+
if (!parsed.success)
|
|
128
|
+
throw new Error('Payment configuration is invalid')
|
|
129
|
+
// 使用 parsed.data;启用 Stripe 前须先初始化共享 reldb。
|
|
86
130
|
```
|
|
87
131
|
|
|
88
132
|
只配置需要的 Provider 即可,未配置的 Provider 不会注册。
|
|
89
133
|
|
|
90
|
-
|
|
134
|
+
金额字段统一使用“分”为单位,且必须为正整数。模块会在调用具体 Provider 之前先校验 `amount` / `totalAmount`,避免把非法金额继续传给下游支付平台。
|
|
135
|
+
|
|
136
|
+
所有内置 Provider 的 HTTP 请求默认附带 15 秒超时,避免网络异常时长时间挂起。
|
|
137
|
+
|
|
138
|
+
### 通知身份、时钟与业务幂等
|
|
139
|
+
|
|
140
|
+
微信 `platformKeys` 必填,key 是受信平台证书序列号或公钥 ID,value 为对应 PEM;通知只按 `Wechatpay-Serial` 精确选取,未知标识不自动信任。证书会核对自身序列号及有效期。轮换时在所有节点部署新旧项,确认渠道停止使用旧项后移除;不存在从通知中下载密钥的逻辑。微信签名时间戳必须为严格整数且与节点时钟相差不超过 300 秒,节点须同步时钟。通知只接受 `TRANSACTION.SUCCESS` / transaction 资源,解密后继续核对 `appid/mchid`。
|
|
141
|
+
|
|
142
|
+
支付宝必填 `sellerId`,验签后核对 `app_id/seller_id`,只接受配置的 RSA2/RSA 算法,拒绝重复表单字段、未知状态和非法日历时间。请求使用明确 `Asia/Shanghai` 的北京时间,签名和订单内容仅在 POST form body 中传输,不进入 URL。`notify_time/gmt_payment` 按北京时间解析;允许渠道跨日重试,不把交易时间或通知原始时间当作五分钟签名窗口。Stripe 校验严格签名时间,支持轮换期多个 v1 签名,并核对 test/live 模式;本 Provider 不支持 Connect/组织上下文事件,应为当前账户配置对应 Webhook secret。
|
|
143
|
+
|
|
144
|
+
三个内置 Provider 均返回必填 `eventId` 和大写 `currency`,自定义 Provider 也须提供。事件 ID 分别为微信 id、支付宝 notify_id、Stripe event.id。验签解析不创建业务订单、不持久去重,也不保证履约一次;接入层必须用共享数据库核对渠道、订单、金额和币种,以渠道/商户为命名空间去重,并防止乱序回退。业务订单号不能删除后复用。
|
|
145
|
+
|
|
146
|
+
[可运行消费者示例](examples/payment-notify-consumer.ts) 将业务订单 pending→paid 的条件更新、唯一通知记录和唯一履约 outbox 放进同一事务,SQL 失败必须抛错以回滚。重复 paid 和晚到 pending 不重复生成 outbox;不同金额、币种、渠道或交易号不能更新订单。只有数据库提交成功才向渠道确认成功。示例表属于应用,payment 不会自动创建它们;外部发货由 outbox 投递者携带稳定 ID 做幂等,数据库事务不能保证跨系统副作用恰好一次。定向双进程测试使用两个真实 Node 进程共享 SQLite,证明重复通知和重启后仍仅产生一条业务 outbox;多主机生产使用同一 PostgreSQL/MySQL。
|
|
147
|
+
|
|
148
|
+
依据:[微信证书与签名时钟](https://pay.wechatpay.cn/doc/v3/merchant/4013053420)、[微信公钥轮换](https://pay.wechatpay.cn/doc/v3/merchant/4012154180)、[支付宝通知核对与重试](https://developer.alibaba.com/docs/doc.htm?articleId=103296&docType=1&treeId=346)。
|
|
149
|
+
|
|
150
|
+
### 支付宝业务响应与退款状态
|
|
151
|
+
|
|
152
|
+
查单、退款和关单均校验对应方法的响应 envelope、字符串 `code=10000`、关键字段及返回订单号。HTTP 200、缺失 envelope 或只有成功码均不能当作业务成功。交易不存在、权限/余额不足等业务错误返回对应 `QUERY_ORDER_FAILED` / `REFUND_FAILED` / `CLOSE_ORDER_FAILED`;错误 cause 只保留有界的渠道错误标识,不透传原始响应和 `msg/sub_msg`。查单拒绝未知状态及非法金额,不能伪造 pending 或零金额。
|
|
153
|
+
|
|
154
|
+
退款返回 `success: true` 仍需检查 `data.status`:只有 `fund_change=Y` 为 `success`;`N` 或缺失为 `processing`,需通过支付宝退款查询核实(本模块尚未提供退款查询 API)。`refund_fee` 为累计退款额,不能与本次退款金额直接比较。支付宝 `refundId` 是原交易号,本次退款以 `refundNo` 区分,重试必须保留相同退款号和参数。
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const refund = await payment.refund('alipay', { orderNo: 'ORDER_1', refundNo: 'REFUND_1', amount: 100 })
|
|
158
|
+
if (refund.success && refund.data.status === 'success') {
|
|
159
|
+
// 才可在共享业务数据库中幂等确认退款成功
|
|
160
|
+
}
|
|
161
|
+
const closed = await payment.closeOrder('alipay', 'ORDER_2')
|
|
162
|
+
if (closed.success) {
|
|
163
|
+
// 已核对关单响应中的订单号,才可更新业务订单
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
依据:[支付宝退款接口与成功判定](https://aipay.alipay.com/docs/subscribe/api-list/other-basic-interfaces/alipay-trade-refund.html)。
|
|
168
|
+
|
|
169
|
+
### Stripe 托管 Checkout 与主动关单
|
|
170
|
+
|
|
171
|
+
只实现 `mode=payment`、`ui_mode=hosted_page` 和 `tradeType=h5`,API 请求固定 `Stripe-Version: 2026-09-30.endive`。不支持 embedded/elements、JSAPI、App、Native 或小程序模式;创建入口、HTTP schema 与浏览器调用均拒绝未实现的场景。服务端 `successUrl` 必填,`cancelUrl` 可选,均为 HTTP(S) 地址;可在成功地址中保留 `{CHECKOUT_SESSION_ID}` 让 Stripe 替换。跳转配置属于服务端,不接受客户端覆盖;同订单重试也不能改变这组创建参数。
|
|
172
|
+
|
|
173
|
+
`closeOrder('stripe', orderNo)` 按持久映射读取 Session:open/unpaid 调用 `/checkout/sessions/:id/expire`;已 expired 确认后幂等成功,complete/paid 返回失败。遇到并发关闭或响应丢失会重新查询,只有确认过期才成功。成功页/取消页跳转本身既不代表已支付,也不等于已关单。
|
|
174
|
+
|
|
175
|
+
依据:[创建 Checkout Session](https://docs.stripe.com/api/checkout/sessions/create)、[主动过期 Session](https://docs.stripe.com/api/checkout/sessions/expire)。
|
|
176
|
+
|
|
177
|
+
### Stripe 回调状态
|
|
178
|
+
|
|
179
|
+
Stripe Provider 只处理 `mode=payment` 的 `checkout.session.completed`、`checkout.session.async_payment_succeeded`、`checkout.session.async_payment_failed`、`checkout.session.expired`。Webhook 端点只订阅这些事件;其它资源事件或不完整 Session 返回 `NOTIFY_PARSE_FAILED`,不会伪造 pending 订单。
|
|
180
|
+
|
|
181
|
+
`completed` 且 `payment_status=unpaid` 返回 `pending`,不得履约;`paid` / `no_payment_required` 才返回 `paid`。异步失败返回 `failed`,过期返回 `closed`。只有 `paid` 结果带 `paidAt`,时间取支付成功事件的 `created`,重复接收不会改变它。
|
|
182
|
+
|
|
183
|
+
结果包含 `eventId`、大写 `currency`,`transactionId` 是 Checkout Session ID。应用须在共享数据库中核对订单、金额、币种,持久去重并防止乱序通知把已支付/已退款订单回退;Provider 验签解析不代替业务事务。回调成功结果也可能为 pending,不能仅判断 `success` 就发货。
|
|
184
|
+
|
|
185
|
+
依据:[Stripe 事件类型](https://docs.stripe.com/api/events/types)、[Checkout Session 字段](https://docs.stripe.com/api/checkout/sessions/object)。
|
|
186
|
+
|
|
187
|
+
### Stripe 持久化与幂等
|
|
188
|
+
|
|
189
|
+
启用 Stripe 前须先 `await reldb.init(dbConfig)` 并检查 success,再初始化 payment;关闭时先 payment 后 reldb。初始化会等待 `hai_payment_stripe_operations` 建表,失败不会发布 Stripe Provider。多节点使用同一 PostgreSQL/MySQL 数据库;SQLite 仅适合开发/明确共享文件的单机进程,不能把不同主机的本地文件当作共享存储。
|
|
190
|
+
|
|
191
|
+
首次需要订单映射的操作会读取 `/v1/account`,结合密钥的 test/live 模式隔离记录;同账户轮换密钥仍复用原映射。密钥必须为 `sk_test_`、`sk_live_`、`rk_test_` 或 `rk_live_` 前缀,受限密钥还须允许读取账户。
|
|
192
|
+
|
|
193
|
+
创建订单按账户、模式和 orderNo 生成稳定 `Idempotency-Key`,数据库唯一记录绑定规范请求摘要与 Session ID;metadata.orderNo 禁止被调用方覆盖。查单/退款只按该 Session ID 读取并核对订单号、金额、币种,再保存 PaymentIntent,未知订单明确失败。退款按 refundNo 保留独立幂等结果,同一标识改参数会失败;自由文本 reason 保存在退款 metadata,渠道 reason 使用 requested_by_customer。
|
|
194
|
+
|
|
195
|
+
网络响应丢失或结果落库失败不会删除预留,也不会伪装成功。未知结果仅在数据库时间计算的 23 小时窗口内沿用同一幂等键重试;超出窗口必须先核对 Stripe 并修复记录,禁止删除记录后盲重试。已确认结果长期保留,不能按普通缓存 TTL 清理;退款返回的是该次受理结果,processing 不等于资金已退回。依据:[Stripe 幂等保留边界](https://docs.stripe.com/api/idempotent_requests)。
|
|
196
|
+
|
|
197
|
+
### 审计日志
|
|
91
198
|
|
|
92
199
|
关键支付操作执行成功后会自动写入审计日志(依赖 `@h-ai/audit`),无需额外配置。审计写入失败仅输出 warn 日志,不影响支付操作。
|
|
93
200
|
|
|
@@ -100,16 +207,19 @@ await payment.init({
|
|
|
100
207
|
|
|
101
208
|
> 订单查询(`queryOrder`)为只读操作,不写审计日志。
|
|
102
209
|
|
|
103
|
-
|
|
210
|
+
### 错误处理
|
|
104
211
|
|
|
105
212
|
```typescript
|
|
106
213
|
const result = await payment.createOrder('wechat', orderInput)
|
|
107
214
|
if (!result.success) {
|
|
108
215
|
switch (result.error.code) {
|
|
109
|
-
case HaiPaymentError.
|
|
216
|
+
case HaiPaymentError.INVALID_AMOUNT.code:
|
|
217
|
+
// 金额不是正整数,或 refund.totalAmount < amount
|
|
218
|
+
break
|
|
219
|
+
case HaiPaymentError.PROVIDER_NOT_FOUND.code:
|
|
110
220
|
// Provider 未注册
|
|
111
221
|
break
|
|
112
|
-
case HaiPaymentError.CREATE_ORDER_FAILED:
|
|
222
|
+
case HaiPaymentError.CREATE_ORDER_FAILED.code:
|
|
113
223
|
// 创建订单失败
|
|
114
224
|
break
|
|
115
225
|
}
|
|
@@ -118,20 +228,30 @@ if (!result.success) {
|
|
|
118
228
|
|
|
119
229
|
常用错误码:
|
|
120
230
|
|
|
121
|
-
| 错误码
|
|
122
|
-
|
|
|
123
|
-
| `HaiPaymentError.CREATE_ORDER_FAILED`
|
|
124
|
-
| `HaiPaymentError.NOT_INITIALIZED`
|
|
125
|
-
| `HaiPaymentError.PROVIDER_NOT_FOUND`
|
|
126
|
-
| `HaiPaymentError.
|
|
127
|
-
| `HaiPaymentError.
|
|
231
|
+
| 错误码 | code | 说明 |
|
|
232
|
+
| ---------------------------------------- | ----------------- | --------------- |
|
|
233
|
+
| `HaiPaymentError.CREATE_ORDER_FAILED` | `hai:payment:001` | 创建订单失败 |
|
|
234
|
+
| `HaiPaymentError.NOT_INITIALIZED` | `hai:payment:010` | 模块未初始化 |
|
|
235
|
+
| `HaiPaymentError.PROVIDER_NOT_FOUND` | `hai:payment:030` | Provider 未注册 |
|
|
236
|
+
| `HaiPaymentError.INVALID_AMOUNT` | `hai:payment:040` | 金额无效 |
|
|
237
|
+
| `HaiPaymentError.UNSUPPORTED_TRADE_TYPE` | `hai:payment:041` | 支付场景未实现 |
|
|
238
|
+
| `HaiPaymentError.NOTIFY_VERIFY_FAILED` | `hai:payment:050` | 回调验签失败 |
|
|
239
|
+
| `HaiPaymentError.NOTIFY_PARSE_FAILED` | `hai:payment:051` | 回调解析失败 |
|
|
240
|
+
| `HaiPaymentError.CONFIG_ERROR` | `hai:payment:070` | 配置无效 |
|
|
128
241
|
|
|
129
|
-
##
|
|
242
|
+
## 开发测试
|
|
130
243
|
|
|
131
244
|
```bash
|
|
245
|
+
pnpm --filter @h-ai/payment typecheck
|
|
246
|
+
pnpm --filter @h-ai/payment lint
|
|
247
|
+
pnpm --filter @h-ai/payment build
|
|
132
248
|
pnpm --filter @h-ai/payment test
|
|
249
|
+
node scripts/check-module-jsdoc.mjs --package payment
|
|
250
|
+
pnpm --filter @h-ai/payment exec vitest run tests/payment-provider-alipay.test.ts tests/payment-provider-wechat.test.ts tests/payment-provider-stripe.test.ts
|
|
133
251
|
```
|
|
134
252
|
|
|
135
|
-
|
|
253
|
+
声明中的示例使用真实 `@h-ai/payment` / `@h-ai/payment/client` 入口,区分配置输入与解析输出、`HaiResult.success` 与业务付款/退款状态。内部签名、协议解析和持久操作仓库标注“模块内部上下文”,不能作为应用的深层导入入口。运行 `node scripts/check-module-jsdoc.mjs --package payment` 校验覆盖及示例类型;它不会执行真实支付、网络请求、设备桥接或业务履约,渠道集成仍需对应验收。详见 [模块 JSDoc 规范](../../.agents/instructions/module-conventions.md#jsdoc-与示例验证)。
|
|
254
|
+
|
|
255
|
+
## 许可证
|
|
136
256
|
|
|
137
257
|
Apache-2.0
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { core } from '@h-ai/core';
|
|
2
|
+
|
|
3
|
+
// src/payment-types.ts
|
|
4
|
+
var PaymentErrorInfo = {
|
|
5
|
+
CREATE_ORDER_FAILED: "001:500",
|
|
6
|
+
QUERY_ORDER_FAILED: "002:500",
|
|
7
|
+
REFUND_FAILED: "003:500",
|
|
8
|
+
CLOSE_ORDER_FAILED: "004:500",
|
|
9
|
+
NOT_INITIALIZED: "010:500",
|
|
10
|
+
SIGN_FAILED: "020:500",
|
|
11
|
+
PROVIDER_NOT_FOUND: "030:404",
|
|
12
|
+
INVALID_AMOUNT: "040:400",
|
|
13
|
+
UNSUPPORTED_TRADE_TYPE: "041:400",
|
|
14
|
+
INVALID_INPUT: "042:400",
|
|
15
|
+
NOTIFY_VERIFY_FAILED: "050:400",
|
|
16
|
+
NOTIFY_PARSE_FAILED: "051:400",
|
|
17
|
+
INVOKE_WEB_FAILED: "060:500",
|
|
18
|
+
CONFIG_ERROR: "070:500"
|
|
19
|
+
};
|
|
20
|
+
var HaiPaymentError = core.error.buildHaiErrorsDef("payment", PaymentErrorInfo);
|
|
21
|
+
|
|
22
|
+
// messages/en-US.json
|
|
23
|
+
var en_US_default = {
|
|
24
|
+
payment_createOrderFailed: "Failed to create payment order",
|
|
25
|
+
payment_queryOrderFailed: "Failed to query order status",
|
|
26
|
+
payment_refundFailed: "Refund request failed",
|
|
27
|
+
payment_closeOrderFailed: "Failed to close order",
|
|
28
|
+
payment_notifyVerifyFailed: "Payment notification verification failed",
|
|
29
|
+
payment_notifyParseFailed: "Failed to parse payment notification",
|
|
30
|
+
payment_signFailed: "Payment signature generation failed",
|
|
31
|
+
payment_providerNotFound: "Payment provider not found",
|
|
32
|
+
payment_invalidCreateOrderInput: "Invalid payment order input",
|
|
33
|
+
payment_invalidAmount: "Invalid payment amount",
|
|
34
|
+
payment_notInitialized: "Payment module not initialized, call payment.init() first",
|
|
35
|
+
payment_wechatBridgeUnavailable: "Wait for the WeChat payment bridge to become available",
|
|
36
|
+
payment_invokeWebFailed: "Failed to invoke web payment",
|
|
37
|
+
payment_configError: "Payment config validation failed: {error}",
|
|
38
|
+
payment_initFailed: "Payment module initialization failed: {error}",
|
|
39
|
+
payment_configFieldRequired: "This config field is required",
|
|
40
|
+
payment_stripeDatabaseRequired: "Stripe payments require an initialized shared reldb database",
|
|
41
|
+
payment_stripeStoreInitFailed: "Failed to initialize Stripe payment persistence",
|
|
42
|
+
payment_stripeKeyInvalid: "Stripe requires a server API key prefixed with sk_test_, sk_live_, rk_test_, or rk_live_",
|
|
43
|
+
payment_unsupportedTradeType: "This payment provider does not support the requested trade type"
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// messages/ja-JP.json
|
|
47
|
+
var ja_JP_default = {
|
|
48
|
+
payment_createOrderFailed: "\u652F\u6255\u3044\u6CE8\u6587\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
49
|
+
payment_queryOrderFailed: "\u6CE8\u6587\u72B6\u6CC1\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
50
|
+
payment_refundFailed: "\u8FD4\u91D1\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
51
|
+
payment_closeOrderFailed: "\u6CE8\u6587\u306E\u7D42\u4E86\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
52
|
+
payment_notifyVerifyFailed: "\u652F\u6255\u3044\u901A\u77E5\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
53
|
+
payment_notifyParseFailed: "\u652F\u6255\u3044\u901A\u77E5\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
54
|
+
payment_signFailed: "\u652F\u6255\u3044\u7F72\u540D\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
55
|
+
payment_providerNotFound: "\u6C7A\u6E08\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
|
|
56
|
+
payment_invalidCreateOrderInput: "\u6C7A\u6E08\u6CE8\u6587\u306E\u5165\u529B\u304C\u7121\u52B9\u3067\u3059",
|
|
57
|
+
payment_invalidAmount: "\u652F\u6255\u91D1\u984D\u304C\u7121\u52B9\u3067\u3059",
|
|
58
|
+
payment_notInitialized: "\u6C7A\u6E08\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bpayment.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
59
|
+
payment_wechatBridgeUnavailable: "WeChat \u6C7A\u6E08\u30D6\u30EA\u30C3\u30B8\u304C\u5229\u7528\u53EF\u80FD\u306B\u306A\u308B\u307E\u3067\u304A\u5F85\u3061\u304F\u3060\u3055\u3044",
|
|
60
|
+
payment_invokeWebFailed: "Web\u6C7A\u6E08\u306E\u547C\u3073\u51FA\u3057\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
61
|
+
payment_configError: "\u6C7A\u6E08\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
62
|
+
payment_initFailed: "\u6C7A\u6E08\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
63
|
+
payment_configFieldRequired: "\u3053\u306E\u8A2D\u5B9A\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059",
|
|
64
|
+
payment_stripeDatabaseRequired: "Stripe \u6C7A\u6E08\u306B\u306F\u521D\u671F\u5316\u6E08\u307F\u306E\u5171\u6709 reldb \u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u5FC5\u8981\u3067\u3059",
|
|
65
|
+
payment_stripeStoreInitFailed: "Stripe \u6C7A\u6E08\u306E\u6C38\u7D9A\u5316\u30B9\u30C8\u30A2\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
66
|
+
payment_stripeKeyInvalid: "Stripe \u306B\u306F sk_test_\u3001sk_live_\u3001rk_test_\u3001rk_live_ \u306E\u3044\u305A\u308C\u304B\u3067\u59CB\u307E\u308B\u30B5\u30FC\u30D0\u30FC API \u30AD\u30FC\u304C\u5FC5\u8981\u3067\u3059",
|
|
67
|
+
payment_unsupportedTradeType: "\u3053\u306E\u6C7A\u6E08\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306F\u6307\u5B9A\u3055\u308C\u305F\u53D6\u5F15\u7A2E\u5225\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093"
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// messages/zh-CN.json
|
|
71
|
+
var zh_CN_default = {
|
|
72
|
+
payment_createOrderFailed: "\u521B\u5EFA\u652F\u4ED8\u8BA2\u5355\u5931\u8D25",
|
|
73
|
+
payment_queryOrderFailed: "\u67E5\u8BE2\u8BA2\u5355\u72B6\u6001\u5931\u8D25",
|
|
74
|
+
payment_refundFailed: "\u9000\u6B3E\u8BF7\u6C42\u5931\u8D25",
|
|
75
|
+
payment_closeOrderFailed: "\u5173\u95ED\u8BA2\u5355\u5931\u8D25",
|
|
76
|
+
payment_notifyVerifyFailed: "\u652F\u4ED8\u56DE\u8C03\u9A8C\u7B7E\u5931\u8D25",
|
|
77
|
+
payment_notifyParseFailed: "\u89E3\u6790\u652F\u4ED8\u56DE\u8C03\u5931\u8D25",
|
|
78
|
+
payment_signFailed: "\u652F\u4ED8\u7B7E\u540D\u751F\u6210\u5931\u8D25",
|
|
79
|
+
payment_providerNotFound: "\u672A\u627E\u5230\u652F\u4ED8 Provider",
|
|
80
|
+
payment_invalidCreateOrderInput: "\u652F\u4ED8\u8BA2\u5355\u53C2\u6570\u65E0\u6548",
|
|
81
|
+
payment_invalidAmount: "\u652F\u4ED8\u91D1\u989D\u65E0\u6548",
|
|
82
|
+
payment_notInitialized: "\u652F\u4ED8\u6A21\u5757\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 payment.init()",
|
|
83
|
+
payment_wechatBridgeUnavailable: "\u8BF7\u7B49\u5F85\u5FAE\u4FE1\u652F\u4ED8\u6865\u63A5\u5BF9\u8C61\u5C31\u7EEA\u540E\u91CD\u8BD5",
|
|
84
|
+
payment_invokeWebFailed: "\u8C03\u8D77\u7F51\u9875\u652F\u4ED8\u5931\u8D25",
|
|
85
|
+
payment_configError: "\u652F\u4ED8\u6A21\u5757\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
|
|
86
|
+
payment_initFailed: "\u652F\u4ED8\u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25\uFF1A{error}",
|
|
87
|
+
payment_configFieldRequired: "\u6B64\u914D\u7F6E\u5B57\u6BB5\u5FC5\u586B",
|
|
88
|
+
payment_stripeDatabaseRequired: "Stripe \u652F\u4ED8\u8981\u6C42\u5148\u521D\u59CB\u5316\u5171\u4EAB\u5173\u7CFB\u6570\u636E\u5E93 reldb",
|
|
89
|
+
payment_stripeStoreInitFailed: "Stripe \u652F\u4ED8\u6301\u4E45\u5316\u4ED3\u5E93\u521D\u59CB\u5316\u5931\u8D25",
|
|
90
|
+
payment_stripeKeyInvalid: "Stripe \u5BC6\u94A5\u987B\u4E3A sk_test_\u3001sk_live_\u3001rk_test_ \u6216 rk_live_ \u5F00\u5934\u7684\u670D\u52A1\u7AEF API Key",
|
|
91
|
+
payment_unsupportedTradeType: "\u8BE5\u652F\u4ED8\u6E20\u9053\u4E0D\u652F\u6301\u6B64\u652F\u4ED8\u573A\u666F"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/payment-i18n.ts
|
|
95
|
+
var paymentM = core.i18n.createMessageGetter({
|
|
96
|
+
"zh-CN": zh_CN_default,
|
|
97
|
+
"en-US": en_US_default,
|
|
98
|
+
"ja-JP": ja_JP_default
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export { HaiPaymentError, paymentM };
|
|
102
|
+
//# sourceMappingURL=chunk-YQD4KPT5.js.map
|
|
103
|
+
//# sourceMappingURL=chunk-YQD4KPT5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payment-types.ts","../messages/en-US.json","../messages/ja-JP.json","../messages/zh-CN.json","../src/payment-i18n.ts"],"names":["core"],"mappings":"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","file":"chunk-YQD4KPT5.js","sourcesContent":["/**\n * @h-ai/payment — 类型定义\n *\n * 统一支付模块的公共类型、Provider 接口与错误类型。\n * @module payment-types\n */\n\nimport type { ErrorInfo, HaiResult } from '@h-ai/core'\nimport type { PaymentConfig, PaymentConfigInput } from './payment-config.js'\nimport { core } from '@h-ai/core'\n\n// ─── 错误定义(照 @h-ai/core 范式) ───\n\nconst PaymentErrorInfo = {\n CREATE_ORDER_FAILED: '001:500',\n QUERY_ORDER_FAILED: '002:500',\n REFUND_FAILED: '003:500',\n CLOSE_ORDER_FAILED: '004:500',\n NOT_INITIALIZED: '010:500',\n SIGN_FAILED: '020:500',\n PROVIDER_NOT_FOUND: '030:404',\n INVALID_AMOUNT: '040:400',\n UNSUPPORTED_TRADE_TYPE: '041:400',\n INVALID_INPUT: '042:400',\n NOTIFY_VERIFY_FAILED: '050:400',\n NOTIFY_PARSE_FAILED: '051:400',\n INVOKE_WEB_FAILED: '060:500',\n CONFIG_ERROR: '070:500',\n} as const satisfies ErrorInfo\n\n/**\n * 支付错误码定义;调用方先判断 HaiResult.success,再按 code 分类处理。\n * @example\n * ```ts\n * import { payment, HaiPaymentError } from '@h-ai/payment'\n * const result = await payment.queryOrder('stripe', 'ORDER_1')\n * if (!result.success && result.error.code === HaiPaymentError.NOT_INITIALIZED.code) {\n * // 应用启动流程应检查 init 结果,不能把未就绪查询当成未付款。\n * throw new Error(result.error.message)\n * }\n * ```\n */\nexport const HaiPaymentError = core.error.buildHaiErrorsDef('payment', PaymentErrorInfo)\n\n// ─── 支付场景 ───\n\n/**\n * 已实现的支付场景:微信 jsapi,支付宝/Stripe h5;各 Provider 不接受其它组合。\n * @example\n * ```ts\n * import type { TradeType } from '@h-ai/payment'\n * const wechatScene: TradeType = 'jsapi'\n * const hostedPageScene: TradeType = 'h5'\n * ```\n */\nexport type TradeType = 'jsapi' | 'h5'\n\n/**\n * 订单状态\n * @example\n * ```ts\n * import type { OrderStatusValue } from '@h-ai/payment'\n * function isPaid(status: OrderStatusValue): boolean {\n * return status === 'paid' // pending、closed 和客户端跳转均不能证明付款。\n * }\n * ```\n */\nexport type OrderStatusValue = 'pending' | 'paid' | 'closed' | 'refunded' | 'failed'\n\n// ─── 创建订单 ───\n\n/**\n * 创建订单入参\n * @example\n * ```ts\n * import type { CreateOrderInput } from '@h-ai/payment'\n * const input: CreateOrderInput = {\n * orderNo: 'ORDER_1', amount: 100, currency: 'CNY', description: '测试商品',\n * tradeType: 'h5', notifyUrl: 'https://api.example.com/payment/notify/stripe',\n * }\n * // 微信改为 jsapi,并提供属于服务端 appId 的 openid;Stripe/支付宝使用 h5。\n * ```\n */\nexport interface CreateOrderInput {\n /** 商户订单号 */\n orderNo: string\n /** 金额(分) */\n amount: number\n /** 大写币种;微信/支付宝使用 CNY,Stripe 省略时使用 USD,必须与业务订单一致。 */\n currency?: string\n /** 商品描述 */\n description: string\n /** 支付场景:微信仅 jsapi,支付宝仅 h5,Stripe 仅 h5(托管 Checkout 页面)。 */\n tradeType: TradeType\n /** 微信公众号 openid,微信 jsapi 必填;必须属于服务端配置的 appId。 */\n userId?: string\n /** 异步通知 URL */\n notifyUrl: string\n /** 附加数据(透传) */\n metadata?: Record<string, string>\n}\n\n/**\n * 支付订单(返回给客户端调起支付)\n * @example\n * ```ts\n * import { payment, type PaymentOrder } from '@h-ai/payment'\n * // 应用已初始化 Stripe 与其共享 reldb;订单号由业务持久化生成。\n * const created = await payment.createOrder('stripe', {\n * orderNo: 'ORDER_1', amount: 100, currency: 'CNY', description: '测试商品',\n * tradeType: 'h5', notifyUrl: 'https://api.example.com/payment/notify/stripe',\n * })\n * if (!created.success) throw new Error(created.error.message)\n * const order: PaymentOrder = created.data\n * order.clientParams // 经受信应用接口交给 @h-ai/payment/client,不含商户私钥。\n * ```\n */\nexport interface PaymentOrder {\n /** Provider 名称 */\n provider: string\n /** 支付场景 */\n tradeType: TradeType\n /** 微信 JSAPI 签名字段、支付宝 { gateway, fields } POST 参数或 Stripe { checkoutUrl };不含 HTML。 */\n clientParams: Record<string, unknown>\n /** 预支付 ID(如微信 prepay_id) */\n prepayId?: string\n}\n\n// ─── 回调通知 ───\n\n/**\n * 回调请求(原始 HTTP 数据)\n * @example\n * ```ts\n * import type { PaymentNotifyRequest } from '@h-ai/payment'\n * async function rawNotification(request: Request): Promise<PaymentNotifyRequest> {\n * return { body: await request.text(), headers: Object.fromEntries(request.headers) }\n * // 必须保留原始 body;不能先 JSON.parse 再序列化后验签。\n * }\n * ```\n */\nexport interface PaymentNotifyRequest {\n /** 请求 body */\n body: string\n /** 请求 headers */\n headers: Record<string, string>\n}\n\n/**\n * 回调解析结果;验签与解析成功不等于已付款,履约前必须检查 status。\n *\n * 宿主需用共享持久存储核对订单、金额、币种并去重,阻止乱序事件倒退已完成状态。\n * @example\n * ```ts\n * import { payment, type PaymentNotifyRequest } from '@h-ai/payment'\n * async function verifyNotification(request: PaymentNotifyRequest) {\n * const result = await payment.handleNotify('stripe', request)\n * if (!result.success) return result\n * if (result.data.status === 'paid') {\n * // 宿主在共享业务事务中核对商户订单、金额、币种,并按 eventId 持久去重后履约。\n * }\n * return result // 验签成功的 pending 结果仍然不能发货。\n * }\n * ```\n */\nexport interface PaymentNotifyResult {\n /** 商户订单号 */\n orderNo: string\n /** 支付渠道交易号 */\n transactionId: string\n /** 渠道通知事件 ID:微信 id、支付宝 notify_id、Stripe event.id;宿主按渠道/商户命名空间持久去重。 */\n eventId: string\n /** 支付金额(分) */\n amount: number\n /** ISO 4217 大写币种;支付宝为 CNY,宿主必须与订单核对。 */\n currency: string\n /** 支付状态 */\n status: OrderStatusValue\n /** 渠道支付时间;Stripe 为支付成功事件的创建时间,非本次通知接收时间。 */\n paidAt?: Date\n /** 原始数据 */\n raw?: Record<string, unknown>\n}\n\n// ─── 查询订单 ───\n\n/**\n * 订单状态查询结果\n * @example\n * ```ts\n * import { payment, type OrderStatus } from '@h-ai/payment'\n * const result = await payment.queryOrder('stripe', 'ORDER_1')\n * if (!result.success) throw new Error(result.error.message)\n * const status: OrderStatus = result.data\n * const hasPaymentConfirmation = status.status === 'paid'\n * ```\n */\nexport interface OrderStatus {\n /** 商户订单号 */\n orderNo: string\n /** 渠道交易号 */\n transactionId?: string\n /** 订单状态 */\n status: OrderStatusValue\n /** 金额(分) */\n amount: number\n /** 支付时间 */\n paidAt?: Date\n}\n\n// ─── 退款 ───\n\n/**\n * 退款入参\n * @example\n * ```ts\n * import type { RefundInput } from '@h-ai/payment'\n * const refund: RefundInput = {\n * orderNo: 'ORDER_1', refundNo: 'REFUND_1', amount: 50, totalAmount: 100, reason: '部分退货',\n * }\n * // 同一业务退款重试复用 refundNo,不能变更金额;微信必须提供原订单总额。\n * ```\n */\nexport interface RefundInput {\n /** 商户订单号 */\n orderNo: string\n /** 退款单号 */\n refundNo: string\n /** 退款金额(分) */\n amount: number\n /** 订单总金额(分),微信退款必填 */\n totalAmount?: number\n /** 退款原因 */\n reason?: string\n}\n\n/**\n * 退款结果\n * @example\n * ```ts\n * import { payment, type RefundResult } from '@h-ai/payment'\n * const result = await payment.refund('stripe', { orderNo: 'ORDER_1', refundNo: 'REFUND_1', amount: 50 })\n * if (!result.success) throw new Error(result.error.message)\n * const refund: RefundResult = result.data\n * const confirmed = refund.status === 'success' // processing 仅表示仍待渠道确认。\n * ```\n */\nexport interface RefundResult {\n /** 退款单号 */\n refundNo: string\n /** 渠道退款标识;支付宝返回原交易号,需同时使用 refundNo 标识本次退款。 */\n refundId: string\n /** 退款状态;支付宝 fund_change 为 N 或缺失时是 processing,须向渠道确认后才能判定退款成功。 */\n status: 'processing' | 'success' | 'failed'\n}\n\n// ─── Provider 接口 ───\n\n/**\n * 支付 Provider 接口\n *\n * 各支付渠道(微信/支付宝/Stripe)实现此接口,\n * 由 payment 模块统一调度。\n * @example\n * ```ts\n * import { payment, type PaymentProvider } from '@h-ai/payment'\n * function registerPartner(provider: PaymentProvider) {\n * // 实现方负责完整的创建、验签解析、查单、退款和关单契约,并返回 HaiResult。\n * payment.registerProvider(provider)\n * return payment.getProvider(provider.name)\n * }\n * ```\n */\nexport interface PaymentProvider {\n /** Provider 名称(如 'wechat', 'alipay', 'stripe') */\n readonly name: string\n\n /** 创建完整客户端契约的订单;unsupported 场景返回失败,不能以空参数占位成功。 */\n createOrder: (input: CreateOrderInput) => Promise<HaiResult<PaymentOrder>>\n\n /** 对原始通知验签并解析;成功可为 pending,不能代替业务核对、持久去重和履约事务。 */\n handleNotify: (request: PaymentNotifyRequest) => Promise<HaiResult<PaymentNotifyResult>>\n\n /** 读取并核对指定商户订单的渠道状态;未知订单或未知状态必须返回失败。 */\n queryOrder: (orderNo: string) => Promise<HaiResult<OrderStatus>>\n\n /** 申请指定 refundNo 的退款;成功后还要区分 processing 与资金确认 success。 */\n refund: (input: RefundInput) => Promise<HaiResult<RefundResult>>\n\n /** 只有渠道确认关闭/过期才成功;禁止无操作返回成功或关闭已付款订单。 */\n closeOrder: (orderNo: string) => Promise<HaiResult<void>>\n}\n\n// ─── 函数接口 ───\n\n/**\n * 支付模块函数接口\n * @example\n * ```ts\n * import { payment, type PaymentFunctions } from '@h-ai/payment'\n * const service: PaymentFunctions = payment\n * if (!service.isInitialized) throw new Error('Payment is not initialized')\n * const result = await service.queryOrder('stripe', 'ORDER_1')\n * if (!result.success) throw new Error(result.error.message)\n * ```\n */\nexport interface PaymentFunctions {\n /** 按 Schema 注册渠道;Stripe 要求共享 reldb 已初始化,失败后不能启动依赖支付的业务。 */\n init: (config: PaymentConfigInput) => Promise<HaiResult<void>>\n /** 清理本进程注册表与配置;不会删除持久幂等记录或关闭宿主 reldb,不返回 HaiResult。 */\n close: () => Promise<void>\n /** 当前解析后的配置,关闭或未初始化时为 null;包含密钥,禁止下发浏览器或记录日志。 */\n readonly config: PaymentConfig | null\n /** 是否已有成功发布的配置;不代表渠道实时可用或商户凭据已被远端验证。 */\n readonly isInitialized: boolean\n /** 创建订单并返回对应场景的最小客户端参数;调用方必须先检查 success。 */\n createOrder: (providerName: string, input: CreateOrderInput) => Promise<HaiResult<PaymentOrder>>\n /** 验签并解析原始回调;宿主核对金额/币种并持久去重后才可确认业务履约。 */\n handleNotify: (providerName: string, request: PaymentNotifyRequest) => Promise<HaiResult<PaymentNotifyResult>>\n /** 查渠道状态;失败不能映射成未付款或直接重建订单。 */\n queryOrder: (providerName: string, orderNo: string) => Promise<HaiResult<OrderStatus>>\n /** 退款标识与参数保持稳定;processing 仍需渠道核对,不能视作最终退款成功。 */\n refund: (providerName: string, input: RefundInput) => Promise<HaiResult<RefundResult>>\n /** 主动关单;Stripe 只在确认 Session expired 后成功,页面返回不是关单证明。 */\n closeOrder: (providerName: string, orderNo: string) => Promise<HaiResult<void>>\n /** 按名称读取本进程 Provider;未注册时为 undefined,应用通常使用统一操作入口。 */\n getProvider: (name: string) => PaymentProvider | undefined\n /** 注册完整实现 PaymentProvider 的自定义渠道;同名会替换本进程注册项,生命周期由宿主协调。 */\n registerProvider: (provider: PaymentProvider) => void\n}\n","{\n \"payment_createOrderFailed\": \"Failed to create payment order\",\n \"payment_queryOrderFailed\": \"Failed to query order status\",\n \"payment_refundFailed\": \"Refund request failed\",\n \"payment_closeOrderFailed\": \"Failed to close order\",\n \"payment_notifyVerifyFailed\": \"Payment notification verification failed\",\n \"payment_notifyParseFailed\": \"Failed to parse payment notification\",\n \"payment_signFailed\": \"Payment signature generation failed\",\n \"payment_providerNotFound\": \"Payment provider not found\",\n \"payment_invalidCreateOrderInput\": \"Invalid payment order input\",\n \"payment_invalidAmount\": \"Invalid payment amount\",\n \"payment_notInitialized\": \"Payment module not initialized, call payment.init() first\",\n \"payment_wechatBridgeUnavailable\": \"Wait for the WeChat payment bridge to become available\",\n \"payment_invokeWebFailed\": \"Failed to invoke web payment\",\n \"payment_configError\": \"Payment config validation failed: {error}\",\n \"payment_initFailed\": \"Payment module initialization failed: {error}\",\n \"payment_configFieldRequired\": \"This config field is required\",\n \"payment_stripeDatabaseRequired\": \"Stripe payments require an initialized shared reldb database\",\n \"payment_stripeStoreInitFailed\": \"Failed to initialize Stripe payment persistence\",\n \"payment_stripeKeyInvalid\": \"Stripe requires a server API key prefixed with sk_test_, sk_live_, rk_test_, or rk_live_\",\n \"payment_unsupportedTradeType\": \"This payment provider does not support the requested trade type\"\n}\n","{\n \"payment_createOrderFailed\": \"支払い注文の作成に失敗しました\",\n \"payment_queryOrderFailed\": \"注文状況の取得に失敗しました\",\n \"payment_refundFailed\": \"返金リクエストに失敗しました\",\n \"payment_closeOrderFailed\": \"注文の終了に失敗しました\",\n \"payment_notifyVerifyFailed\": \"支払い通知の検証に失敗しました\",\n \"payment_notifyParseFailed\": \"支払い通知の解析に失敗しました\",\n \"payment_signFailed\": \"支払い署名の生成に失敗しました\",\n \"payment_providerNotFound\": \"決済プロバイダーが見つかりません\",\n \"payment_invalidCreateOrderInput\": \"決済注文の入力が無効です\",\n \"payment_invalidAmount\": \"支払金額が無効です\",\n \"payment_notInitialized\": \"決済モジュールが初期化されていません。先にpayment.init()を呼び出してください\",\n \"payment_wechatBridgeUnavailable\": \"WeChat 決済ブリッジが利用可能になるまでお待ちください\",\n \"payment_invokeWebFailed\": \"Web決済の呼び出しに失敗しました\",\n \"payment_configError\": \"決済設定の検証に失敗しました: {error}\",\n \"payment_initFailed\": \"決済モジュールの初期化に失敗しました: {error}\",\n \"payment_configFieldRequired\": \"この設定項目は必須です\",\n \"payment_stripeDatabaseRequired\": \"Stripe 決済には初期化済みの共有 reldb データベースが必要です\",\n \"payment_stripeStoreInitFailed\": \"Stripe 決済の永続化ストアの初期化に失敗しました\",\n \"payment_stripeKeyInvalid\": \"Stripe には sk_test_、sk_live_、rk_test_、rk_live_ のいずれかで始まるサーバー API キーが必要です\",\n \"payment_unsupportedTradeType\": \"この決済プロバイダーは指定された取引種別に対応していません\"\n}\n","{\n \"payment_createOrderFailed\": \"创建支付订单失败\",\n \"payment_queryOrderFailed\": \"查询订单状态失败\",\n \"payment_refundFailed\": \"退款请求失败\",\n \"payment_closeOrderFailed\": \"关闭订单失败\",\n \"payment_notifyVerifyFailed\": \"支付回调验签失败\",\n \"payment_notifyParseFailed\": \"解析支付回调失败\",\n \"payment_signFailed\": \"支付签名生成失败\",\n \"payment_providerNotFound\": \"未找到支付 Provider\",\n \"payment_invalidCreateOrderInput\": \"支付订单参数无效\",\n \"payment_invalidAmount\": \"支付金额无效\",\n \"payment_notInitialized\": \"支付模块未初始化,请先调用 payment.init()\",\n \"payment_wechatBridgeUnavailable\": \"请等待微信支付桥接对象就绪后重试\",\n \"payment_invokeWebFailed\": \"调起网页支付失败\",\n \"payment_configError\": \"支付模块配置校验失败:{error}\",\n \"payment_initFailed\": \"支付模块初始化失败:{error}\",\n \"payment_configFieldRequired\": \"此配置字段必填\",\n \"payment_stripeDatabaseRequired\": \"Stripe 支付要求先初始化共享关系数据库 reldb\",\n \"payment_stripeStoreInitFailed\": \"Stripe 支付持久化仓库初始化失败\",\n \"payment_stripeKeyInvalid\": \"Stripe 密钥须为 sk_test_、sk_live_、rk_test_ 或 rk_live_ 开头的服务端 API Key\",\n \"payment_unsupportedTradeType\": \"该支付渠道不支持此支付场景\"\n}\n","/**\n * @h-ai/payment — i18n\n *\n * 模块 i18n 文案访问入口。\n * @module payment-i18n\n */\n\nimport { core } from '@h-ai/core'\nimport messagesEnUS from '../messages/en-US.json'\nimport messagesJaJP from '../messages/ja-JP.json'\nimport messagesZhCN from '../messages/zh-CN.json'\n\n/**\n * 支付模块本地化消息键;编译时约束受维护的双语字典。\n * @internal\n * @example 模块内部上下文\n * ```ts\n * const key: PaymentMessageKey = 'payment_notInitialized'\n * const localized = paymentM(key)\n * ```\n */\ntype PaymentMessageKey = keyof typeof messagesZhCN\n\n/**\n * payment 模块 i18n 文案获取器\n *\n * @param key - 文案 Key\n * @returns 本地化后的文案字符串\n * @internal\n * @example 模块内部上下文\n * ```ts\n * const message = paymentM('payment_notInitialized')\n * const error = new Error(message)\n * ```\n */\nexport const paymentM = core.i18n.createMessageGetter<PaymentMessageKey>({\n 'zh-CN': messagesZhCN,\n 'en-US': messagesEnUS,\n 'ja-JP': messagesJaJP,\n})\n"]}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as PaymentOrder } from '../payment-types-D1GvLKHg.js';
|
|
2
1
|
import { HaiResult } from '@h-ai/core';
|
|
2
|
+
import { P as PaymentOrder } from '../payment-types-D02t-Pc9.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,49 +8,50 @@ import 'zod';
|
|
|
8
8
|
* 客户端支付调起相关类型。
|
|
9
9
|
* @module payment-client-types
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 支付调起结果
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import type { InvokePaymentResult } from '@h-ai/payment/client'
|
|
16
|
+
* function needsRetry(result: InvokePaymentResult): boolean {
|
|
17
|
+
* return !result.invoked // invoked=true 仍不证明付款,可能只是提交页面/桥接请求。
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
13
21
|
interface InvokePaymentResult {
|
|
14
|
-
/**
|
|
22
|
+
/** 是否已经提交客户端调起;即使用户取消也可能为 true,不能作为付款证明。 */
|
|
15
23
|
invoked: boolean;
|
|
16
24
|
/** 额外信息 */
|
|
17
25
|
message?: string;
|
|
18
26
|
}
|
|
19
|
-
/** 支付调起选项 */
|
|
20
|
-
interface InvokePaymentOptions {
|
|
21
|
-
/** 支付订单(来自后端 createOrder 返回) */
|
|
22
|
-
order: PaymentOrder;
|
|
23
|
-
/** 成功回调 URL(Web 跳转场景) */
|
|
24
|
-
successUrl?: string;
|
|
25
|
-
/** 取消回调 URL */
|
|
26
|
-
cancelUrl?: string;
|
|
27
|
-
}
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
* @h-ai/payment/client —
|
|
29
|
+
* @h-ai/payment/client — 浏览器支付调起
|
|
31
30
|
*
|
|
32
|
-
*
|
|
31
|
+
* 微信 JSAPI、支付宝 WAP 表单与 Stripe 托管页面使用各自完整的客户端协议。
|
|
33
32
|
* @module payment-client-web
|
|
34
33
|
*/
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
36
|
+
* 按服务端返回的渠道与场景调起支付;不支持的组合直接返回错误。
|
|
38
37
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @param order - 后端返回的支付订单
|
|
42
|
-
* @returns 调起结果
|
|
38
|
+
* 调用前等待微信 WeixinJSBridgeReady;invoked 只表示已提交调起,付款状态由服务端确认。
|
|
43
39
|
*
|
|
40
|
+
* @param order - 服务端创建的支付订单
|
|
41
|
+
* @returns 调起结果;失败时应向用户提示并允许重试。
|
|
44
42
|
* @example
|
|
45
43
|
* ```ts
|
|
46
44
|
* import { invokePayment } from '@h-ai/payment/client'
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* const result = await invokePayment(
|
|
45
|
+
* import type { PaymentOrder } from '@h-ai/payment'
|
|
46
|
+
* async function pay(orderFromServer: PaymentOrder) {
|
|
47
|
+
* // 浏览器环境;微信先等待 WeixinJSBridgeReady。参数来自受信服务端创建订单接口。
|
|
48
|
+
* const result = await invokePayment(orderFromServer)
|
|
49
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
50
|
+
* if (!result.data.invoked) throw new Error(result.data.message)
|
|
51
|
+
* // 继续等待服务端查询/通知确认,不能依据 invoked、页面跳转或用户返回就发货。
|
|
51
52
|
* }
|
|
52
53
|
* ```
|
|
53
54
|
*/
|
|
54
55
|
declare function invokePayment(order: PaymentOrder): Promise<HaiResult<InvokePaymentResult>>;
|
|
55
56
|
|
|
56
|
-
export { type
|
|
57
|
+
export { type InvokePaymentResult, invokePayment };
|