@zucker-framework/payment 1.0.0 → 1.0.2

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 CHANGED
@@ -21,16 +21,31 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ DatabaseOrderPersistence: () => DatabaseOrderPersistence,
25
+ ORDER_PERSISTENCE: () => ORDER_PERSISTENCE,
26
+ OrderNotFoundError: () => OrderNotFoundError,
24
27
  OrderService: () => OrderService,
25
28
  PAYMENT_PROVIDERS: () => PAYMENT_PROVIDERS,
29
+ PayPalPaymentTransport: () => PayPalPaymentTransport,
26
30
  PaymentEvents: () => PaymentEvents,
27
31
  PaymentRecoveryService: () => PaymentRecoveryService,
28
32
  PaymentService: () => PaymentService,
33
+ RECOVERY_ATTEMPT_STORE: () => RECOVERY_ATTEMPT_STORE,
29
34
  RECOVERY_MAIL_SENDER: () => RECOVERY_MAIL_SENDER,
30
35
  RECOVERY_URL_GENERATOR: () => RECOVERY_URL_GENERATOR,
31
36
  RECOVERY_USER_LOOKUP: () => RECOVERY_USER_LOOKUP,
32
37
  ZuckerPaymentModule: () => ZuckerPaymentModule,
33
- friendlyErrorReason: () => friendlyErrorReason
38
+ createStripeCheckoutSession: () => createStripeCheckoutSession,
39
+ createStripeClient: () => createStripeClient,
40
+ decodePayPalWebhookEvent: () => decodePayPalWebhookEvent,
41
+ friendlyErrorReason: () => friendlyErrorReason,
42
+ observePayPalOrder: () => observePayPalOrder,
43
+ observeStripeCheckoutSession: () => observeStripeCheckoutSession,
44
+ observeStripePaymentIntent: () => observeStripePaymentIntent,
45
+ retrieveStripeCheckoutSession: () => retrieveStripeCheckoutSession,
46
+ retrieveStripePaymentIntent: () => retrieveStripePaymentIntent,
47
+ stripeResourceId: () => stripeResourceId,
48
+ verifyStripeWebhookEvent: () => verifyStripeWebhookEvent
34
49
  });
35
50
  module.exports = __toCommonJS(index_exports);
36
51
 
@@ -120,6 +135,48 @@ PaymentService = _ts_decorate([
120
135
 
121
136
  // src/order.service.ts
122
137
  var import_common2 = require("@nestjs/common");
138
+
139
+ // src/order-persistence.ts
140
+ var ORDER_PERSISTENCE = "PAYMENT_ORDER_PERSISTENCE";
141
+ var DatabaseOrderPersistence = class {
142
+ static {
143
+ __name(this, "DatabaseOrderPersistence");
144
+ }
145
+ model;
146
+ constructor(model) {
147
+ this.model = model;
148
+ }
149
+ create(args) {
150
+ return this.model.create(args);
151
+ }
152
+ findUnique(args) {
153
+ return this.model.findUnique(args);
154
+ }
155
+ findMany(args) {
156
+ return this.model.findMany(args);
157
+ }
158
+ update(args) {
159
+ return this.model.update(args);
160
+ }
161
+ updateMany(args) {
162
+ return this.model.updateMany(args);
163
+ }
164
+ count(args) {
165
+ return this.model.count(args);
166
+ }
167
+ async compareAndSet(id, expected, data) {
168
+ const result = await this.model.updateMany({
169
+ where: {
170
+ ...expected,
171
+ id
172
+ },
173
+ data
174
+ });
175
+ return result.count === 1;
176
+ }
177
+ };
178
+
179
+ // src/order.service.ts
123
180
  function _ts_decorate2(decorators, target, key, desc) {
124
181
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
125
182
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -140,132 +197,161 @@ function _ts_metadata(metadataKey, metadataValue) {
140
197
  }
141
198
  }
142
199
  __name(_ts_metadata, "_ts_metadata");
143
- var OrderService = class _OrderService {
200
+ function _ts_param(paramIndex, decorator) {
201
+ return function(target, key) {
202
+ decorator(target, key, paramIndex);
203
+ };
204
+ }
205
+ __name(_ts_param, "_ts_param");
206
+ var OrderNotFoundError = class extends Error {
207
+ static {
208
+ __name(this, "OrderNotFoundError");
209
+ }
210
+ orderId;
211
+ constructor(orderId) {
212
+ super(`\u8BA2\u5355 "${orderId}" \u4E0D\u5B58\u5728`), this.orderId = orderId;
213
+ this.name = "OrderNotFoundError";
214
+ }
215
+ };
216
+ var OrderService = class {
144
217
  static {
145
218
  __name(this, "OrderService");
146
219
  }
147
220
  paymentService;
148
- logger = new import_common2.Logger(_OrderService.name);
149
- orders = /* @__PURE__ */ new Map();
150
- constructor(paymentService) {
221
+ orders;
222
+ constructor(paymentService, orders) {
151
223
  this.paymentService = paymentService;
224
+ this.orders = orders;
225
+ if (!orders) throw new Error("Order persistence must be configured");
152
226
  }
153
- /** 创建订单并发起支付 */
154
227
  async createOrder(params) {
155
- const { orderId, providerId, payment } = params;
156
- if (this.orders.has(orderId)) {
157
- throw new Error(`\u8BA2\u5355 "${orderId}" \u5DF2\u5B58\u5728`);
228
+ if (await this.orders.findUnique({
229
+ where: {
230
+ id: params.orderId
231
+ }
232
+ })) {
233
+ throw new Error(`\u8BA2\u5355 "${params.orderId}" \u5DF2\u5B58\u5728`);
158
234
  }
159
235
  const now = /* @__PURE__ */ new Date();
160
- const order = {
161
- id: orderId,
162
- providerId,
163
- amount: payment.amount,
164
- currency: payment.currency,
165
- status: "created",
166
- metadata: payment.metadata,
167
- createdAt: now,
168
- updatedAt: now
169
- };
170
- this.orders.set(orderId, order);
171
- this.logger.log(`\u8BA2\u5355 "${orderId}" \u5DF2\u521B\u5EFA`);
172
- try {
173
- const intent = await this.paymentService.createPaymentIntent(providerId, payment);
174
- order.paymentIntentId = intent.id;
175
- order.status = "paying";
176
- order.updatedAt = /* @__PURE__ */ new Date();
177
- this.logger.log(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u5DF2\u53D1\u8D77, intentId=${intent.id}`);
178
- } catch (error) {
179
- order.status = "failed";
180
- order.updatedAt = /* @__PURE__ */ new Date();
181
- this.logger.error(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u53D1\u8D77\u5931\u8D25`, error);
182
- throw error;
183
- }
184
- return {
185
- ...order
186
- };
236
+ const order = await this.orders.create({
237
+ data: {
238
+ id: params.orderId,
239
+ providerId: params.providerId,
240
+ amount: params.payment.amount,
241
+ currency: params.payment.currency,
242
+ metadata: params.payment.metadata,
243
+ status: "created",
244
+ createdAt: now,
245
+ updatedAt: now
246
+ }
247
+ });
248
+ return this.initiatePayment(order, params.payment);
249
+ }
250
+ async initiatePayment(order, payment) {
251
+ const intent = await this.paymentService.createPaymentIntent(order.providerId, payment);
252
+ await this.transition(order.id, [
253
+ "created"
254
+ ], "paying", {
255
+ paymentIntentId: intent.id
256
+ });
257
+ return this.getOrder(order.id);
187
258
  }
188
- /** 确认订单支付 */
189
259
  async confirmOrder(orderId) {
190
- const order = this.getOrder(orderId);
191
- if (order.status !== "paying") {
192
- throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u786E\u8BA4\u652F\u4ED8`);
193
- }
194
- if (!order.paymentIntentId) {
195
- throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentIntentId`);
196
- }
260
+ const order = await this.getOrder(orderId);
261
+ if (!order.paymentIntentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentIntentId`);
262
+ await this.transition(orderId, [
263
+ "paying"
264
+ ], "confirming");
197
265
  const result = await this.paymentService.confirmPayment(order.providerId, order.paymentIntentId);
198
266
  if (result.status === "succeeded") {
199
- order.status = "paid";
200
- order.paymentId = result.id;
201
- order.paidAt = result.paidAt ?? /* @__PURE__ */ new Date();
202
- } else {
203
- order.status = "failed";
267
+ await this.transition(orderId, [
268
+ "confirming"
269
+ ], "paid", {
270
+ paymentId: result.id,
271
+ paidAt: result.paidAt ?? /* @__PURE__ */ new Date()
272
+ });
273
+ } else if (result.status === "failed" || result.status === "cancelled") {
274
+ await this.transition(orderId, [
275
+ "confirming"
276
+ ], "failed");
277
+ } else if (result.status === "refunded") {
278
+ await this.transition(orderId, [
279
+ "confirming"
280
+ ], "refunded");
204
281
  }
205
- order.updatedAt = /* @__PURE__ */ new Date();
206
- this.logger.log(`\u8BA2\u5355 "${orderId}" \u652F\u4ED8\u7ED3\u679C: ${result.status}`);
207
282
  return result;
208
283
  }
209
- /** 订单退款 */
210
284
  async refundOrder(orderId, amount, reason) {
211
- const order = this.getOrder(orderId);
212
- if (order.status !== "paid" && order.status !== "refunding") {
213
- throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u9000\u6B3E`);
214
- }
215
- if (!order.paymentId) {
216
- throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentId`);
217
- }
218
- order.status = "refunding";
219
- order.updatedAt = /* @__PURE__ */ new Date();
285
+ const order = await this.getOrder(orderId);
286
+ if (!order.paymentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentId`);
287
+ await this.transition(orderId, [
288
+ "paid"
289
+ ], "refunding");
220
290
  const result = await this.paymentService.refund(order.providerId, order.paymentId, amount, reason);
221
291
  if (result.status === "succeeded") {
222
- order.status = "refunded";
223
- order.refundedAt = /* @__PURE__ */ new Date();
292
+ await this.transition(orderId, [
293
+ "refunding"
294
+ ], "refunded", {
295
+ refundedAt: /* @__PURE__ */ new Date()
296
+ });
224
297
  } else if (result.status === "failed") {
225
- order.status = "paid";
298
+ await this.transition(orderId, [
299
+ "refunding"
300
+ ], "paid");
226
301
  }
227
- order.updatedAt = /* @__PURE__ */ new Date();
228
- this.logger.log(`\u8BA2\u5355 "${orderId}" \u9000\u6B3E\u7ED3\u679C: ${result.status}`);
229
302
  return result;
230
303
  }
231
- /** 取消订单 */
232
- cancelOrder(orderId) {
233
- const order = this.getOrder(orderId);
234
- const cancellableStates = [
304
+ async retryOrder(orderId) {
305
+ const order = await this.getOrder(orderId);
306
+ await this.transition(orderId, [
307
+ "failed"
308
+ ], "created");
309
+ return this.initiatePayment(order, {
310
+ amount: order.amount,
311
+ currency: order.currency,
312
+ metadata: order.metadata
313
+ });
314
+ }
315
+ async cancelOrder(orderId) {
316
+ await this.transition(orderId, [
235
317
  "created",
236
318
  "paying",
237
319
  "failed"
238
- ];
239
- if (!cancellableStates.includes(order.status)) {
240
- throw new Error(`\u8BA2\u5355 "${orderId}" \u72B6\u6001\u4E3A "${order.status}"\uFF0C\u65E0\u6CD5\u53D6\u6D88`);
241
- }
242
- order.status = "cancelled";
243
- order.updatedAt = /* @__PURE__ */ new Date();
244
- this.logger.log(`\u8BA2\u5355 "${orderId}" \u5DF2\u53D6\u6D88`);
245
- return {
246
- ...order
247
- };
320
+ ], "cancelled");
321
+ return this.getOrder(orderId);
248
322
  }
249
- /** 查询订单 */
250
- getOrder(orderId) {
251
- const order = this.orders.get(orderId);
252
- if (!order) {
253
- throw new Error(`\u8BA2\u5355 "${orderId}" \u4E0D\u5B58\u5728`);
254
- }
323
+ async getOrder(orderId) {
324
+ const order = await this.orders.findUnique({
325
+ where: {
326
+ id: orderId
327
+ }
328
+ });
329
+ if (!order) throw new OrderNotFoundError(orderId);
255
330
  return order;
256
331
  }
257
- /** 列出所有订单 */
258
332
  listOrders() {
259
- return Array.from(this.orders.values()).map((o) => ({
260
- ...o
261
- }));
333
+ return this.orders.findMany();
334
+ }
335
+ async transition(id, from, to, data = {}) {
336
+ const changed = await this.orders.compareAndSet(id, {
337
+ status: {
338
+ in: from
339
+ }
340
+ }, {
341
+ ...data,
342
+ status: to,
343
+ updatedAt: /* @__PURE__ */ new Date()
344
+ });
345
+ if (!changed) throw new Error(`\u8BA2\u5355 "${id}" \u4E0D\u5B58\u5728\u6216\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u65E0\u6CD5\u8F6C\u6362\u4E3A ${to}`);
262
346
  }
263
347
  };
264
348
  OrderService = _ts_decorate2([
265
349
  (0, import_common2.Injectable)(),
350
+ _ts_param(1, (0, import_common2.Inject)(ORDER_PERSISTENCE)),
266
351
  _ts_metadata("design:type", Function),
267
352
  _ts_metadata("design:paramtypes", [
268
- typeof PaymentService === "undefined" ? Object : PaymentService
353
+ typeof PaymentService === "undefined" ? Object : PaymentService,
354
+ typeof OrderPersistence === "undefined" ? Object : OrderPersistence
269
355
  ])
270
356
  ], OrderService);
271
357
 
@@ -304,15 +390,16 @@ function _ts_metadata2(metadataKey, metadataValue) {
304
390
  }
305
391
  }
306
392
  __name(_ts_metadata2, "_ts_metadata");
307
- function _ts_param(paramIndex, decorator) {
393
+ function _ts_param2(paramIndex, decorator) {
308
394
  return function(target, key) {
309
395
  decorator(target, key, paramIndex);
310
396
  };
311
397
  }
312
- __name(_ts_param, "_ts_param");
398
+ __name(_ts_param2, "_ts_param");
313
399
  var RECOVERY_MAIL_SENDER = "RECOVERY_MAIL_SENDER";
314
400
  var RECOVERY_USER_LOOKUP = "RECOVERY_USER_LOOKUP";
315
401
  var RECOVERY_URL_GENERATOR = "RECOVERY_URL_GENERATOR";
402
+ var RECOVERY_ATTEMPT_STORE = "RECOVERY_ATTEMPT_STORE";
316
403
  var DEFAULT_OPTIONS = {
317
404
  maxAttempts: 3,
318
405
  retryLinkExpireHours: 48,
@@ -323,18 +410,16 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
323
410
  __name(this, "PaymentRecoveryService");
324
411
  }
325
412
  orderService;
326
- paymentService;
413
+ attempts;
327
414
  mailSender;
328
415
  userLookup;
329
416
  urlGenerator;
330
417
  eventEmitter;
331
418
  logger = new import_common3.Logger(_PaymentRecoveryService.name);
332
419
  options;
333
- /** 内存挽回计数(生产环境建议由业务项目持久化) */
334
- recoveryAttempts = /* @__PURE__ */ new Map();
335
- constructor(orderService, paymentService, mailSender, userLookup, urlGenerator, eventEmitter) {
420
+ constructor(orderService, attempts, mailSender, userLookup, urlGenerator, eventEmitter) {
336
421
  this.orderService = orderService;
337
- this.paymentService = paymentService;
422
+ this.attempts = attempts;
338
423
  this.mailSender = mailSender;
339
424
  this.userLookup = userLookup;
340
425
  this.urlGenerator = urlGenerator;
@@ -353,8 +438,9 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
353
438
  async handleFailedPayment(orderId, errorCode, errorMessage) {
354
439
  let order;
355
440
  try {
356
- order = this.orderService.getOrder(orderId);
357
- } catch {
441
+ order = await this.orderService.getOrder(orderId);
442
+ } catch (error) {
443
+ if (!(error instanceof OrderNotFoundError)) throw error;
358
444
  this.logger.warn(`\u8BA2\u5355 ${orderId} \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u633D\u56DE`);
359
445
  return;
360
446
  }
@@ -362,11 +448,11 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
362
448
  this.logger.warn(`\u8BA2\u5355 ${orderId} \u72B6\u6001\u4E3A ${order.status}\uFF0C\u8DF3\u8FC7\u633D\u56DE`);
363
449
  return;
364
450
  }
365
- const attempts = this.recoveryAttempts.get(orderId);
366
- if (attempts && attempts.count >= this.options.maxAttempts) {
367
- this.logger.log(`\u8BA2\u5355 ${orderId} \u5DF2\u633D\u56DE ${attempts.count} \u6B21\uFF0C\u4E0D\u518D\u53D1\u9001`);
451
+ if (!this.mailSender) {
452
+ this.logger.warn("Recovery mail sender is not configured; no attempt was reserved");
368
453
  return;
369
454
  }
455
+ if (!this.attempts) throw new Error("Recovery attempt persistence must be configured");
370
456
  const userId = order.metadata?.userId;
371
457
  let email;
372
458
  let userName = "";
@@ -387,32 +473,20 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
387
473
  this.logger.warn(`\u8BA2\u5355 ${orderId} \u65E0\u6CD5\u751F\u6210\u91CD\u8BD5\u94FE\u63A5`);
388
474
  return;
389
475
  }
390
- const errorReason = friendlyErrorReason(errorCode, errorMessage);
391
- const attemptNumber = (attempts?.count ?? 0) + 1;
392
- if (this.mailSender) {
393
- try {
394
- await this.mailSender.sendRecoveryEmail({
395
- to: email,
396
- userName: userName || email.split("@")[0],
397
- orderId: order.id,
398
- orderNumber: order.metadata?.orderNumber,
399
- amount: order.amount,
400
- currency: order.currency,
401
- errorReason,
402
- retryUrl,
403
- expireHours: this.options.retryLinkExpireHours
404
- });
405
- this.logger.log(`\u633D\u56DE\u90AE\u4EF6\u5DF2\u53D1\u9001: ${email}, \u8BA2\u5355 ${orderId} (\u7B2C ${attemptNumber} \u6B21)`);
406
- } catch (err) {
407
- this.logger.error(`\u633D\u56DE\u90AE\u4EF6\u53D1\u9001\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
408
- return;
409
- }
410
- } else {
411
- this.logger.log(`[Recovery:NoMailSender] \u8BA2\u5355 ${orderId} \u9700\u8981\u633D\u56DE, email=${email}, retryUrl=${retryUrl}, reason=${errorReason}`);
412
- }
413
- this.recoveryAttempts.set(orderId, {
414
- count: attemptNumber,
415
- lastAt: /* @__PURE__ */ new Date()
476
+ const reservation = await this.attempts.claim(orderId, this.options.maxAttempts, /* @__PURE__ */ new Date());
477
+ if (!reservation) return;
478
+ const attemptNumber = reservation.attemptNumber;
479
+ await this.mailSender.sendRecoveryEmail({
480
+ to: email,
481
+ userName: userName || email.split("@")[0],
482
+ orderId: order.id,
483
+ orderNumber: order.metadata?.orderNumber,
484
+ amount: order.amount,
485
+ currency: order.currency,
486
+ errorReason: friendlyErrorReason(errorCode, errorMessage),
487
+ retryUrl,
488
+ expireHours: this.options.retryLinkExpireHours,
489
+ idempotencyKey: `payment-recovery:${orderId}:${attemptNumber}`
416
490
  });
417
491
  if (this.eventEmitter) {
418
492
  const event = {
@@ -428,28 +502,12 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
428
502
  /**
429
503
  * 重试失败订单 — 将 failed → created 并重新发起支付
430
504
  */
431
- async retryOrder(orderId) {
432
- const order = this.orderService.getOrder(orderId);
433
- if (order.status !== "failed") {
434
- throw new Error(`\u8BA2\u5355 ${orderId} \u72B6\u6001\u4E3A ${order.status}\uFF0C\u4EC5 failed \u8BA2\u5355\u53EF\u91CD\u8BD5`);
435
- }
436
- const intent = await this.paymentService.createPaymentIntent(order.providerId, {
437
- amount: order.amount,
438
- currency: order.currency,
439
- metadata: order.metadata
440
- });
441
- const internalOrder = this.orderService.getOrder(orderId);
442
- internalOrder.paymentIntentId = intent.id;
443
- internalOrder.status = "paying";
444
- internalOrder.updatedAt = /* @__PURE__ */ new Date();
445
- this.logger.log(`\u8BA2\u5355 ${orderId} \u91CD\u8BD5\u652F\u4ED8, \u65B0 intentId=${intent.id}`);
446
- return {
447
- ...internalOrder
448
- };
505
+ retryOrder(orderId) {
506
+ return this.orderService.retryOrder(orderId);
449
507
  }
450
- /** 获取订单挽回记录 */
451
508
  getRecoveryAttempts(orderId) {
452
- return this.recoveryAttempts.get(orderId);
509
+ if (!this.attempts) throw new Error("Recovery attempt persistence must be configured");
510
+ return this.attempts.get(orderId);
453
511
  }
454
512
  async generateRetryUrl(order) {
455
513
  if (this.urlGenerator) {
@@ -460,17 +518,18 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
460
518
  };
461
519
  PaymentRecoveryService = _ts_decorate3([
462
520
  (0, import_common3.Injectable)(),
463
- _ts_param(2, (0, import_common3.Optional)()),
464
- _ts_param(2, (0, import_common3.Inject)(RECOVERY_MAIL_SENDER)),
465
- _ts_param(3, (0, import_common3.Optional)()),
466
- _ts_param(3, (0, import_common3.Inject)(RECOVERY_USER_LOOKUP)),
467
- _ts_param(4, (0, import_common3.Optional)()),
468
- _ts_param(4, (0, import_common3.Inject)(RECOVERY_URL_GENERATOR)),
469
- _ts_param(5, (0, import_common3.Optional)()),
521
+ _ts_param2(1, (0, import_common3.Inject)(RECOVERY_ATTEMPT_STORE)),
522
+ _ts_param2(2, (0, import_common3.Optional)()),
523
+ _ts_param2(2, (0, import_common3.Inject)(RECOVERY_MAIL_SENDER)),
524
+ _ts_param2(3, (0, import_common3.Optional)()),
525
+ _ts_param2(3, (0, import_common3.Inject)(RECOVERY_USER_LOOKUP)),
526
+ _ts_param2(4, (0, import_common3.Optional)()),
527
+ _ts_param2(4, (0, import_common3.Inject)(RECOVERY_URL_GENERATOR)),
528
+ _ts_param2(5, (0, import_common3.Optional)()),
470
529
  _ts_metadata2("design:type", Function),
471
530
  _ts_metadata2("design:paramtypes", [
472
531
  typeof OrderService === "undefined" ? Object : OrderService,
473
- typeof PaymentService === "undefined" ? Object : PaymentService,
532
+ Object,
474
533
  typeof RecoveryMailSender === "undefined" ? Object : RecoveryMailSender,
475
534
  typeof RecoveryUserLookup === "undefined" ? Object : RecoveryUserLookup,
476
535
  typeof RecoveryUrlGenerator === "undefined" ? Object : RecoveryUrlGenerator,
@@ -510,30 +569,63 @@ function _ts_decorate4(decorators, target, key, desc) {
510
569
  }
511
570
  __name(_ts_decorate4, "_ts_decorate");
512
571
  var PAYMENT_PROVIDERS = "PAYMENT_PROVIDERS";
572
+ var PAYMENT_OPTIONS = /* @__PURE__ */ Symbol("PAYMENT_OPTIONS");
573
+ function configuredProviders() {
574
+ return [
575
+ {
576
+ provide: ORDER_PERSISTENCE,
577
+ inject: [
578
+ PAYMENT_OPTIONS
579
+ ],
580
+ useFactory: /* @__PURE__ */ __name((options) => {
581
+ if (!options.orderPersistence) throw new Error("Order persistence must be configured");
582
+ return options.orderPersistence;
583
+ }, "useFactory")
584
+ },
585
+ {
586
+ provide: RECOVERY_ATTEMPT_STORE,
587
+ inject: [
588
+ PAYMENT_OPTIONS
589
+ ],
590
+ useFactory: /* @__PURE__ */ __name((options) => options.recoveryAttempts ?? null, "useFactory")
591
+ },
592
+ {
593
+ provide: PaymentService,
594
+ inject: [
595
+ PAYMENT_OPTIONS
596
+ ],
597
+ useFactory: /* @__PURE__ */ __name((options) => {
598
+ const service = new PaymentService();
599
+ for (const provider of options.providers ?? []) service.register(provider);
600
+ return service;
601
+ }, "useFactory")
602
+ },
603
+ OrderService,
604
+ PaymentRecoveryService,
605
+ {
606
+ provide: PAYMENT_PROVIDERS,
607
+ inject: [
608
+ PaymentService
609
+ ],
610
+ useFactory: /* @__PURE__ */ __name((service) => service.listProviders(), "useFactory")
611
+ }
612
+ ];
613
+ }
614
+ __name(configuredProviders, "configuredProviders");
513
615
  var ZuckerPaymentModule = class _ZuckerPaymentModule {
514
616
  static {
515
617
  __name(this, "ZuckerPaymentModule");
516
618
  }
517
- static forRoot(options = {}) {
619
+ static forRoot(options) {
518
620
  return {
519
621
  module: _ZuckerPaymentModule,
520
622
  global: true,
521
623
  providers: [
522
- PaymentService,
523
- OrderService,
524
- PaymentRecoveryService,
525
624
  {
526
- provide: PAYMENT_PROVIDERS,
527
- useFactory: /* @__PURE__ */ __name((paymentService) => {
528
- for (const provider of options.providers ?? []) {
529
- paymentService.register(provider);
530
- }
531
- return paymentService.listProviders();
532
- }, "useFactory"),
533
- inject: [
534
- PaymentService
535
- ]
536
- }
625
+ provide: PAYMENT_OPTIONS,
626
+ useValue: options
627
+ },
628
+ ...configuredProviders()
537
629
  ],
538
630
  exports: [
539
631
  PaymentService,
@@ -548,24 +640,12 @@ var ZuckerPaymentModule = class _ZuckerPaymentModule {
548
640
  global: true,
549
641
  imports: options.imports ?? [],
550
642
  providers: [
551
- PaymentService,
552
- OrderService,
553
- PaymentRecoveryService,
554
643
  {
555
- provide: PAYMENT_PROVIDERS,
556
- useFactory: /* @__PURE__ */ __name(async (paymentService, ...args) => {
557
- const factoryFn = options.useFactory;
558
- const moduleOptions = await factoryFn(...args);
559
- for (const provider of moduleOptions.providers ?? []) {
560
- paymentService.register(provider);
561
- }
562
- return paymentService.listProviders();
563
- }, "useFactory"),
564
- inject: [
565
- PaymentService,
566
- ...options.inject ?? []
567
- ]
568
- }
644
+ provide: PAYMENT_OPTIONS,
645
+ useFactory: options.useFactory,
646
+ inject: options.inject ?? []
647
+ },
648
+ ...configuredProviders()
569
649
  ],
570
650
  exports: [
571
651
  PaymentService,
@@ -579,16 +659,243 @@ ZuckerPaymentModule = _ts_decorate4([
579
659
  (0, import_common4.Global)(),
580
660
  (0, import_common4.Module)({})
581
661
  ], ZuckerPaymentModule);
662
+
663
+ // src/providers/stripe.ts
664
+ var import_node_module = require("module");
665
+ var import_meta = {};
666
+ var requireSdk = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url);
667
+ function createStripeClient(secretKey, options) {
668
+ const StripeSdk = requireSdk("stripe");
669
+ return new StripeSdk(secretKey, options);
670
+ }
671
+ __name(createStripeClient, "createStripeClient");
672
+ function verifyStripeWebhookEvent(client, payload, signature, webhookSecret) {
673
+ return client.webhooks.constructEvent(payload, signature, webhookSecret);
674
+ }
675
+ __name(verifyStripeWebhookEvent, "verifyStripeWebhookEvent");
676
+ function stripeResourceId(value) {
677
+ return typeof value === "string" ? value : value?.id;
678
+ }
679
+ __name(stripeResourceId, "stripeResourceId");
680
+ function observeStripeCheckoutSession(raw) {
681
+ return {
682
+ raw,
683
+ id: raw.id,
684
+ url: raw.url || void 0,
685
+ clientReferenceId: raw.client_reference_id || void 0,
686
+ paymentIntentId: stripeResourceId(raw.payment_intent),
687
+ subscriptionId: stripeResourceId(raw.subscription),
688
+ settlement: raw.payment_status === "paid" ? "paid" : raw.status === "expired" ? "unpaid" : "unknown"
689
+ };
690
+ }
691
+ __name(observeStripeCheckoutSession, "observeStripeCheckoutSession");
692
+ async function createStripeCheckoutSession(client, params) {
693
+ return observeStripeCheckoutSession(await client.checkout.sessions.create(params));
694
+ }
695
+ __name(createStripeCheckoutSession, "createStripeCheckoutSession");
696
+ async function retrieveStripeCheckoutSession(client, id) {
697
+ return observeStripeCheckoutSession(await client.checkout.sessions.retrieve(id));
698
+ }
699
+ __name(retrieveStripeCheckoutSession, "retrieveStripeCheckoutSession");
700
+ function observeStripePaymentIntent(raw) {
701
+ const charge = typeof raw.latest_charge === "object" ? raw.latest_charge : null;
702
+ return {
703
+ raw,
704
+ id: raw.id,
705
+ chargeId: stripeResourceId(raw.latest_charge) ?? null,
706
+ paymentMethodType: charge?.payment_method_details?.type,
707
+ failureCode: raw.last_payment_error?.code,
708
+ failureMessage: raw.last_payment_error?.message,
709
+ settlement: raw.status === "succeeded" ? "paid" : raw.status === "canceled" ? "unpaid" : "unknown"
710
+ };
711
+ }
712
+ __name(observeStripePaymentIntent, "observeStripePaymentIntent");
713
+ async function retrieveStripePaymentIntent(client, id, params) {
714
+ const raw = params ? await client.paymentIntents.retrieve(id, params) : await client.paymentIntents.retrieve(id);
715
+ return observeStripePaymentIntent(raw);
716
+ }
717
+ __name(retrieveStripePaymentIntent, "retrieveStripePaymentIntent");
718
+
719
+ // src/providers/paypal.ts
720
+ var import_node_module2 = require("module");
721
+ var import_meta2 = {};
722
+ var requireSdk2 = (0, import_node_module2.createRequire)(typeof __filename === "string" ? __filename : import_meta2.url);
723
+ function observePayPalOrder(raw) {
724
+ return {
725
+ raw,
726
+ id: raw.id,
727
+ approveUrl: raw.links?.find((link) => link.rel === "approve")?.href,
728
+ captureId: raw.purchaseUnits?.[0]?.payments?.captures?.[0]?.id,
729
+ settlement: raw.status === "COMPLETED" ? "paid" : raw.status === "VOIDED" ? "unpaid" : "unknown"
730
+ };
731
+ }
732
+ __name(observePayPalOrder, "observePayPalOrder");
733
+ function asRecord(value) {
734
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
735
+ }
736
+ __name(asRecord, "asRecord");
737
+ function decodePayPalWebhookEvent(payload) {
738
+ const raw = asRecord(JSON.parse(payload));
739
+ if (!raw || typeof raw.event_type !== "string") throw new Error("Invalid PayPal webhook event");
740
+ const type = raw.event_type;
741
+ const kind = type === "PAYMENT.CAPTURE.COMPLETED" ? "capture-succeeded" : type === "PAYMENT.CAPTURE.DENIED" || type === "PAYMENT.CAPTURE.DECLINED" ? "capture-failed" : "unknown";
742
+ if (kind === "unknown") return {
743
+ raw,
744
+ type,
745
+ kind
746
+ };
747
+ const resource = asRecord(raw.resource);
748
+ if (!resource || typeof resource.id !== "string" || !resource.id) {
749
+ throw new Error("PayPal capture event is missing its resource ID");
750
+ }
751
+ const reason = asRecord(resource.status_details)?.reason;
752
+ return {
753
+ raw,
754
+ type,
755
+ kind,
756
+ capture: {
757
+ raw: resource,
758
+ id: resource.id,
759
+ failureReason: typeof reason === "string" ? reason : void 0
760
+ }
761
+ };
762
+ }
763
+ __name(decodePayPalWebhookEvent, "decodePayPalWebhookEvent");
764
+ var PayPalPaymentTransport = class {
765
+ static {
766
+ __name(this, "PayPalPaymentTransport");
767
+ }
768
+ options;
769
+ orders;
770
+ baseUrl;
771
+ fetcher;
772
+ constructor(options) {
773
+ this.options = options;
774
+ const { Client, Environment, LogLevel, OrdersController } = requireSdk2("@paypal/paypal-server-sdk");
775
+ const client = new Client({
776
+ clientCredentialsAuthCredentials: {
777
+ oAuthClientId: options.clientId,
778
+ oAuthClientSecret: options.clientSecret
779
+ },
780
+ timeout: 0,
781
+ environment: options.mode === "live" ? Environment.Production : Environment.Sandbox,
782
+ logging: {
783
+ logLevel: LogLevel.Info,
784
+ logRequest: {
785
+ logBody: false
786
+ },
787
+ logResponse: {
788
+ logHeaders: false
789
+ }
790
+ }
791
+ });
792
+ this.orders = new OrdersController(client);
793
+ this.baseUrl = options.mode === "live" ? "https://api.paypal.com" : "https://api.sandbox.paypal.com";
794
+ this.fetcher = options.fetcher ?? fetch;
795
+ }
796
+ async createOrder(input) {
797
+ const { result } = await this.orders.createOrder(input);
798
+ return observePayPalOrder(result);
799
+ }
800
+ /** An explicit capture command; never invoked by getOrder or status observation. */
801
+ async captureOrder(input) {
802
+ const { result } = await this.orders.captureOrder(input);
803
+ return observePayPalOrder(result);
804
+ }
805
+ async getOrder(input) {
806
+ const { result } = await this.orders.getOrder(input);
807
+ return observePayPalOrder(result);
808
+ }
809
+ async getAccessToken() {
810
+ const response = await this.fetcher(`${this.baseUrl}/v1/oauth2/token`, {
811
+ method: "POST",
812
+ headers: {
813
+ "Content-Type": "application/x-www-form-urlencoded",
814
+ Authorization: `Basic ${Buffer.from(`${this.options.clientId}:${this.options.clientSecret}`).toString("base64")}`
815
+ },
816
+ body: "grant_type=client_credentials"
817
+ });
818
+ const data = await response.json();
819
+ if (!response.ok || typeof data.access_token !== "string" || !data.access_token) {
820
+ throw new Error("Unable to obtain PayPal access token");
821
+ }
822
+ return data.access_token;
823
+ }
824
+ async verifyWebhookSignature(payload, headers, webhookId) {
825
+ const transmissionId = headers["paypal-transmission-id"];
826
+ const transmissionTime = headers["paypal-transmission-time"];
827
+ const certUrl = headers["paypal-cert-url"];
828
+ const authAlgo = headers["paypal-auth-algo"];
829
+ const transmissionSig = headers["paypal-transmission-sig"];
830
+ if (!transmissionId || !transmissionTime || !certUrl || !authAlgo || !transmissionSig) {
831
+ this.options.logger?.warn("PayPal webhook is missing required signature headers");
832
+ return false;
833
+ }
834
+ try {
835
+ const accessToken = await this.getAccessToken();
836
+ const response = await this.fetcher(`${this.baseUrl}/v1/notifications/verify-webhook-signature`, {
837
+ method: "POST",
838
+ headers: {
839
+ "Content-Type": "application/json",
840
+ Authorization: `Bearer ${accessToken}`
841
+ },
842
+ body: JSON.stringify({
843
+ auth_algo: authAlgo,
844
+ cert_url: certUrl,
845
+ transmission_id: transmissionId,
846
+ transmission_sig: transmissionSig,
847
+ transmission_time: transmissionTime,
848
+ webhook_id: webhookId,
849
+ webhook_event: JSON.parse(payload)
850
+ })
851
+ });
852
+ const result = await response.json();
853
+ return response.ok && result.verification_status === "SUCCESS";
854
+ } catch (error) {
855
+ this.options.logger?.error(`PayPal webhook verification failed: ${error instanceof Error ? error.message : String(error)}`);
856
+ return false;
857
+ }
858
+ }
859
+ async refundCapture(captureId, request) {
860
+ const accessToken = await this.getAccessToken();
861
+ const response = await this.fetcher(`${this.baseUrl}/v2/payments/captures/${encodeURIComponent(captureId)}/refund`, {
862
+ method: "POST",
863
+ headers: {
864
+ "Content-Type": "application/json",
865
+ Authorization: `Bearer ${accessToken}`
866
+ },
867
+ body: JSON.stringify(request)
868
+ });
869
+ const result = await response.json();
870
+ if (!response.ok && result.status === "COMPLETED") throw new Error(`PayPal refund request failed (${response.status})`);
871
+ return result;
872
+ }
873
+ };
582
874
  // Annotate the CommonJS export names for ESM import in node:
583
875
  0 && (module.exports = {
876
+ DatabaseOrderPersistence,
877
+ ORDER_PERSISTENCE,
878
+ OrderNotFoundError,
584
879
  OrderService,
585
880
  PAYMENT_PROVIDERS,
881
+ PayPalPaymentTransport,
586
882
  PaymentEvents,
587
883
  PaymentRecoveryService,
588
884
  PaymentService,
885
+ RECOVERY_ATTEMPT_STORE,
589
886
  RECOVERY_MAIL_SENDER,
590
887
  RECOVERY_URL_GENERATOR,
591
888
  RECOVERY_USER_LOOKUP,
592
889
  ZuckerPaymentModule,
593
- friendlyErrorReason
890
+ createStripeCheckoutSession,
891
+ createStripeClient,
892
+ decodePayPalWebhookEvent,
893
+ friendlyErrorReason,
894
+ observePayPalOrder,
895
+ observeStripeCheckoutSession,
896
+ observeStripePaymentIntent,
897
+ retrieveStripeCheckoutSession,
898
+ retrieveStripePaymentIntent,
899
+ stripeResourceId,
900
+ verifyStripeWebhookEvent
594
901
  });