@h-ai/payment 0.1.0-alpha5

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/dist/index.js ADDED
@@ -0,0 +1,854 @@
1
+ import { paymentM, HaiPaymentError } from './chunk-4IUARFWH.js';
2
+ export { HaiPaymentError } from './chunk-4IUARFWH.js';
3
+ import { z } from 'zod';
4
+ import { core, err, ok } from '@h-ai/core';
5
+ import { audit } from '@h-ai/audit';
6
+ import { createVerify, createDecipheriv, randomBytes, createSign, createHmac } from 'crypto';
7
+ import { Buffer } from 'buffer';
8
+
9
+ var WechatPayConfigSchema = z.object({
10
+ mchId: z.string().min(1, paymentM("payment_configFieldRequired")),
11
+ apiV3Key: z.string().min(1, paymentM("payment_configFieldRequired")),
12
+ serialNo: z.string().min(1, paymentM("payment_configFieldRequired")),
13
+ privateKey: z.string().min(1, paymentM("payment_configFieldRequired")),
14
+ platformCert: z.string().optional(),
15
+ appId: z.string().min(1, paymentM("payment_configFieldRequired"))
16
+ });
17
+ var AlipayConfigSchema = z.object({
18
+ appId: z.string().min(1, paymentM("payment_configFieldRequired")),
19
+ privateKey: z.string().min(1, paymentM("payment_configFieldRequired")),
20
+ alipayPublicKey: z.string().min(1, paymentM("payment_configFieldRequired")),
21
+ signType: z.enum(["RSA2", "RSA"]).default("RSA2"),
22
+ sandbox: z.boolean().default(false)
23
+ });
24
+ var StripeConfigSchema = z.object({
25
+ secretKey: z.string().min(1, paymentM("payment_configFieldRequired")),
26
+ webhookSecret: z.string().min(1, paymentM("payment_configFieldRequired"))
27
+ });
28
+ var PaymentConfigSchema = z.object({
29
+ wechat: WechatPayConfigSchema.optional(),
30
+ alipay: AlipayConfigSchema.optional(),
31
+ stripe: StripeConfigSchema.optional()
32
+ });
33
+ var logger = core.logger.child({ module: "payment", scope: "functions" });
34
+ var providers = /* @__PURE__ */ new Map();
35
+ async function auditLog(input) {
36
+ const result = await audit.log(input);
37
+ if (!result.success) {
38
+ logger.warn("Failed to write payment audit log", { action: input.action, error: result.error.message });
39
+ }
40
+ }
41
+ function registerProvider(provider) {
42
+ providers.set(provider.name, provider);
43
+ }
44
+ function getProvider(name) {
45
+ return providers.get(name);
46
+ }
47
+ function requireProvider(name) {
48
+ const provider = providers.get(name);
49
+ if (!provider) {
50
+ return err(
51
+ HaiPaymentError.PROVIDER_NOT_FOUND,
52
+ paymentM("payment_providerNotFound")
53
+ );
54
+ }
55
+ return ok(provider);
56
+ }
57
+ async function createOrder(providerName, input) {
58
+ const result = requireProvider(providerName);
59
+ if (!result.success)
60
+ return result;
61
+ const orderResult = await result.data.createOrder(input);
62
+ if (orderResult.success) {
63
+ await auditLog({
64
+ action: "create_order",
65
+ resource: "payment",
66
+ resourceId: input.orderNo,
67
+ details: { provider: providerName, amount: input.amount, tradeType: input.tradeType }
68
+ });
69
+ }
70
+ return orderResult;
71
+ }
72
+ async function handleNotify(providerName, request) {
73
+ const result = requireProvider(providerName);
74
+ if (!result.success)
75
+ return result;
76
+ const notifyResult = await result.data.handleNotify(request);
77
+ if (notifyResult.success) {
78
+ await auditLog({
79
+ action: "payment_notify",
80
+ resource: "payment",
81
+ resourceId: notifyResult.data.orderNo,
82
+ details: { provider: providerName, transactionId: notifyResult.data.transactionId, status: notifyResult.data.status, amount: notifyResult.data.amount }
83
+ });
84
+ }
85
+ return notifyResult;
86
+ }
87
+ async function queryOrder(providerName, orderNo) {
88
+ const result = requireProvider(providerName);
89
+ if (!result.success)
90
+ return result;
91
+ return result.data.queryOrder(orderNo);
92
+ }
93
+ async function refund(providerName, input) {
94
+ const result = requireProvider(providerName);
95
+ if (!result.success)
96
+ return result;
97
+ const refundResult = await result.data.refund(input);
98
+ if (refundResult.success) {
99
+ await auditLog({
100
+ action: "refund",
101
+ resource: "payment",
102
+ resourceId: input.orderNo,
103
+ details: { provider: providerName, refundNo: input.refundNo, amount: input.amount }
104
+ });
105
+ }
106
+ return refundResult;
107
+ }
108
+ async function closeOrder(providerName, orderNo) {
109
+ const result = requireProvider(providerName);
110
+ if (!result.success)
111
+ return result;
112
+ const closeResult = await result.data.closeOrder(orderNo);
113
+ if (closeResult.success) {
114
+ await auditLog({
115
+ action: "close_order",
116
+ resource: "payment",
117
+ resourceId: orderNo,
118
+ details: { provider: providerName }
119
+ });
120
+ }
121
+ return closeResult;
122
+ }
123
+ function clearProviders() {
124
+ providers.clear();
125
+ }
126
+ function signAlipayParams(params, privateKey, signType = "RSA2") {
127
+ const sorted = Object.keys(params).filter((k) => params[k] !== void 0 && params[k] !== "").sort().map((k) => `${k}=${params[k]}`).join("&");
128
+ const algorithm = signType === "RSA2" ? "RSA-SHA256" : "RSA-SHA1";
129
+ const sign = createSign(algorithm);
130
+ sign.update(sorted);
131
+ return sign.sign(privateKey, "base64");
132
+ }
133
+ function verifyAlipayNotify(params, alipayPublicKey) {
134
+ const sign = params.sign ?? "";
135
+ const signType = params.sign_type ?? "RSA2";
136
+ const sorted = Object.keys(params).filter((k) => k !== "sign" && k !== "sign_type" && params[k] !== void 0 && params[k] !== "").sort().map((k) => `${k}=${params[k]}`).join("&");
137
+ const algorithm = signType === "RSA2" ? "RSA-SHA256" : "RSA-SHA1";
138
+ const verify = createVerify(algorithm);
139
+ verify.update(sorted);
140
+ return verify.verify(alipayPublicKey, sign, "base64");
141
+ }
142
+
143
+ // src/providers/alipay/alipay-provider.ts
144
+ var ALIPAY_GATEWAY = "https://openapi.alipay.com/gateway.do";
145
+ var ALIPAY_SANDBOX_GATEWAY = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
146
+ function createAlipayProvider(config) {
147
+ const gateway = config.sandbox ? ALIPAY_SANDBOX_GATEWAY : ALIPAY_GATEWAY;
148
+ const signType = config.signType ?? "RSA2";
149
+ function buildCommonParams(method, notifyUrl) {
150
+ return {
151
+ app_id: config.appId,
152
+ method,
153
+ charset: "utf-8",
154
+ sign_type: signType,
155
+ timestamp: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19),
156
+ version: "1.0",
157
+ notify_url: notifyUrl
158
+ };
159
+ }
160
+ function getTradeConfig(tradeType) {
161
+ const map = {
162
+ jsapi: { method: "alipay.trade.create", productCode: "JSAPI_PAY" },
163
+ h5: { method: "alipay.trade.wap.pay", productCode: "QUICK_WAP_WAY" },
164
+ app: { method: "alipay.trade.app.pay", productCode: "QUICK_MSECURITY_PAY" },
165
+ native: { method: "alipay.trade.precreate", productCode: "FACE_TO_FACE_PAYMENT" }
166
+ };
167
+ return map[tradeType] ?? map.h5;
168
+ }
169
+ async function alipayRequest(params) {
170
+ const sign = signAlipayParams(params, config.privateKey, signType);
171
+ const allParams = { ...params, sign };
172
+ const queryString = Object.entries(allParams).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
173
+ const response = await fetch(`${gateway}?${queryString}`, {
174
+ method: "POST",
175
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
176
+ });
177
+ if (!response.ok) {
178
+ throw new Error(`Alipay API failed: ${response.status}`);
179
+ }
180
+ return response.json();
181
+ }
182
+ return {
183
+ name: "alipay",
184
+ async createOrder(input) {
185
+ try {
186
+ const { method, productCode } = getTradeConfig(input.tradeType);
187
+ const bizContent = JSON.stringify({
188
+ out_trade_no: input.orderNo,
189
+ total_amount: (input.amount / 100).toFixed(2),
190
+ // 分 → 元
191
+ subject: input.description,
192
+ product_code: productCode,
193
+ passback_params: input.metadata ? JSON.stringify(input.metadata) : void 0
194
+ });
195
+ const params = {
196
+ ...buildCommonParams(method, input.notifyUrl),
197
+ biz_content: bizContent
198
+ };
199
+ const response = await alipayRequest(params);
200
+ const key = `${method.replace(/\./g, "_")}_response`;
201
+ const data = response[key];
202
+ return ok({
203
+ provider: "alipay",
204
+ tradeType: input.tradeType,
205
+ clientParams: data ?? {},
206
+ prepayId: data?.trade_no
207
+ });
208
+ } catch (cause) {
209
+ return err(
210
+ HaiPaymentError.CREATE_ORDER_FAILED,
211
+ paymentM("payment_createOrderFailed"),
212
+ cause
213
+ );
214
+ }
215
+ },
216
+ async handleNotify(request) {
217
+ try {
218
+ const params = {};
219
+ const pairs = request.body.split("&");
220
+ for (const pair of pairs) {
221
+ const [key, ...rest] = pair.split("=");
222
+ params[decodeURIComponent(key)] = decodeURIComponent(rest.join("="));
223
+ }
224
+ const valid = verifyAlipayNotify(params, config.alipayPublicKey);
225
+ if (!valid) {
226
+ return err(
227
+ HaiPaymentError.NOTIFY_VERIFY_FAILED,
228
+ paymentM("payment_notifyVerifyFailed")
229
+ );
230
+ }
231
+ const statusMap = {
232
+ TRADE_SUCCESS: "paid",
233
+ TRADE_CLOSED: "closed",
234
+ TRADE_FINISHED: "paid",
235
+ WAIT_BUYER_PAY: "pending"
236
+ };
237
+ return ok({
238
+ orderNo: params.out_trade_no,
239
+ transactionId: params.trade_no,
240
+ amount: Math.round(Number.parseFloat(params.total_amount) * 100),
241
+ // 元 → 分
242
+ status: statusMap[params.trade_status] ?? "pending",
243
+ paidAt: params.gmt_payment ? new Date(params.gmt_payment) : void 0,
244
+ raw: params
245
+ });
246
+ } catch (cause) {
247
+ return err(
248
+ HaiPaymentError.NOTIFY_PARSE_FAILED,
249
+ paymentM("payment_notifyParseFailed"),
250
+ cause
251
+ );
252
+ }
253
+ },
254
+ async queryOrder(orderNo) {
255
+ try {
256
+ const method = "alipay.trade.query";
257
+ const params = {
258
+ ...buildCommonParams(method, ""),
259
+ biz_content: JSON.stringify({ out_trade_no: orderNo })
260
+ };
261
+ const response = await alipayRequest(params);
262
+ const data = response.alipay_trade_query_response;
263
+ const statusMap = {
264
+ TRADE_SUCCESS: "paid",
265
+ TRADE_CLOSED: "closed",
266
+ TRADE_FINISHED: "paid",
267
+ WAIT_BUYER_PAY: "pending"
268
+ };
269
+ return ok({
270
+ orderNo,
271
+ transactionId: data?.trade_no,
272
+ status: statusMap[data?.trade_status] ?? "pending",
273
+ amount: Math.round(Number.parseFloat(data?.total_amount ?? "0") * 100),
274
+ paidAt: data?.send_pay_date ? new Date(data.send_pay_date) : void 0
275
+ });
276
+ } catch (cause) {
277
+ return err(
278
+ HaiPaymentError.QUERY_ORDER_FAILED,
279
+ paymentM("payment_queryOrderFailed"),
280
+ cause
281
+ );
282
+ }
283
+ },
284
+ async refund(input) {
285
+ try {
286
+ const method = "alipay.trade.refund";
287
+ const params = {
288
+ ...buildCommonParams(method, ""),
289
+ biz_content: JSON.stringify({
290
+ out_trade_no: input.orderNo,
291
+ out_request_no: input.refundNo,
292
+ refund_amount: (input.amount / 100).toFixed(2),
293
+ refund_reason: input.reason
294
+ })
295
+ };
296
+ const response = await alipayRequest(params);
297
+ const data = response.alipay_trade_refund_response;
298
+ return ok({
299
+ refundNo: input.refundNo,
300
+ refundId: data?.trade_no ?? "",
301
+ status: data?.fund_change === "Y" ? "success" : "processing"
302
+ });
303
+ } catch (cause) {
304
+ return err(
305
+ HaiPaymentError.REFUND_FAILED,
306
+ paymentM("payment_refundFailed"),
307
+ cause
308
+ );
309
+ }
310
+ },
311
+ async closeOrder(orderNo) {
312
+ try {
313
+ const method = "alipay.trade.close";
314
+ const params = {
315
+ ...buildCommonParams(method, ""),
316
+ biz_content: JSON.stringify({ out_trade_no: orderNo })
317
+ };
318
+ await alipayRequest(params);
319
+ return ok(void 0);
320
+ } catch (cause) {
321
+ return err(
322
+ HaiPaymentError.CLOSE_ORDER_FAILED,
323
+ paymentM("payment_closeOrderFailed"),
324
+ cause
325
+ );
326
+ }
327
+ }
328
+ };
329
+ }
330
+ var STRIPE_API_BASE = "https://api.stripe.com/v1";
331
+ function createStripeProvider(config) {
332
+ async function stripeRequest(method, path, body) {
333
+ const response = await fetch(`${STRIPE_API_BASE}${path}`, {
334
+ method,
335
+ headers: {
336
+ "Authorization": `Bearer ${config.secretKey}`,
337
+ "Content-Type": "application/x-www-form-urlencoded"
338
+ },
339
+ body: body ? Object.entries(body).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&") : void 0
340
+ });
341
+ if (!response.ok) {
342
+ const errorText = await response.text();
343
+ throw new Error(`Stripe API ${method} ${path} failed: ${response.status} ${errorText}`);
344
+ }
345
+ return response.json();
346
+ }
347
+ const TIMESTAMP_TOLERANCE = 300;
348
+ function verifyWebhookSignature(payload, signature) {
349
+ const parts = signature.split(",");
350
+ const timestamp = parts.find((p) => p.startsWith("t="))?.slice(2) ?? "";
351
+ const v1 = parts.find((p) => p.startsWith("v1="))?.slice(3) ?? "";
352
+ const ts = Number.parseInt(timestamp, 10);
353
+ if (Number.isNaN(ts) || Math.abs(Math.floor(Date.now() / 1e3) - ts) > TIMESTAMP_TOLERANCE) {
354
+ return false;
355
+ }
356
+ const signedPayload = `${timestamp}.${payload}`;
357
+ const expected = createHmac("sha256", config.webhookSecret).update(signedPayload).digest("hex");
358
+ try {
359
+ return core.string.constantTimeEqual(v1, expected);
360
+ } catch {
361
+ return false;
362
+ }
363
+ }
364
+ return {
365
+ name: "stripe",
366
+ async createOrder(input) {
367
+ try {
368
+ const params = {
369
+ "mode": "payment",
370
+ "line_items[0][price_data][currency]": input.currency?.toLowerCase() ?? "usd",
371
+ "line_items[0][price_data][product_data][name]": input.description,
372
+ "line_items[0][price_data][unit_amount]": input.amount.toString(),
373
+ "line_items[0][quantity]": "1"
374
+ };
375
+ params["metadata[orderNo]"] = input.orderNo;
376
+ if (input.metadata) {
377
+ for (const [k, v] of Object.entries(input.metadata)) {
378
+ params[`metadata[${k}]`] = v;
379
+ }
380
+ }
381
+ const session = await stripeRequest("POST", "/checkout/sessions", params);
382
+ return ok({
383
+ provider: "stripe",
384
+ tradeType: input.tradeType,
385
+ clientParams: {
386
+ sessionId: session.id,
387
+ checkoutUrl: session.url
388
+ }
389
+ });
390
+ } catch (cause) {
391
+ return err(
392
+ HaiPaymentError.CREATE_ORDER_FAILED,
393
+ paymentM("payment_createOrderFailed"),
394
+ cause
395
+ );
396
+ }
397
+ },
398
+ async handleNotify(request) {
399
+ try {
400
+ const signature = request.headers["stripe-signature"] ?? "";
401
+ const valid = verifyWebhookSignature(request.body, signature);
402
+ if (!valid) {
403
+ return err(
404
+ HaiPaymentError.NOTIFY_VERIFY_FAILED,
405
+ paymentM("payment_notifyVerifyFailed")
406
+ );
407
+ }
408
+ const event = JSON.parse(request.body);
409
+ const obj = event.data.object;
410
+ const orderNo = obj.metadata?.orderNo ?? "";
411
+ const statusMap = {
412
+ "checkout.session.completed": "paid",
413
+ "payment_intent.payment_failed": "failed"
414
+ };
415
+ return ok({
416
+ orderNo,
417
+ transactionId: obj.id,
418
+ amount: obj.amount_total ?? 0,
419
+ status: statusMap[event.type] ?? "pending",
420
+ paidAt: /* @__PURE__ */ new Date(),
421
+ raw: event
422
+ });
423
+ } catch (cause) {
424
+ return err(
425
+ HaiPaymentError.NOTIFY_PARSE_FAILED,
426
+ paymentM("payment_notifyParseFailed"),
427
+ cause
428
+ );
429
+ }
430
+ },
431
+ async queryOrder(orderNo) {
432
+ try {
433
+ const sessions = await stripeRequest("GET", `/checkout/sessions?limit=1&metadata[orderNo]=${encodeURIComponent(orderNo)}`);
434
+ const session = sessions.data[0];
435
+ if (!session) {
436
+ return ok({
437
+ orderNo,
438
+ status: "pending",
439
+ amount: 0
440
+ });
441
+ }
442
+ const statusMap = {
443
+ paid: "paid",
444
+ unpaid: "pending",
445
+ no_payment_required: "paid"
446
+ };
447
+ return ok({
448
+ orderNo,
449
+ transactionId: session.id,
450
+ status: statusMap[session.payment_status] ?? "pending",
451
+ amount: session.amount_total
452
+ });
453
+ } catch (cause) {
454
+ return err(
455
+ HaiPaymentError.QUERY_ORDER_FAILED,
456
+ paymentM("payment_queryOrderFailed"),
457
+ cause
458
+ );
459
+ }
460
+ },
461
+ async refund(input) {
462
+ try {
463
+ const sessions = await stripeRequest("GET", `/checkout/sessions?limit=1&metadata[orderNo]=${encodeURIComponent(input.orderNo)}`);
464
+ const paymentIntent = sessions.data[0]?.payment_intent;
465
+ if (!paymentIntent) {
466
+ return err(
467
+ HaiPaymentError.REFUND_FAILED,
468
+ paymentM("payment_refundFailed")
469
+ );
470
+ }
471
+ const refund2 = await stripeRequest("POST", "/refunds", {
472
+ payment_intent: paymentIntent,
473
+ amount: input.amount.toString(),
474
+ reason: input.reason ?? "requested_by_customer"
475
+ });
476
+ return ok({
477
+ refundNo: input.refundNo,
478
+ refundId: refund2.id,
479
+ status: refund2.status === "succeeded" ? "success" : "processing"
480
+ });
481
+ } catch (cause) {
482
+ return err(
483
+ HaiPaymentError.REFUND_FAILED,
484
+ paymentM("payment_refundFailed"),
485
+ cause
486
+ );
487
+ }
488
+ },
489
+ async closeOrder(_orderNo) {
490
+ return ok(void 0);
491
+ }
492
+ };
493
+ }
494
+ function generateNonce(length = 32) {
495
+ return randomBytes(length).toString("hex").slice(0, length);
496
+ }
497
+ function getTimestamp() {
498
+ return Math.floor(Date.now() / 1e3).toString();
499
+ }
500
+ function signRequest(method, url, timestamp, nonce, body, privateKey) {
501
+ const message = `${method}
502
+ ${url}
503
+ ${timestamp}
504
+ ${nonce}
505
+ ${body}
506
+ `;
507
+ const sign = createSign("RSA-SHA256");
508
+ sign.update(message);
509
+ return sign.sign(privateKey, "base64");
510
+ }
511
+ function signJsapi(appId, timestamp, nonce, prepayId, privateKey) {
512
+ const message = `${appId}
513
+ ${timestamp}
514
+ ${nonce}
515
+ prepay_id=${prepayId}
516
+ `;
517
+ const sign = createSign("RSA-SHA256");
518
+ sign.update(message);
519
+ return sign.sign(privateKey, "base64");
520
+ }
521
+ function verifyNotifySignature(timestamp, nonce, body, signature, platformCert) {
522
+ const message = `${timestamp}
523
+ ${nonce}
524
+ ${body}
525
+ `;
526
+ const verify = createVerify("RSA-SHA256");
527
+ verify.update(message);
528
+ return verify.verify(platformCert, signature, "base64");
529
+ }
530
+ function decryptResource(ciphertext, nonce, associatedData, apiV3Key) {
531
+ const buf = Buffer.from(ciphertext, "base64");
532
+ const authTag = buf.subarray(buf.length - 16);
533
+ const data = buf.subarray(0, buf.length - 16);
534
+ const decipher = createDecipheriv("aes-256-gcm", apiV3Key, nonce);
535
+ decipher.setAuthTag(authTag);
536
+ decipher.setAAD(Buffer.from(associatedData));
537
+ const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
538
+ return decrypted.toString("utf-8");
539
+ }
540
+
541
+ // src/providers/wechat/wechat-pay-provider.ts
542
+ var WECHAT_API_BASE = "https://api.mch.weixin.qq.com";
543
+ function createWechatPayProvider(config) {
544
+ async function wechatRequest(method, path, body) {
545
+ const timestamp = getTimestamp();
546
+ const nonce = generateNonce();
547
+ const bodyStr = body ? JSON.stringify(body) : "";
548
+ const signature = signRequest(method, path, timestamp, nonce, bodyStr, config.privateKey);
549
+ const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${config.mchId}",nonce_str="${nonce}",timestamp="${timestamp}",serial_no="${config.serialNo}",signature="${signature}"`;
550
+ const response = await fetch(`${WECHAT_API_BASE}${path}`, {
551
+ method,
552
+ headers: {
553
+ "Content-Type": "application/json",
554
+ "Authorization": authorization,
555
+ "Accept": "application/json"
556
+ },
557
+ body: bodyStr || void 0
558
+ });
559
+ if (!response.ok) {
560
+ const errorText = await response.text();
561
+ throw new Error(`Wechat API ${method} ${path} failed: ${response.status} ${errorText}`);
562
+ }
563
+ return response.json();
564
+ }
565
+ function getOrderPath(tradeType) {
566
+ const map = {
567
+ jsapi: "/v3/pay/transactions/jsapi",
568
+ h5: "/v3/pay/transactions/h5",
569
+ app: "/v3/pay/transactions/app",
570
+ native: "/v3/pay/transactions/native"
571
+ };
572
+ return map[tradeType] ?? "/v3/pay/transactions/jsapi";
573
+ }
574
+ return {
575
+ name: "wechat",
576
+ async createOrder(input) {
577
+ try {
578
+ const requestBody = {
579
+ appid: config.appId,
580
+ mchid: config.mchId,
581
+ description: input.description,
582
+ out_trade_no: input.orderNo,
583
+ notify_url: input.notifyUrl,
584
+ amount: {
585
+ total: input.amount,
586
+ currency: input.currency ?? "CNY"
587
+ }
588
+ };
589
+ if (input.tradeType === "jsapi" && input.userId) {
590
+ requestBody.payer = { openid: input.userId };
591
+ }
592
+ if (input.metadata) {
593
+ requestBody.attach = JSON.stringify(input.metadata);
594
+ }
595
+ const path = getOrderPath(input.tradeType);
596
+ const response = await wechatRequest("POST", path, requestBody);
597
+ let clientParams = {};
598
+ if (input.tradeType === "jsapi") {
599
+ const timestamp = getTimestamp();
600
+ const nonce = generateNonce();
601
+ const paySign = signJsapi(config.appId, timestamp, nonce, response.prepay_id, config.privateKey);
602
+ clientParams = {
603
+ appId: config.appId,
604
+ timeStamp: timestamp,
605
+ nonceStr: nonce,
606
+ package: `prepay_id=${response.prepay_id}`,
607
+ signType: "RSA",
608
+ paySign
609
+ };
610
+ } else if (input.tradeType === "h5") {
611
+ clientParams = { h5Url: response.h5_url };
612
+ } else if (input.tradeType === "native") {
613
+ clientParams = { codeUrl: response.code_url };
614
+ }
615
+ return ok({
616
+ provider: "wechat",
617
+ tradeType: input.tradeType,
618
+ clientParams,
619
+ prepayId: response.prepay_id
620
+ });
621
+ } catch (cause) {
622
+ return err(
623
+ HaiPaymentError.CREATE_ORDER_FAILED,
624
+ paymentM("payment_createOrderFailed"),
625
+ cause
626
+ );
627
+ }
628
+ },
629
+ async handleNotify(request) {
630
+ try {
631
+ const timestamp = request.headers["wechatpay-timestamp"] ?? "";
632
+ const nonce = request.headers["wechatpay-nonce"] ?? "";
633
+ const signature = request.headers["wechatpay-signature"] ?? "";
634
+ if (!config.platformCert) {
635
+ return err(
636
+ HaiPaymentError.NOTIFY_VERIFY_FAILED,
637
+ paymentM("payment_notifyVerifyFailed")
638
+ );
639
+ }
640
+ const valid = verifyNotifySignature(timestamp, nonce, request.body, signature, config.platformCert);
641
+ if (!valid) {
642
+ return err(
643
+ HaiPaymentError.NOTIFY_VERIFY_FAILED,
644
+ paymentM("payment_notifyVerifyFailed")
645
+ );
646
+ }
647
+ const body = JSON.parse(request.body);
648
+ const decrypted = decryptResource(
649
+ body.resource.ciphertext,
650
+ body.resource.nonce,
651
+ body.resource.associated_data,
652
+ config.apiV3Key
653
+ );
654
+ const resource = JSON.parse(decrypted);
655
+ return ok({
656
+ orderNo: resource.out_trade_no,
657
+ transactionId: resource.transaction_id,
658
+ amount: resource.amount.total,
659
+ status: resource.trade_state === "SUCCESS" ? "paid" : "failed",
660
+ paidAt: resource.success_time ? new Date(resource.success_time) : void 0,
661
+ raw: resource
662
+ });
663
+ } catch (cause) {
664
+ return err(
665
+ HaiPaymentError.NOTIFY_PARSE_FAILED,
666
+ paymentM("payment_notifyParseFailed"),
667
+ cause
668
+ );
669
+ }
670
+ },
671
+ async queryOrder(orderNo) {
672
+ try {
673
+ const path = `/v3/pay/transactions/out-trade-no/${encodeURIComponent(orderNo)}?mchid=${encodeURIComponent(config.mchId)}`;
674
+ const response = await wechatRequest("GET", path);
675
+ const statusMap = {
676
+ SUCCESS: "paid",
677
+ CLOSED: "closed",
678
+ REFUND: "refunded",
679
+ NOTPAY: "pending",
680
+ PAYERROR: "failed"
681
+ };
682
+ return ok({
683
+ orderNo: response.out_trade_no,
684
+ transactionId: response.transaction_id,
685
+ status: statusMap[response.trade_state] ?? "pending",
686
+ amount: response.amount.total,
687
+ paidAt: response.success_time ? new Date(response.success_time) : void 0
688
+ });
689
+ } catch (cause) {
690
+ return err(
691
+ HaiPaymentError.QUERY_ORDER_FAILED,
692
+ paymentM("payment_queryOrderFailed"),
693
+ cause
694
+ );
695
+ }
696
+ },
697
+ async refund(input) {
698
+ try {
699
+ const response = await wechatRequest("POST", "/v3/refund/domestic/refunds", {
700
+ out_trade_no: input.orderNo,
701
+ out_refund_no: input.refundNo,
702
+ amount: {
703
+ refund: input.amount,
704
+ total: input.totalAmount ?? input.amount,
705
+ currency: "CNY"
706
+ },
707
+ reason: input.reason
708
+ });
709
+ const statusMap = {
710
+ SUCCESS: "success",
711
+ PROCESSING: "processing",
712
+ ABNORMAL: "failed"
713
+ };
714
+ return ok({
715
+ refundNo: input.refundNo,
716
+ refundId: response.refund_id,
717
+ status: statusMap[response.status] ?? "processing"
718
+ });
719
+ } catch (cause) {
720
+ return err(
721
+ HaiPaymentError.REFUND_FAILED,
722
+ paymentM("payment_refundFailed"),
723
+ cause
724
+ );
725
+ }
726
+ },
727
+ async closeOrder(orderNo) {
728
+ try {
729
+ await wechatRequest("POST", `/v3/pay/transactions/out-trade-no/${encodeURIComponent(orderNo)}/close`, {
730
+ mchid: config.mchId
731
+ });
732
+ return ok(void 0);
733
+ } catch (cause) {
734
+ return err(
735
+ HaiPaymentError.CLOSE_ORDER_FAILED,
736
+ paymentM("payment_closeOrderFailed"),
737
+ cause
738
+ );
739
+ }
740
+ }
741
+ };
742
+ }
743
+
744
+ // src/payment-main.ts
745
+ var logger2 = core.logger.child({ module: "payment", scope: "main" });
746
+ var currentConfig = null;
747
+ var initInProgress = false;
748
+ var notInitialized = core.module.createNotInitializedKit(
749
+ HaiPaymentError.NOT_INITIALIZED,
750
+ () => paymentM("payment_notInitialized")
751
+ );
752
+ var payment = {
753
+ /**
754
+ * 初始化支付模块
755
+ *
756
+ * 根据提供的配置自动注册对应的 Provider。
757
+ *
758
+ * @param config - 支付配置
759
+ */
760
+ async init(config) {
761
+ if (initInProgress) {
762
+ logger2.warn("Payment module init already in progress, skipping");
763
+ return err(
764
+ HaiPaymentError.CONFIG_ERROR,
765
+ paymentM("payment_configError", { params: { error: "init already in progress" } })
766
+ );
767
+ }
768
+ initInProgress = true;
769
+ try {
770
+ if (currentConfig !== null) {
771
+ logger2.warn("Payment module is already initialized, reinitializing");
772
+ await payment.close();
773
+ }
774
+ logger2.info("Initializing payment module");
775
+ const parseResult = PaymentConfigSchema.safeParse(config);
776
+ if (!parseResult.success) {
777
+ logger2.error("Payment config validation failed", { error: parseResult.error.message });
778
+ return err(
779
+ HaiPaymentError.CONFIG_ERROR,
780
+ paymentM("payment_configError", { params: { error: parseResult.error.message } }),
781
+ parseResult.error
782
+ );
783
+ }
784
+ const parsed = parseResult.data;
785
+ clearProviders();
786
+ if (parsed.wechat) {
787
+ registerProvider(createWechatPayProvider(parsed.wechat));
788
+ }
789
+ if (parsed.alipay) {
790
+ registerProvider(createAlipayProvider(parsed.alipay));
791
+ }
792
+ if (parsed.stripe) {
793
+ registerProvider(createStripeProvider(parsed.stripe));
794
+ }
795
+ currentConfig = parsed;
796
+ logger2.info("Payment module initialized", {
797
+ providers: [
798
+ parsed.wechat ? "wechat" : null,
799
+ parsed.alipay ? "alipay" : null,
800
+ parsed.stripe ? "stripe" : null
801
+ ].filter(Boolean)
802
+ });
803
+ return ok(void 0);
804
+ } catch (error) {
805
+ logger2.error("Payment module initialization failed", { error });
806
+ return err(
807
+ HaiPaymentError.CONFIG_ERROR,
808
+ paymentM("payment_initFailed", {
809
+ params: { error: error instanceof Error ? error.message : String(error) }
810
+ }),
811
+ error
812
+ );
813
+ } finally {
814
+ initInProgress = false;
815
+ }
816
+ },
817
+ /**
818
+ * 关闭模块、清除所有 Provider
819
+ */
820
+ async close() {
821
+ if (!currentConfig) {
822
+ logger2.info("Payment module already closed, skipping");
823
+ return;
824
+ }
825
+ logger2.info("Closing payment module");
826
+ clearProviders();
827
+ currentConfig = null;
828
+ logger2.info("Payment module closed");
829
+ },
830
+ get config() {
831
+ return currentConfig;
832
+ },
833
+ get isInitialized() {
834
+ return currentConfig !== null;
835
+ },
836
+ /** 创建支付订单 */
837
+ createOrder: (...args) => currentConfig ? createOrder(...args) : Promise.resolve(notInitialized.result()),
838
+ /** 处理异步回调通知 */
839
+ handleNotify: (...args) => currentConfig ? handleNotify(...args) : Promise.resolve(notInitialized.result()),
840
+ /** 查询订单状态 */
841
+ queryOrder: (...args) => currentConfig ? queryOrder(...args) : Promise.resolve(notInitialized.result()),
842
+ /** 发起退款 */
843
+ refund: (...args) => currentConfig ? refund(...args) : Promise.resolve(notInitialized.result()),
844
+ /** 关闭订单 */
845
+ closeOrder: (...args) => currentConfig ? closeOrder(...args) : Promise.resolve(notInitialized.result()),
846
+ /** 获取已注册的 Provider */
847
+ getProvider,
848
+ /** 手动注册 Provider(自定义渠道) */
849
+ registerProvider
850
+ };
851
+
852
+ export { AlipayConfigSchema, PaymentConfigSchema, StripeConfigSchema, WechatPayConfigSchema, payment };
853
+ //# sourceMappingURL=index.js.map
854
+ //# sourceMappingURL=index.js.map