@h-ai/payment 0.1.0-alpha9 → 1.0.0-beta.10
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
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import * as _h_ai_core from '@h-ai/core';
|
|
2
|
+
import { HaiResult } from '@h-ai/core';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @h-ai/payment — 错误码 + 配置 Schema
|
|
7
|
+
*
|
|
8
|
+
* 支付模块的错误码、Zod 配置 Schema 与配置类型。
|
|
9
|
+
* @module payment-config
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 微信公众号 JSAPI 配置 Schema;平台密钥按受信 serial 精确配置。
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { WechatPayConfigSchema } from '@h-ai/payment'
|
|
17
|
+
* // 部署系统注入可信配置;Schema 校验形状,不证明密钥、证书或渠道账户可用。
|
|
18
|
+
* const platformKeys: unknown = JSON.parse(process.env.HAI_PAYMENT_WECHAT_PLATFORMKEYS ?? '{}')
|
|
19
|
+
* const parsed = WechatPayConfigSchema.safeParse({
|
|
20
|
+
* mchId: process.env.HAI_PAYMENT_WECHAT_MCHID,
|
|
21
|
+
* apiV3Key: process.env.HAI_PAYMENT_WECHAT_APIV3KEY,
|
|
22
|
+
* serialNo: process.env.HAI_PAYMENT_WECHAT_SERIALNO,
|
|
23
|
+
* privateKey: process.env.HAI_PAYMENT_WECHAT_PRIVATEKEY,
|
|
24
|
+
* platformKeys,
|
|
25
|
+
* appId: process.env.HAI_PAYMENT_WECHAT_APPID,
|
|
26
|
+
* })
|
|
27
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
28
|
+
* const config = parsed.data
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare const WechatPayConfigSchema: z.ZodObject<{
|
|
32
|
+
mchId: z.ZodString;
|
|
33
|
+
apiV3Key: z.ZodString;
|
|
34
|
+
serialNo: z.ZodString;
|
|
35
|
+
privateKey: z.ZodString;
|
|
36
|
+
platformKeys: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
37
|
+
appId: z.ZodString;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
/**
|
|
40
|
+
* 支付宝手机网站支付配置 Schema;returnUrl 为可信 HTTPS 导航地址。
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { AlipayConfigSchema } from '@h-ai/payment'
|
|
44
|
+
* // 部署系统注入可信配置;Schema 校验形状,不证明密钥、证书或渠道账户可用。
|
|
45
|
+
* const parsed = AlipayConfigSchema.safeParse({
|
|
46
|
+
* appId: process.env.HAI_PAYMENT_ALIPAY_APPID,
|
|
47
|
+
* sellerId: process.env.HAI_PAYMENT_ALIPAY_SELLERID,
|
|
48
|
+
* privateKey: process.env.HAI_PAYMENT_ALIPAY_PRIVATEKEY,
|
|
49
|
+
* alipayPublicKey: process.env.HAI_PAYMENT_ALIPAY_ALIPAYPUBLICKEY,
|
|
50
|
+
* returnUrl: 'https://shop.example.com/payment/result',
|
|
51
|
+
* })
|
|
52
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
53
|
+
* const config = parsed.data
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare const AlipayConfigSchema: z.ZodObject<{
|
|
57
|
+
appId: z.ZodString;
|
|
58
|
+
sellerId: z.ZodString;
|
|
59
|
+
privateKey: z.ZodString;
|
|
60
|
+
alipayPublicKey: z.ZodString;
|
|
61
|
+
returnUrl: z.ZodURL;
|
|
62
|
+
signType: z.ZodDefault<z.ZodEnum<{
|
|
63
|
+
RSA2: "RSA2";
|
|
64
|
+
RSA: "RSA";
|
|
65
|
+
}>>;
|
|
66
|
+
sandbox: z.ZodDefault<z.ZodBoolean>;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
/**
|
|
69
|
+
* Stripe 托管 Checkout 配置 Schema;初始化前要求共享 reldb 就绪。
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* import { StripeConfigSchema } from '@h-ai/payment'
|
|
73
|
+
* // 部署系统注入可信配置;Schema 校验形状,不证明密钥、证书或渠道账户可用。
|
|
74
|
+
* const parsed = StripeConfigSchema.safeParse({
|
|
75
|
+
* secretKey: process.env.HAI_PAYMENT_STRIPE_SECRETKEY,
|
|
76
|
+
* webhookSecret: process.env.HAI_PAYMENT_STRIPE_WEBHOOKSECRET,
|
|
77
|
+
* successUrl: 'https://shop.example.com/payment/result?session={CHECKOUT_SESSION_ID}',
|
|
78
|
+
* })
|
|
79
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
80
|
+
* const config = parsed.data
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare const StripeConfigSchema: z.ZodObject<{
|
|
84
|
+
secretKey: z.ZodString;
|
|
85
|
+
webhookSecret: z.ZodString;
|
|
86
|
+
successUrl: z.ZodURL;
|
|
87
|
+
cancelUrl: z.ZodOptional<z.ZodURL>;
|
|
88
|
+
webhookToleranceSeconds: z.ZodDefault<z.ZodNumber>;
|
|
89
|
+
}, z.core.$strict>;
|
|
90
|
+
/**
|
|
91
|
+
* 支付模块配置 Schema;按配置注册渠道,密钥与运行时依赖由宿主提供。
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { PaymentConfigSchema } from '@h-ai/payment'
|
|
95
|
+
* const parsed = PaymentConfigSchema.safeParse({ stripe: {
|
|
96
|
+
* secretKey: process.env.HAI_PAYMENT_STRIPE_SECRETKEY,
|
|
97
|
+
* webhookSecret: process.env.HAI_PAYMENT_STRIPE_WEBHOOKSECRET,
|
|
98
|
+
* successUrl: 'https://shop.example.com/payment/result?session={CHECKOUT_SESSION_ID}',
|
|
99
|
+
* } })
|
|
100
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
101
|
+
* const toleranceSeconds = parsed.data.stripe?.webhookToleranceSeconds
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare const PaymentConfigSchema: z.ZodObject<{
|
|
105
|
+
wechat: z.ZodOptional<z.ZodObject<{
|
|
106
|
+
mchId: z.ZodString;
|
|
107
|
+
apiV3Key: z.ZodString;
|
|
108
|
+
serialNo: z.ZodString;
|
|
109
|
+
privateKey: z.ZodString;
|
|
110
|
+
platformKeys: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
111
|
+
appId: z.ZodString;
|
|
112
|
+
}, z.core.$strip>>;
|
|
113
|
+
alipay: z.ZodOptional<z.ZodObject<{
|
|
114
|
+
appId: z.ZodString;
|
|
115
|
+
sellerId: z.ZodString;
|
|
116
|
+
privateKey: z.ZodString;
|
|
117
|
+
alipayPublicKey: z.ZodString;
|
|
118
|
+
returnUrl: z.ZodURL;
|
|
119
|
+
signType: z.ZodDefault<z.ZodEnum<{
|
|
120
|
+
RSA2: "RSA2";
|
|
121
|
+
RSA: "RSA";
|
|
122
|
+
}>>;
|
|
123
|
+
sandbox: z.ZodDefault<z.ZodBoolean>;
|
|
124
|
+
}, z.core.$strip>>;
|
|
125
|
+
stripe: z.ZodOptional<z.ZodObject<{
|
|
126
|
+
secretKey: z.ZodString;
|
|
127
|
+
webhookSecret: z.ZodString;
|
|
128
|
+
successUrl: z.ZodURL;
|
|
129
|
+
cancelUrl: z.ZodOptional<z.ZodURL>;
|
|
130
|
+
webhookToleranceSeconds: z.ZodDefault<z.ZodNumber>;
|
|
131
|
+
}, z.core.$strict>>;
|
|
132
|
+
}, z.core.$strip>;
|
|
133
|
+
/**
|
|
134
|
+
* PaymentConfigSchema 解析后的配置类型;不能把未解析输入当作该类型。
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { PaymentConfigSchema, type PaymentConfig } from '@h-ai/payment'
|
|
138
|
+
* const parsed = PaymentConfigSchema.safeParse({ stripe: {
|
|
139
|
+
* secretKey: process.env.HAI_PAYMENT_STRIPE_SECRETKEY,
|
|
140
|
+
* webhookSecret: process.env.HAI_PAYMENT_STRIPE_WEBHOOKSECRET,
|
|
141
|
+
* successUrl: 'https://shop.example.com/payment/result?session={CHECKOUT_SESSION_ID}',
|
|
142
|
+
* } })
|
|
143
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
144
|
+
* const config: PaymentConfig = parsed.data
|
|
145
|
+
* config.stripe?.webhookToleranceSeconds // 解析输出包含默认的签名容差。
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
type PaymentConfig = z.infer<typeof PaymentConfigSchema>;
|
|
149
|
+
/**
|
|
150
|
+
* 支付初始化输入;允许省略由 Schema 补齐的配置默认值。
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { payment, type PaymentConfigInput } from '@h-ai/payment'
|
|
154
|
+
* async function initializePayment(input: PaymentConfigInput) {
|
|
155
|
+
* // 启用 Stripe 时,宿主已经先初始化共享 reldb 并检查成功。
|
|
156
|
+
* const result = await payment.init(input)
|
|
157
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
type PaymentConfigInput = z.input<typeof PaymentConfigSchema>;
|
|
162
|
+
/**
|
|
163
|
+
* 微信配置的解析输出类型;默认值已经由对应 Schema 补齐。
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* import { WechatPayConfigSchema, type WechatPayConfig } from '@h-ai/payment'
|
|
167
|
+
* const platformKeys: unknown = JSON.parse(process.env.HAI_PAYMENT_WECHAT_PLATFORMKEYS ?? '{}')
|
|
168
|
+
* const parsed = WechatPayConfigSchema.safeParse({
|
|
169
|
+
* mchId: process.env.HAI_PAYMENT_WECHAT_MCHID,
|
|
170
|
+
* apiV3Key: process.env.HAI_PAYMENT_WECHAT_APIV3KEY,
|
|
171
|
+
* serialNo: process.env.HAI_PAYMENT_WECHAT_SERIALNO,
|
|
172
|
+
* privateKey: process.env.HAI_PAYMENT_WECHAT_PRIVATEKEY,
|
|
173
|
+
* platformKeys,
|
|
174
|
+
* appId: process.env.HAI_PAYMENT_WECHAT_APPID,
|
|
175
|
+
* })
|
|
176
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
177
|
+
* const config: WechatPayConfig = parsed.data
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
type WechatPayConfig = z.infer<typeof WechatPayConfigSchema>;
|
|
181
|
+
/**
|
|
182
|
+
* 支付宝配置的解析输出类型;默认值已经由对应 Schema 补齐。
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* import { AlipayConfigSchema, type AlipayConfig } from '@h-ai/payment'
|
|
186
|
+
* const parsed = AlipayConfigSchema.safeParse({
|
|
187
|
+
* appId: process.env.HAI_PAYMENT_ALIPAY_APPID,
|
|
188
|
+
* sellerId: process.env.HAI_PAYMENT_ALIPAY_SELLERID,
|
|
189
|
+
* privateKey: process.env.HAI_PAYMENT_ALIPAY_PRIVATEKEY,
|
|
190
|
+
* alipayPublicKey: process.env.HAI_PAYMENT_ALIPAY_ALIPAYPUBLICKEY,
|
|
191
|
+
* returnUrl: 'https://shop.example.com/payment/result',
|
|
192
|
+
* })
|
|
193
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
194
|
+
* const config: AlipayConfig = parsed.data
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
type AlipayConfig = z.infer<typeof AlipayConfigSchema>;
|
|
198
|
+
/**
|
|
199
|
+
* Stripe配置的解析输出类型;默认值已经由对应 Schema 补齐。
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* import { StripeConfigSchema, type StripeConfig } from '@h-ai/payment'
|
|
203
|
+
* const parsed = StripeConfigSchema.safeParse({
|
|
204
|
+
* secretKey: process.env.HAI_PAYMENT_STRIPE_SECRETKEY,
|
|
205
|
+
* webhookSecret: process.env.HAI_PAYMENT_STRIPE_WEBHOOKSECRET,
|
|
206
|
+
* successUrl: 'https://shop.example.com/payment/result?session={CHECKOUT_SESSION_ID}',
|
|
207
|
+
* })
|
|
208
|
+
* if (!parsed.success) throw new Error('Payment configuration is invalid')
|
|
209
|
+
* const config: StripeConfig = parsed.data
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
type StripeConfig = z.infer<typeof StripeConfigSchema>;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 支付错误码定义;调用方先判断 HaiResult.success,再按 code 分类处理。
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* import { payment, HaiPaymentError } from '@h-ai/payment'
|
|
219
|
+
* const result = await payment.queryOrder('stripe', 'ORDER_1')
|
|
220
|
+
* if (!result.success && result.error.code === HaiPaymentError.NOT_INITIALIZED.code) {
|
|
221
|
+
* // 应用启动流程应检查 init 结果,不能把未就绪查询当成未付款。
|
|
222
|
+
* throw new Error(result.error.message)
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
declare const HaiPaymentError: {
|
|
227
|
+
readonly CREATE_ORDER_FAILED: _h_ai_core.HaiErrorDef;
|
|
228
|
+
readonly QUERY_ORDER_FAILED: _h_ai_core.HaiErrorDef;
|
|
229
|
+
readonly REFUND_FAILED: _h_ai_core.HaiErrorDef;
|
|
230
|
+
readonly CLOSE_ORDER_FAILED: _h_ai_core.HaiErrorDef;
|
|
231
|
+
readonly NOT_INITIALIZED: _h_ai_core.HaiErrorDef;
|
|
232
|
+
readonly SIGN_FAILED: _h_ai_core.HaiErrorDef;
|
|
233
|
+
readonly PROVIDER_NOT_FOUND: _h_ai_core.HaiErrorDef;
|
|
234
|
+
readonly INVALID_AMOUNT: _h_ai_core.HaiErrorDef;
|
|
235
|
+
readonly UNSUPPORTED_TRADE_TYPE: _h_ai_core.HaiErrorDef;
|
|
236
|
+
readonly INVALID_INPUT: _h_ai_core.HaiErrorDef;
|
|
237
|
+
readonly NOTIFY_VERIFY_FAILED: _h_ai_core.HaiErrorDef;
|
|
238
|
+
readonly NOTIFY_PARSE_FAILED: _h_ai_core.HaiErrorDef;
|
|
239
|
+
readonly INVOKE_WEB_FAILED: _h_ai_core.HaiErrorDef;
|
|
240
|
+
readonly CONFIG_ERROR: _h_ai_core.HaiErrorDef;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* 已实现的支付场景:微信 jsapi,支付宝/Stripe h5;各 Provider 不接受其它组合。
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import type { TradeType } from '@h-ai/payment'
|
|
247
|
+
* const wechatScene: TradeType = 'jsapi'
|
|
248
|
+
* const hostedPageScene: TradeType = 'h5'
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
type TradeType = 'jsapi' | 'h5';
|
|
252
|
+
/**
|
|
253
|
+
* 订单状态
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* import type { OrderStatusValue } from '@h-ai/payment'
|
|
257
|
+
* function isPaid(status: OrderStatusValue): boolean {
|
|
258
|
+
* return status === 'paid' // pending、closed 和客户端跳转均不能证明付款。
|
|
259
|
+
* }
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
type OrderStatusValue = 'pending' | 'paid' | 'closed' | 'refunded' | 'failed';
|
|
263
|
+
/**
|
|
264
|
+
* 创建订单入参
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* import type { CreateOrderInput } from '@h-ai/payment'
|
|
268
|
+
* const input: CreateOrderInput = {
|
|
269
|
+
* orderNo: 'ORDER_1', amount: 100, currency: 'CNY', description: '测试商品',
|
|
270
|
+
* tradeType: 'h5', notifyUrl: 'https://api.example.com/payment/notify/stripe',
|
|
271
|
+
* }
|
|
272
|
+
* // 微信改为 jsapi,并提供属于服务端 appId 的 openid;Stripe/支付宝使用 h5。
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
interface CreateOrderInput {
|
|
276
|
+
/** 商户订单号 */
|
|
277
|
+
orderNo: string;
|
|
278
|
+
/** 金额(分) */
|
|
279
|
+
amount: number;
|
|
280
|
+
/** 大写币种;微信/支付宝使用 CNY,Stripe 省略时使用 USD,必须与业务订单一致。 */
|
|
281
|
+
currency?: string;
|
|
282
|
+
/** 商品描述 */
|
|
283
|
+
description: string;
|
|
284
|
+
/** 支付场景:微信仅 jsapi,支付宝仅 h5,Stripe 仅 h5(托管 Checkout 页面)。 */
|
|
285
|
+
tradeType: TradeType;
|
|
286
|
+
/** 微信公众号 openid,微信 jsapi 必填;必须属于服务端配置的 appId。 */
|
|
287
|
+
userId?: string;
|
|
288
|
+
/** 异步通知 URL */
|
|
289
|
+
notifyUrl: string;
|
|
290
|
+
/** 附加数据(透传) */
|
|
291
|
+
metadata?: Record<string, string>;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* 支付订单(返回给客户端调起支付)
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* import { payment, type PaymentOrder } from '@h-ai/payment'
|
|
298
|
+
* // 应用已初始化 Stripe 与其共享 reldb;订单号由业务持久化生成。
|
|
299
|
+
* const created = await payment.createOrder('stripe', {
|
|
300
|
+
* orderNo: 'ORDER_1', amount: 100, currency: 'CNY', description: '测试商品',
|
|
301
|
+
* tradeType: 'h5', notifyUrl: 'https://api.example.com/payment/notify/stripe',
|
|
302
|
+
* })
|
|
303
|
+
* if (!created.success) throw new Error(created.error.message)
|
|
304
|
+
* const order: PaymentOrder = created.data
|
|
305
|
+
* order.clientParams // 经受信应用接口交给 @h-ai/payment/client,不含商户私钥。
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
interface PaymentOrder {
|
|
309
|
+
/** Provider 名称 */
|
|
310
|
+
provider: string;
|
|
311
|
+
/** 支付场景 */
|
|
312
|
+
tradeType: TradeType;
|
|
313
|
+
/** 微信 JSAPI 签名字段、支付宝 { gateway, fields } POST 参数或 Stripe { checkoutUrl };不含 HTML。 */
|
|
314
|
+
clientParams: Record<string, unknown>;
|
|
315
|
+
/** 预支付 ID(如微信 prepay_id) */
|
|
316
|
+
prepayId?: string;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* 回调请求(原始 HTTP 数据)
|
|
320
|
+
* @example
|
|
321
|
+
* ```ts
|
|
322
|
+
* import type { PaymentNotifyRequest } from '@h-ai/payment'
|
|
323
|
+
* async function rawNotification(request: Request): Promise<PaymentNotifyRequest> {
|
|
324
|
+
* return { body: await request.text(), headers: Object.fromEntries(request.headers) }
|
|
325
|
+
* // 必须保留原始 body;不能先 JSON.parse 再序列化后验签。
|
|
326
|
+
* }
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
interface PaymentNotifyRequest {
|
|
330
|
+
/** 请求 body */
|
|
331
|
+
body: string;
|
|
332
|
+
/** 请求 headers */
|
|
333
|
+
headers: Record<string, string>;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 回调解析结果;验签与解析成功不等于已付款,履约前必须检查 status。
|
|
337
|
+
*
|
|
338
|
+
* 宿主需用共享持久存储核对订单、金额、币种并去重,阻止乱序事件倒退已完成状态。
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* import { payment, type PaymentNotifyRequest } from '@h-ai/payment'
|
|
342
|
+
* async function verifyNotification(request: PaymentNotifyRequest) {
|
|
343
|
+
* const result = await payment.handleNotify('stripe', request)
|
|
344
|
+
* if (!result.success) return result
|
|
345
|
+
* if (result.data.status === 'paid') {
|
|
346
|
+
* // 宿主在共享业务事务中核对商户订单、金额、币种,并按 eventId 持久去重后履约。
|
|
347
|
+
* }
|
|
348
|
+
* return result // 验签成功的 pending 结果仍然不能发货。
|
|
349
|
+
* }
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
interface PaymentNotifyResult {
|
|
353
|
+
/** 商户订单号 */
|
|
354
|
+
orderNo: string;
|
|
355
|
+
/** 支付渠道交易号 */
|
|
356
|
+
transactionId: string;
|
|
357
|
+
/** 渠道通知事件 ID:微信 id、支付宝 notify_id、Stripe event.id;宿主按渠道/商户命名空间持久去重。 */
|
|
358
|
+
eventId: string;
|
|
359
|
+
/** 支付金额(分) */
|
|
360
|
+
amount: number;
|
|
361
|
+
/** ISO 4217 大写币种;支付宝为 CNY,宿主必须与订单核对。 */
|
|
362
|
+
currency: string;
|
|
363
|
+
/** 支付状态 */
|
|
364
|
+
status: OrderStatusValue;
|
|
365
|
+
/** 渠道支付时间;Stripe 为支付成功事件的创建时间,非本次通知接收时间。 */
|
|
366
|
+
paidAt?: Date;
|
|
367
|
+
/** 原始数据 */
|
|
368
|
+
raw?: Record<string, unknown>;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* 订单状态查询结果
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* import { payment, type OrderStatus } from '@h-ai/payment'
|
|
375
|
+
* const result = await payment.queryOrder('stripe', 'ORDER_1')
|
|
376
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
377
|
+
* const status: OrderStatus = result.data
|
|
378
|
+
* const hasPaymentConfirmation = status.status === 'paid'
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
interface OrderStatus {
|
|
382
|
+
/** 商户订单号 */
|
|
383
|
+
orderNo: string;
|
|
384
|
+
/** 渠道交易号 */
|
|
385
|
+
transactionId?: string;
|
|
386
|
+
/** 订单状态 */
|
|
387
|
+
status: OrderStatusValue;
|
|
388
|
+
/** 金额(分) */
|
|
389
|
+
amount: number;
|
|
390
|
+
/** 支付时间 */
|
|
391
|
+
paidAt?: Date;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* 退款入参
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* import type { RefundInput } from '@h-ai/payment'
|
|
398
|
+
* const refund: RefundInput = {
|
|
399
|
+
* orderNo: 'ORDER_1', refundNo: 'REFUND_1', amount: 50, totalAmount: 100, reason: '部分退货',
|
|
400
|
+
* }
|
|
401
|
+
* // 同一业务退款重试复用 refundNo,不能变更金额;微信必须提供原订单总额。
|
|
402
|
+
* ```
|
|
403
|
+
*/
|
|
404
|
+
interface RefundInput {
|
|
405
|
+
/** 商户订单号 */
|
|
406
|
+
orderNo: string;
|
|
407
|
+
/** 退款单号 */
|
|
408
|
+
refundNo: string;
|
|
409
|
+
/** 退款金额(分) */
|
|
410
|
+
amount: number;
|
|
411
|
+
/** 订单总金额(分),微信退款必填 */
|
|
412
|
+
totalAmount?: number;
|
|
413
|
+
/** 退款原因 */
|
|
414
|
+
reason?: string;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* 退款结果
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* import { payment, type RefundResult } from '@h-ai/payment'
|
|
421
|
+
* const result = await payment.refund('stripe', { orderNo: 'ORDER_1', refundNo: 'REFUND_1', amount: 50 })
|
|
422
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
423
|
+
* const refund: RefundResult = result.data
|
|
424
|
+
* const confirmed = refund.status === 'success' // processing 仅表示仍待渠道确认。
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
interface RefundResult {
|
|
428
|
+
/** 退款单号 */
|
|
429
|
+
refundNo: string;
|
|
430
|
+
/** 渠道退款标识;支付宝返回原交易号,需同时使用 refundNo 标识本次退款。 */
|
|
431
|
+
refundId: string;
|
|
432
|
+
/** 退款状态;支付宝 fund_change 为 N 或缺失时是 processing,须向渠道确认后才能判定退款成功。 */
|
|
433
|
+
status: 'processing' | 'success' | 'failed';
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* 支付 Provider 接口
|
|
437
|
+
*
|
|
438
|
+
* 各支付渠道(微信/支付宝/Stripe)实现此接口,
|
|
439
|
+
* 由 payment 模块统一调度。
|
|
440
|
+
* @example
|
|
441
|
+
* ```ts
|
|
442
|
+
* import { payment, type PaymentProvider } from '@h-ai/payment'
|
|
443
|
+
* function registerPartner(provider: PaymentProvider) {
|
|
444
|
+
* // 实现方负责完整的创建、验签解析、查单、退款和关单契约,并返回 HaiResult。
|
|
445
|
+
* payment.registerProvider(provider)
|
|
446
|
+
* return payment.getProvider(provider.name)
|
|
447
|
+
* }
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
interface PaymentProvider {
|
|
451
|
+
/** Provider 名称(如 'wechat', 'alipay', 'stripe') */
|
|
452
|
+
readonly name: string;
|
|
453
|
+
/** 创建完整客户端契约的订单;unsupported 场景返回失败,不能以空参数占位成功。 */
|
|
454
|
+
createOrder: (input: CreateOrderInput) => Promise<HaiResult<PaymentOrder>>;
|
|
455
|
+
/** 对原始通知验签并解析;成功可为 pending,不能代替业务核对、持久去重和履约事务。 */
|
|
456
|
+
handleNotify: (request: PaymentNotifyRequest) => Promise<HaiResult<PaymentNotifyResult>>;
|
|
457
|
+
/** 读取并核对指定商户订单的渠道状态;未知订单或未知状态必须返回失败。 */
|
|
458
|
+
queryOrder: (orderNo: string) => Promise<HaiResult<OrderStatus>>;
|
|
459
|
+
/** 申请指定 refundNo 的退款;成功后还要区分 processing 与资金确认 success。 */
|
|
460
|
+
refund: (input: RefundInput) => Promise<HaiResult<RefundResult>>;
|
|
461
|
+
/** 只有渠道确认关闭/过期才成功;禁止无操作返回成功或关闭已付款订单。 */
|
|
462
|
+
closeOrder: (orderNo: string) => Promise<HaiResult<void>>;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* 支付模块函数接口
|
|
466
|
+
* @example
|
|
467
|
+
* ```ts
|
|
468
|
+
* import { payment, type PaymentFunctions } from '@h-ai/payment'
|
|
469
|
+
* const service: PaymentFunctions = payment
|
|
470
|
+
* if (!service.isInitialized) throw new Error('Payment is not initialized')
|
|
471
|
+
* const result = await service.queryOrder('stripe', 'ORDER_1')
|
|
472
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
interface PaymentFunctions {
|
|
476
|
+
/** 按 Schema 注册渠道;Stripe 要求共享 reldb 已初始化,失败后不能启动依赖支付的业务。 */
|
|
477
|
+
init: (config: PaymentConfigInput) => Promise<HaiResult<void>>;
|
|
478
|
+
/** 清理本进程注册表与配置;不会删除持久幂等记录或关闭宿主 reldb,不返回 HaiResult。 */
|
|
479
|
+
close: () => Promise<void>;
|
|
480
|
+
/** 当前解析后的配置,关闭或未初始化时为 null;包含密钥,禁止下发浏览器或记录日志。 */
|
|
481
|
+
readonly config: PaymentConfig | null;
|
|
482
|
+
/** 是否已有成功发布的配置;不代表渠道实时可用或商户凭据已被远端验证。 */
|
|
483
|
+
readonly isInitialized: boolean;
|
|
484
|
+
/** 创建订单并返回对应场景的最小客户端参数;调用方必须先检查 success。 */
|
|
485
|
+
createOrder: (providerName: string, input: CreateOrderInput) => Promise<HaiResult<PaymentOrder>>;
|
|
486
|
+
/** 验签并解析原始回调;宿主核对金额/币种并持久去重后才可确认业务履约。 */
|
|
487
|
+
handleNotify: (providerName: string, request: PaymentNotifyRequest) => Promise<HaiResult<PaymentNotifyResult>>;
|
|
488
|
+
/** 查渠道状态;失败不能映射成未付款或直接重建订单。 */
|
|
489
|
+
queryOrder: (providerName: string, orderNo: string) => Promise<HaiResult<OrderStatus>>;
|
|
490
|
+
/** 退款标识与参数保持稳定;processing 仍需渠道核对,不能视作最终退款成功。 */
|
|
491
|
+
refund: (providerName: string, input: RefundInput) => Promise<HaiResult<RefundResult>>;
|
|
492
|
+
/** 主动关单;Stripe 只在确认 Session expired 后成功,页面返回不是关单证明。 */
|
|
493
|
+
closeOrder: (providerName: string, orderNo: string) => Promise<HaiResult<void>>;
|
|
494
|
+
/** 按名称读取本进程 Provider;未注册时为 undefined,应用通常使用统一操作入口。 */
|
|
495
|
+
getProvider: (name: string) => PaymentProvider | undefined;
|
|
496
|
+
/** 注册完整实现 PaymentProvider 的自定义渠道;同名会替换本进程注册项,生命周期由宿主协调。 */
|
|
497
|
+
registerProvider: (provider: PaymentProvider) => void;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export { type AlipayConfig as A, type CreateOrderInput as C, HaiPaymentError as H, type OrderStatus as O, type PaymentOrder as P, type RefundInput as R, type StripeConfig as S, type TradeType as T, type WechatPayConfig as W, type PaymentFunctions as a, AlipayConfigSchema as b, type OrderStatusValue as c, type PaymentConfig as d, type PaymentConfigInput as e, PaymentConfigSchema as f, type PaymentNotifyRequest as g, type PaymentNotifyResult as h, type PaymentProvider as i, type RefundResult as j, StripeConfigSchema as k, WechatPayConfigSchema as l };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/payment",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.0-beta.10",
|
|
5
5
|
"description": "Hai Framework payment module for WeChat Pay, Alipay, and Stripe.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
"./client": {
|
|
13
13
|
"types": "./dist/client/index.d.ts",
|
|
14
14
|
"import": "./dist/client/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./api": {
|
|
17
|
-
"types": "./dist/api/index.d.ts",
|
|
18
|
-
"import": "./dist/api/index.js"
|
|
19
15
|
}
|
|
20
16
|
},
|
|
21
17
|
"main": "./dist/index.js",
|
|
@@ -26,26 +22,32 @@
|
|
|
26
22
|
"publishConfig": {
|
|
27
23
|
"access": "public"
|
|
28
24
|
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": "^22.23.4 || ^24.22.1 || ^26.12.1"
|
|
27
|
+
},
|
|
29
28
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"@h-ai/
|
|
32
|
-
"@h-ai/
|
|
29
|
+
"@h-ai/audit": "1.0.0-beta.10",
|
|
30
|
+
"@h-ai/core": "1.0.0-beta.10",
|
|
31
|
+
"@h-ai/reldb": "1.0.0-beta.10",
|
|
32
|
+
"zod": "^4.6.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@types/node": "^
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
35
|
+
"@types/node": "^26.6.5",
|
|
36
|
+
"jsdom": "^30.1.2",
|
|
37
|
+
"rimraf": "^6.1.3",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.11"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"build": "tsup",
|
|
43
44
|
"dev": "tsup --watch",
|
|
44
45
|
"test": "vitest run",
|
|
45
46
|
"test:watch": "vitest",
|
|
46
|
-
"typecheck": "
|
|
47
|
+
"typecheck": "node ../../scripts/typecheck.mjs --noEmit",
|
|
47
48
|
"lint": "eslint .",
|
|
48
49
|
"lint:fix": "eslint . --fix",
|
|
49
|
-
"clean": "rimraf dist .turbo"
|
|
50
|
+
"clean": "rimraf dist .turbo",
|
|
51
|
+
"build:clean": "rimraf dist"
|
|
50
52
|
}
|
|
51
53
|
}
|