@zucker-framework/payment 1.0.0
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/LICENSE +201 -0
- package/NOTICE +22 -0
- package/THIRD_PARTY_NOTICES +4 -0
- package/dist/index.d.mts +283 -0
- package/dist/index.d.ts +283 -0
- package/dist/index.js +594 -0
- package/dist/index.mjs +560 -0
- package/package.json +40 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/payment.service.ts
|
|
5
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
6
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
7
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
9
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
10
|
+
} else {
|
|
11
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
12
|
+
if (d = decorators[i]) {
|
|
13
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
18
|
+
}
|
|
19
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
20
|
+
var PaymentService = class _PaymentService {
|
|
21
|
+
static {
|
|
22
|
+
__name(this, "PaymentService");
|
|
23
|
+
}
|
|
24
|
+
logger = new Logger(_PaymentService.name);
|
|
25
|
+
providers = /* @__PURE__ */ new Map();
|
|
26
|
+
/** 注册支付提供商 */
|
|
27
|
+
register(provider) {
|
|
28
|
+
if (this.providers.has(provider.id)) {
|
|
29
|
+
this.logger.warn(`PaymentProvider "${provider.id}" \u5DF2\u6CE8\u518C\uFF0C\u5C06\u88AB\u8986\u76D6`);
|
|
30
|
+
}
|
|
31
|
+
this.providers.set(provider.id, provider);
|
|
32
|
+
this.logger.log(`PaymentProvider "${provider.id}" (${provider.name}) \u5DF2\u6CE8\u518C`);
|
|
33
|
+
}
|
|
34
|
+
/** 注销支付提供商 */
|
|
35
|
+
unregister(providerId) {
|
|
36
|
+
const removed = this.providers.delete(providerId);
|
|
37
|
+
if (removed) {
|
|
38
|
+
this.logger.log(`PaymentProvider "${providerId}" \u5DF2\u6CE8\u9500`);
|
|
39
|
+
}
|
|
40
|
+
return removed;
|
|
41
|
+
}
|
|
42
|
+
/** 获取已注册的提供商列表 */
|
|
43
|
+
listProviders() {
|
|
44
|
+
return Array.from(this.providers.values());
|
|
45
|
+
}
|
|
46
|
+
/** 获取指定提供商 */
|
|
47
|
+
getProvider(providerId) {
|
|
48
|
+
const provider = this.providers.get(providerId);
|
|
49
|
+
if (!provider) {
|
|
50
|
+
throw new Error(`PaymentProvider "${providerId}" \u672A\u6CE8\u518C\u3002\u5DF2\u6CE8\u518C: [${Array.from(this.providers.keys()).join(", ")}]`);
|
|
51
|
+
}
|
|
52
|
+
return provider;
|
|
53
|
+
}
|
|
54
|
+
/** 创建支付意图 */
|
|
55
|
+
async createPaymentIntent(providerId, params) {
|
|
56
|
+
const provider = this.getProvider(providerId);
|
|
57
|
+
this.logger.log(`[${providerId}] \u521B\u5EFA\u652F\u4ED8\u610F\u56FE: ${params.amount} ${params.currency}`);
|
|
58
|
+
return provider.createPaymentIntent(params);
|
|
59
|
+
}
|
|
60
|
+
/** 确认支付 */
|
|
61
|
+
async confirmPayment(providerId, intentId) {
|
|
62
|
+
const provider = this.getProvider(providerId);
|
|
63
|
+
this.logger.log(`[${providerId}] \u786E\u8BA4\u652F\u4ED8: ${intentId}`);
|
|
64
|
+
return provider.confirmPayment(intentId);
|
|
65
|
+
}
|
|
66
|
+
/** 退款 */
|
|
67
|
+
async refund(providerId, paymentId, amount, reason) {
|
|
68
|
+
const provider = this.getProvider(providerId);
|
|
69
|
+
this.logger.log(`[${providerId}] \u9000\u6B3E: ${paymentId}, amount=${amount ?? "full"}`);
|
|
70
|
+
return provider.refund(paymentId, amount, reason);
|
|
71
|
+
}
|
|
72
|
+
/** 查询支付状态 */
|
|
73
|
+
async getPaymentStatus(providerId, intentId) {
|
|
74
|
+
const provider = this.getProvider(providerId);
|
|
75
|
+
return provider.getPaymentStatus(intentId);
|
|
76
|
+
}
|
|
77
|
+
/** 处理 Webhook */
|
|
78
|
+
async handleWebhook(providerId, payload, signature) {
|
|
79
|
+
const provider = this.getProvider(providerId);
|
|
80
|
+
this.logger.log(`[${providerId}] \u5904\u7406 Webhook`);
|
|
81
|
+
return provider.handleWebhook(payload, signature);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
PaymentService = _ts_decorate([
|
|
85
|
+
Injectable()
|
|
86
|
+
], PaymentService);
|
|
87
|
+
|
|
88
|
+
// src/order.service.ts
|
|
89
|
+
import { Injectable as Injectable2, Logger as Logger2 } from "@nestjs/common";
|
|
90
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
91
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
92
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
93
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
94
|
+
} else {
|
|
95
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
96
|
+
if (d = decorators[i]) {
|
|
97
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
102
|
+
}
|
|
103
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
104
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
105
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
106
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
110
|
+
var OrderService = class _OrderService {
|
|
111
|
+
static {
|
|
112
|
+
__name(this, "OrderService");
|
|
113
|
+
}
|
|
114
|
+
paymentService;
|
|
115
|
+
logger = new Logger2(_OrderService.name);
|
|
116
|
+
orders = /* @__PURE__ */ new Map();
|
|
117
|
+
constructor(paymentService) {
|
|
118
|
+
this.paymentService = paymentService;
|
|
119
|
+
}
|
|
120
|
+
/** 创建订单并发起支付 */
|
|
121
|
+
async createOrder(params) {
|
|
122
|
+
const { orderId, providerId, payment } = params;
|
|
123
|
+
if (this.orders.has(orderId)) {
|
|
124
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u5DF2\u5B58\u5728`);
|
|
125
|
+
}
|
|
126
|
+
const now = /* @__PURE__ */ new Date();
|
|
127
|
+
const order = {
|
|
128
|
+
id: orderId,
|
|
129
|
+
providerId,
|
|
130
|
+
amount: payment.amount,
|
|
131
|
+
currency: payment.currency,
|
|
132
|
+
status: "created",
|
|
133
|
+
metadata: payment.metadata,
|
|
134
|
+
createdAt: now,
|
|
135
|
+
updatedAt: now
|
|
136
|
+
};
|
|
137
|
+
this.orders.set(orderId, order);
|
|
138
|
+
this.logger.log(`\u8BA2\u5355 "${orderId}" \u5DF2\u521B\u5EFA`);
|
|
139
|
+
try {
|
|
140
|
+
const intent = await this.paymentService.createPaymentIntent(providerId, payment);
|
|
141
|
+
order.paymentIntentId = intent.id;
|
|
142
|
+
order.status = "paying";
|
|
143
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
144
|
+
this.logger.log(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u5DF2\u53D1\u8D77, intentId=${intent.id}`);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
order.status = "failed";
|
|
147
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
148
|
+
this.logger.error(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u53D1\u8D77\u5931\u8D25`, error);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
...order
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** 确认订单支付 */
|
|
156
|
+
async confirmOrder(orderId) {
|
|
157
|
+
const order = this.getOrder(orderId);
|
|
158
|
+
if (order.status !== "paying") {
|
|
159
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u786E\u8BA4\u652F\u4ED8`);
|
|
160
|
+
}
|
|
161
|
+
if (!order.paymentIntentId) {
|
|
162
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentIntentId`);
|
|
163
|
+
}
|
|
164
|
+
const result = await this.paymentService.confirmPayment(order.providerId, order.paymentIntentId);
|
|
165
|
+
if (result.status === "succeeded") {
|
|
166
|
+
order.status = "paid";
|
|
167
|
+
order.paymentId = result.id;
|
|
168
|
+
order.paidAt = result.paidAt ?? /* @__PURE__ */ new Date();
|
|
169
|
+
} else {
|
|
170
|
+
order.status = "failed";
|
|
171
|
+
}
|
|
172
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
173
|
+
this.logger.log(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u7ED3\u679C: ${result.status}`);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/** 订单退款 */
|
|
177
|
+
async refundOrder(orderId, amount, reason) {
|
|
178
|
+
const order = this.getOrder(orderId);
|
|
179
|
+
if (order.status !== "paid" && order.status !== "refunding") {
|
|
180
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u9000\u6B3E`);
|
|
181
|
+
}
|
|
182
|
+
if (!order.paymentId) {
|
|
183
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentId`);
|
|
184
|
+
}
|
|
185
|
+
order.status = "refunding";
|
|
186
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
187
|
+
const result = await this.paymentService.refund(order.providerId, order.paymentId, amount, reason);
|
|
188
|
+
if (result.status === "succeeded") {
|
|
189
|
+
order.status = "refunded";
|
|
190
|
+
order.refundedAt = /* @__PURE__ */ new Date();
|
|
191
|
+
} else if (result.status === "failed") {
|
|
192
|
+
order.status = "paid";
|
|
193
|
+
}
|
|
194
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
195
|
+
this.logger.log(`\u8BA2\u5355 "${orderId}" \u9000\u6B3E\u7ED3\u679C: ${result.status}`);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/** 取消订单 */
|
|
199
|
+
cancelOrder(orderId) {
|
|
200
|
+
const order = this.getOrder(orderId);
|
|
201
|
+
const cancellableStates = [
|
|
202
|
+
"created",
|
|
203
|
+
"paying",
|
|
204
|
+
"failed"
|
|
205
|
+
];
|
|
206
|
+
if (!cancellableStates.includes(order.status)) {
|
|
207
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u53D6\u6D88`);
|
|
208
|
+
}
|
|
209
|
+
order.status = "cancelled";
|
|
210
|
+
order.updatedAt = /* @__PURE__ */ new Date();
|
|
211
|
+
this.logger.log(`\u8BA2\u5355 "${orderId}" \u5DF2\u53D6\u6D88`);
|
|
212
|
+
return {
|
|
213
|
+
...order
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** 查询订单 */
|
|
217
|
+
getOrder(orderId) {
|
|
218
|
+
const order = this.orders.get(orderId);
|
|
219
|
+
if (!order) {
|
|
220
|
+
throw new Error(`\u8BA2\u5355 "${orderId}" \u4E0D\u5B58\u5728`);
|
|
221
|
+
}
|
|
222
|
+
return order;
|
|
223
|
+
}
|
|
224
|
+
/** 列出所有订单 */
|
|
225
|
+
listOrders() {
|
|
226
|
+
return Array.from(this.orders.values()).map((o) => ({
|
|
227
|
+
...o
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
OrderService = _ts_decorate2([
|
|
232
|
+
Injectable2(),
|
|
233
|
+
_ts_metadata("design:type", Function),
|
|
234
|
+
_ts_metadata("design:paramtypes", [
|
|
235
|
+
typeof PaymentService === "undefined" ? Object : PaymentService
|
|
236
|
+
])
|
|
237
|
+
], OrderService);
|
|
238
|
+
|
|
239
|
+
// src/recovery.service.ts
|
|
240
|
+
import { Injectable as Injectable3, Logger as Logger3, Optional, Inject } from "@nestjs/common";
|
|
241
|
+
import { EventEmitter2 } from "@nestjs/event-emitter";
|
|
242
|
+
|
|
243
|
+
// src/payment.events.ts
|
|
244
|
+
var PaymentEvents = {
|
|
245
|
+
ORDER_CREATED: "payment.order.created",
|
|
246
|
+
ORDER_PAID: "payment.order.paid",
|
|
247
|
+
ORDER_FAILED: "payment.order.failed",
|
|
248
|
+
ORDER_CANCELED: "payment.order.canceled",
|
|
249
|
+
ORDER_REFUNDED: "payment.order.refunded",
|
|
250
|
+
PAYMENT_RECOVERY_SENT: "payment.recovery.sent"
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/recovery.service.ts
|
|
254
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
255
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
256
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
257
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
258
|
+
} else {
|
|
259
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
260
|
+
if (d = decorators[i]) {
|
|
261
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
266
|
+
}
|
|
267
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
268
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
269
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
270
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
274
|
+
function _ts_param(paramIndex, decorator) {
|
|
275
|
+
return function(target, key) {
|
|
276
|
+
decorator(target, key, paramIndex);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
__name(_ts_param, "_ts_param");
|
|
280
|
+
var RECOVERY_MAIL_SENDER = "RECOVERY_MAIL_SENDER";
|
|
281
|
+
var RECOVERY_USER_LOOKUP = "RECOVERY_USER_LOOKUP";
|
|
282
|
+
var RECOVERY_URL_GENERATOR = "RECOVERY_URL_GENERATOR";
|
|
283
|
+
var DEFAULT_OPTIONS = {
|
|
284
|
+
maxAttempts: 3,
|
|
285
|
+
retryLinkExpireHours: 48,
|
|
286
|
+
frontendUrl: "http://localhost:3000"
|
|
287
|
+
};
|
|
288
|
+
var PaymentRecoveryService = class _PaymentRecoveryService {
|
|
289
|
+
static {
|
|
290
|
+
__name(this, "PaymentRecoveryService");
|
|
291
|
+
}
|
|
292
|
+
orderService;
|
|
293
|
+
paymentService;
|
|
294
|
+
mailSender;
|
|
295
|
+
userLookup;
|
|
296
|
+
urlGenerator;
|
|
297
|
+
eventEmitter;
|
|
298
|
+
logger = new Logger3(_PaymentRecoveryService.name);
|
|
299
|
+
options;
|
|
300
|
+
/** 内存挽回计数(生产环境建议由业务项目持久化) */
|
|
301
|
+
recoveryAttempts = /* @__PURE__ */ new Map();
|
|
302
|
+
constructor(orderService, paymentService, mailSender, userLookup, urlGenerator, eventEmitter) {
|
|
303
|
+
this.orderService = orderService;
|
|
304
|
+
this.paymentService = paymentService;
|
|
305
|
+
this.mailSender = mailSender;
|
|
306
|
+
this.userLookup = userLookup;
|
|
307
|
+
this.urlGenerator = urlGenerator;
|
|
308
|
+
this.eventEmitter = eventEmitter;
|
|
309
|
+
this.options = {
|
|
310
|
+
...DEFAULT_OPTIONS
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/** 设置挽回选项 */
|
|
314
|
+
configure(options) {
|
|
315
|
+
Object.assign(this.options, options);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 处理付款失败 — 核心挽回入口
|
|
319
|
+
*/
|
|
320
|
+
async handleFailedPayment(orderId, errorCode, errorMessage) {
|
|
321
|
+
let order;
|
|
322
|
+
try {
|
|
323
|
+
order = this.orderService.getOrder(orderId);
|
|
324
|
+
} catch {
|
|
325
|
+
this.logger.warn(`\u8BA2\u5355 ${orderId} \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u633D\u56DE`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (order.status !== "failed") {
|
|
329
|
+
this.logger.warn(`\u8BA2\u5355 ${orderId} \u72B6\u6001\u4E3A ${order.status}\uFF0C\u8DF3\u8FC7\u633D\u56DE`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const attempts = this.recoveryAttempts.get(orderId);
|
|
333
|
+
if (attempts && attempts.count >= this.options.maxAttempts) {
|
|
334
|
+
this.logger.log(`\u8BA2\u5355 ${orderId} \u5DF2\u633D\u56DE ${attempts.count} \u6B21\uFF0C\u4E0D\u518D\u53D1\u9001`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const userId = order.metadata?.userId;
|
|
338
|
+
let email;
|
|
339
|
+
let userName = "";
|
|
340
|
+
if (userId && this.userLookup) {
|
|
341
|
+
const user = await this.userLookup.getUser(userId);
|
|
342
|
+
email = user?.email;
|
|
343
|
+
userName = user?.name ?? "";
|
|
344
|
+
}
|
|
345
|
+
if (!email) {
|
|
346
|
+
email = order.metadata?.email;
|
|
347
|
+
}
|
|
348
|
+
if (!email) {
|
|
349
|
+
this.logger.warn(`\u8BA2\u5355 ${orderId} \u65E0\u6CD5\u83B7\u53D6\u7528\u6237\u90AE\u7BB1\uFF0C\u8DF3\u8FC7\u90AE\u4EF6\u53D1\u9001`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const retryUrl = await this.generateRetryUrl(order);
|
|
353
|
+
if (!retryUrl) {
|
|
354
|
+
this.logger.warn(`\u8BA2\u5355 ${orderId} \u65E0\u6CD5\u751F\u6210\u91CD\u8BD5\u94FE\u63A5`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const errorReason = friendlyErrorReason(errorCode, errorMessage);
|
|
358
|
+
const attemptNumber = (attempts?.count ?? 0) + 1;
|
|
359
|
+
if (this.mailSender) {
|
|
360
|
+
try {
|
|
361
|
+
await this.mailSender.sendRecoveryEmail({
|
|
362
|
+
to: email,
|
|
363
|
+
userName: userName || email.split("@")[0],
|
|
364
|
+
orderId: order.id,
|
|
365
|
+
orderNumber: order.metadata?.orderNumber,
|
|
366
|
+
amount: order.amount,
|
|
367
|
+
currency: order.currency,
|
|
368
|
+
errorReason,
|
|
369
|
+
retryUrl,
|
|
370
|
+
expireHours: this.options.retryLinkExpireHours
|
|
371
|
+
});
|
|
372
|
+
this.logger.log(`\u633D\u56DE\u90AE\u4EF6\u5DF2\u53D1\u9001: ${email}, \u8BA2\u5355 ${orderId} (\u7B2C ${attemptNumber} \u6B21)`);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
this.logger.error(`\u633D\u56DE\u90AE\u4EF6\u53D1\u9001\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
this.logger.log(`[Recovery:NoMailSender] \u8BA2\u5355 ${orderId} \u9700\u8981\u633D\u56DE, email=${email}, retryUrl=${retryUrl}, reason=${errorReason}`);
|
|
379
|
+
}
|
|
380
|
+
this.recoveryAttempts.set(orderId, {
|
|
381
|
+
count: attemptNumber,
|
|
382
|
+
lastAt: /* @__PURE__ */ new Date()
|
|
383
|
+
});
|
|
384
|
+
if (this.eventEmitter) {
|
|
385
|
+
const event = {
|
|
386
|
+
orderId,
|
|
387
|
+
userId,
|
|
388
|
+
email,
|
|
389
|
+
retryUrl,
|
|
390
|
+
attemptNumber
|
|
391
|
+
};
|
|
392
|
+
this.eventEmitter.emit(PaymentEvents.PAYMENT_RECOVERY_SENT, event);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* 重试失败订单 — 将 failed → created 并重新发起支付
|
|
397
|
+
*/
|
|
398
|
+
async retryOrder(orderId) {
|
|
399
|
+
const order = this.orderService.getOrder(orderId);
|
|
400
|
+
if (order.status !== "failed") {
|
|
401
|
+
throw new Error(`\u8BA2\u5355 ${orderId} \u72B6\u6001\u4E3A ${order.status}\uFF0C\u4EC5 failed \u8BA2\u5355\u53EF\u91CD\u8BD5`);
|
|
402
|
+
}
|
|
403
|
+
const intent = await this.paymentService.createPaymentIntent(order.providerId, {
|
|
404
|
+
amount: order.amount,
|
|
405
|
+
currency: order.currency,
|
|
406
|
+
metadata: order.metadata
|
|
407
|
+
});
|
|
408
|
+
const internalOrder = this.orderService.getOrder(orderId);
|
|
409
|
+
internalOrder.paymentIntentId = intent.id;
|
|
410
|
+
internalOrder.status = "paying";
|
|
411
|
+
internalOrder.updatedAt = /* @__PURE__ */ new Date();
|
|
412
|
+
this.logger.log(`\u8BA2\u5355 ${orderId} \u91CD\u8BD5\u652F\u4ED8, \u65B0 intentId=${intent.id}`);
|
|
413
|
+
return {
|
|
414
|
+
...internalOrder
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/** 获取订单挽回记录 */
|
|
418
|
+
getRecoveryAttempts(orderId) {
|
|
419
|
+
return this.recoveryAttempts.get(orderId);
|
|
420
|
+
}
|
|
421
|
+
async generateRetryUrl(order) {
|
|
422
|
+
if (this.urlGenerator) {
|
|
423
|
+
return this.urlGenerator.generateRetryUrl(order);
|
|
424
|
+
}
|
|
425
|
+
return `${this.options.frontendUrl}/payment/retry/${order.id}`;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
PaymentRecoveryService = _ts_decorate3([
|
|
429
|
+
Injectable3(),
|
|
430
|
+
_ts_param(2, Optional()),
|
|
431
|
+
_ts_param(2, Inject(RECOVERY_MAIL_SENDER)),
|
|
432
|
+
_ts_param(3, Optional()),
|
|
433
|
+
_ts_param(3, Inject(RECOVERY_USER_LOOKUP)),
|
|
434
|
+
_ts_param(4, Optional()),
|
|
435
|
+
_ts_param(4, Inject(RECOVERY_URL_GENERATOR)),
|
|
436
|
+
_ts_param(5, Optional()),
|
|
437
|
+
_ts_metadata2("design:type", Function),
|
|
438
|
+
_ts_metadata2("design:paramtypes", [
|
|
439
|
+
typeof OrderService === "undefined" ? Object : OrderService,
|
|
440
|
+
typeof PaymentService === "undefined" ? Object : PaymentService,
|
|
441
|
+
typeof RecoveryMailSender === "undefined" ? Object : RecoveryMailSender,
|
|
442
|
+
typeof RecoveryUserLookup === "undefined" ? Object : RecoveryUserLookup,
|
|
443
|
+
typeof RecoveryUrlGenerator === "undefined" ? Object : RecoveryUrlGenerator,
|
|
444
|
+
typeof EventEmitter2 === "undefined" ? Object : EventEmitter2
|
|
445
|
+
])
|
|
446
|
+
], PaymentRecoveryService);
|
|
447
|
+
function friendlyErrorReason(errorCode, errorMessage) {
|
|
448
|
+
const codeMap = {
|
|
449
|
+
card_declined: "\u94F6\u884C\u5361\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u5C1D\u8BD5\u5176\u4ED6\u652F\u4ED8\u65B9\u5F0F",
|
|
450
|
+
insufficient_funds: "\u4F59\u989D\u4E0D\u8DB3\uFF0C\u8BF7\u786E\u8BA4\u8D26\u6237\u4F59\u989D\u540E\u91CD\u8BD5",
|
|
451
|
+
expired_card: "\u94F6\u884C\u5361\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u4F7F\u7528\u6709\u6548\u7684\u94F6\u884C\u5361",
|
|
452
|
+
incorrect_cvc: "\u5B89\u5168\u7801(CVC)\u4E0D\u6B63\u786E\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165",
|
|
453
|
+
processing_error: "\u652F\u4ED8\u5904\u7406\u51FA\u9519\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
454
|
+
authentication_required: "\u9700\u8981\u989D\u5916\u9A8C\u8BC1\uFF0C\u8BF7\u5B8C\u6210\u94F6\u884C\u7684\u8EAB\u4EFD\u9A8C\u8BC1",
|
|
455
|
+
do_not_honor: "\u94F6\u884C\u62D2\u7EDD\u4E86\u6B64\u4EA4\u6613\uFF0C\u8BF7\u8054\u7CFB\u53D1\u5361\u884C\u6216\u5C1D\u8BD5\u5176\u4ED6\u652F\u4ED8\u65B9\u5F0F"
|
|
456
|
+
};
|
|
457
|
+
if (errorCode && codeMap[errorCode]) return codeMap[errorCode];
|
|
458
|
+
if (errorMessage) return errorMessage;
|
|
459
|
+
return "\u652F\u4ED8\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u95EE\u9898\uFF0C\u8BF7\u91CD\u8BD5\u6216\u66F4\u6362\u652F\u4ED8\u65B9\u5F0F";
|
|
460
|
+
}
|
|
461
|
+
__name(friendlyErrorReason, "friendlyErrorReason");
|
|
462
|
+
|
|
463
|
+
// src/payment.module.ts
|
|
464
|
+
import { Module, Global } from "@nestjs/common";
|
|
465
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
466
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
467
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
468
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
469
|
+
} else {
|
|
470
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
471
|
+
if (d = decorators[i]) {
|
|
472
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
477
|
+
}
|
|
478
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
479
|
+
var PAYMENT_PROVIDERS = "PAYMENT_PROVIDERS";
|
|
480
|
+
var ZuckerPaymentModule = class _ZuckerPaymentModule {
|
|
481
|
+
static {
|
|
482
|
+
__name(this, "ZuckerPaymentModule");
|
|
483
|
+
}
|
|
484
|
+
static forRoot(options = {}) {
|
|
485
|
+
return {
|
|
486
|
+
module: _ZuckerPaymentModule,
|
|
487
|
+
global: true,
|
|
488
|
+
providers: [
|
|
489
|
+
PaymentService,
|
|
490
|
+
OrderService,
|
|
491
|
+
PaymentRecoveryService,
|
|
492
|
+
{
|
|
493
|
+
provide: PAYMENT_PROVIDERS,
|
|
494
|
+
useFactory: /* @__PURE__ */ __name((paymentService) => {
|
|
495
|
+
for (const provider of options.providers ?? []) {
|
|
496
|
+
paymentService.register(provider);
|
|
497
|
+
}
|
|
498
|
+
return paymentService.listProviders();
|
|
499
|
+
}, "useFactory"),
|
|
500
|
+
inject: [
|
|
501
|
+
PaymentService
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
],
|
|
505
|
+
exports: [
|
|
506
|
+
PaymentService,
|
|
507
|
+
OrderService,
|
|
508
|
+
PaymentRecoveryService
|
|
509
|
+
]
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
static forRootAsync(options) {
|
|
513
|
+
return {
|
|
514
|
+
module: _ZuckerPaymentModule,
|
|
515
|
+
global: true,
|
|
516
|
+
imports: options.imports ?? [],
|
|
517
|
+
providers: [
|
|
518
|
+
PaymentService,
|
|
519
|
+
OrderService,
|
|
520
|
+
PaymentRecoveryService,
|
|
521
|
+
{
|
|
522
|
+
provide: PAYMENT_PROVIDERS,
|
|
523
|
+
useFactory: /* @__PURE__ */ __name(async (paymentService, ...args) => {
|
|
524
|
+
const factoryFn = options.useFactory;
|
|
525
|
+
const moduleOptions = await factoryFn(...args);
|
|
526
|
+
for (const provider of moduleOptions.providers ?? []) {
|
|
527
|
+
paymentService.register(provider);
|
|
528
|
+
}
|
|
529
|
+
return paymentService.listProviders();
|
|
530
|
+
}, "useFactory"),
|
|
531
|
+
inject: [
|
|
532
|
+
PaymentService,
|
|
533
|
+
...options.inject ?? []
|
|
534
|
+
]
|
|
535
|
+
}
|
|
536
|
+
],
|
|
537
|
+
exports: [
|
|
538
|
+
PaymentService,
|
|
539
|
+
OrderService,
|
|
540
|
+
PaymentRecoveryService
|
|
541
|
+
]
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
ZuckerPaymentModule = _ts_decorate4([
|
|
546
|
+
Global(),
|
|
547
|
+
Module({})
|
|
548
|
+
], ZuckerPaymentModule);
|
|
549
|
+
export {
|
|
550
|
+
OrderService,
|
|
551
|
+
PAYMENT_PROVIDERS,
|
|
552
|
+
PaymentEvents,
|
|
553
|
+
PaymentRecoveryService,
|
|
554
|
+
PaymentService,
|
|
555
|
+
RECOVERY_MAIL_SENDER,
|
|
556
|
+
RECOVERY_URL_GENERATOR,
|
|
557
|
+
RECOVERY_USER_LOOKUP,
|
|
558
|
+
ZuckerPaymentModule,
|
|
559
|
+
friendlyErrorReason
|
|
560
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zucker-framework/payment",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"NOTICE",
|
|
11
|
+
"THIRD_PARTY_NOTICES"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@zucker-framework/core": "1.0.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@nestjs/common": "^12.0.0",
|
|
18
|
+
"@nestjs/core": "^12.0.0",
|
|
19
|
+
"@nestjs/event-emitter": "^12.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@nestjs/event-emitter": "^12.0.0"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"author": "Lind",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/Lind-pro/zucker-framework.git",
|
|
29
|
+
"directory": "packages/payment"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --external @nestjs/common --external @nestjs/core --external @nestjs/event-emitter --external @zucker-framework/core",
|
|
37
|
+
"clean": "rm -rf dist",
|
|
38
|
+
"lint": "eslint src/"
|
|
39
|
+
}
|
|
40
|
+
}
|