@nehorai/payments 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/index.cjs +116 -0
  3. package/dist/config/index.cjs.map +1 -0
  4. package/dist/config/index.d.cts +125 -0
  5. package/dist/config/index.d.ts +125 -0
  6. package/dist/config/index.js +83 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/factory.cjs +807 -0
  9. package/dist/factory.cjs.map +1 -0
  10. package/dist/factory.d.cts +96 -0
  11. package/dist/factory.d.ts +96 -0
  12. package/dist/factory.js +777 -0
  13. package/dist/factory.js.map +1 -0
  14. package/dist/index.cjs +1341 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +40 -0
  17. package/dist/index.d.ts +40 -0
  18. package/dist/index.js +1260 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/payment-orchestrator-CPaLmDM5.d.ts +404 -0
  21. package/dist/payment-orchestrator-Co_X6T_V.d.cts +404 -0
  22. package/dist/payment-types-68W-PlGg.d.cts +211 -0
  23. package/dist/payment-types-68W-PlGg.d.ts +211 -0
  24. package/dist/providers/interfaces/index.cjs +19 -0
  25. package/dist/providers/interfaces/index.cjs.map +1 -0
  26. package/dist/providers/interfaces/index.d.cts +80 -0
  27. package/dist/providers/interfaces/index.d.ts +80 -0
  28. package/dist/providers/interfaces/index.js +1 -0
  29. package/dist/providers/interfaces/index.js.map +1 -0
  30. package/dist/repository/interfaces/index.cjs +19 -0
  31. package/dist/repository/interfaces/index.cjs.map +1 -0
  32. package/dist/repository/interfaces/index.d.cts +556 -0
  33. package/dist/repository/interfaces/index.d.ts +556 -0
  34. package/dist/repository/interfaces/index.js +1 -0
  35. package/dist/repository/interfaces/index.js.map +1 -0
  36. package/dist/routing-engine.interface-DJzGXor9.d.cts +194 -0
  37. package/dist/routing-engine.interface-h9_GmQ4b.d.ts +194 -0
  38. package/dist/services/index.cjs +806 -0
  39. package/dist/services/index.cjs.map +1 -0
  40. package/dist/services/index.d.cts +75 -0
  41. package/dist/services/index.d.ts +75 -0
  42. package/dist/services/index.js +763 -0
  43. package/dist/services/index.js.map +1 -0
  44. package/dist/state-machine-Cu6_qKnv.d.cts +109 -0
  45. package/dist/state-machine-Cu6_qKnv.d.ts +109 -0
  46. package/dist/types/index.cjs +173 -0
  47. package/dist/types/index.cjs.map +1 -0
  48. package/dist/types/index.d.cts +127 -0
  49. package/dist/types/index.d.ts +127 -0
  50. package/dist/types/index.js +130 -0
  51. package/dist/types/index.js.map +1 -0
  52. package/dist/utils/index.cjs +167 -0
  53. package/dist/utils/index.cjs.map +1 -0
  54. package/dist/utils/index.d.cts +102 -0
  55. package/dist/utils/index.d.ts +102 -0
  56. package/dist/utils/index.js +127 -0
  57. package/dist/utils/index.js.map +1 -0
  58. package/package.json +68 -0
@@ -0,0 +1,807 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/factory.ts
21
+ var factory_exports = {};
22
+ __export(factory_exports, {
23
+ createPaymentServices: () => createPaymentServices,
24
+ getPaymentServices: () => getPaymentServices,
25
+ registerProvider: () => registerProvider,
26
+ resetPaymentServices: () => resetPaymentServices
27
+ });
28
+ module.exports = __toCommonJS(factory_exports);
29
+
30
+ // src/services/payment-orchestrator.ts
31
+ var import_crypto = require("crypto");
32
+
33
+ // src/config/payment-config.ts
34
+ function createPartialConfig(partial) {
35
+ return {
36
+ providers: partial.providers ?? {},
37
+ environment: partial.environment ?? "sandbox",
38
+ defaultCurrency: partial.defaultCurrency ?? "USD"
39
+ };
40
+ }
41
+ function getConfiguredProviders(config) {
42
+ const availability = {};
43
+ for (const [name, providerConfig] of Object.entries(config.providers)) {
44
+ availability[name] = providerConfig !== void 0 && Object.keys(providerConfig).length > 0;
45
+ }
46
+ return availability;
47
+ }
48
+
49
+ // src/services/circuit-breaker-storage.interface.ts
50
+ function createDefaultState(provider) {
51
+ return {
52
+ provider,
53
+ state: "closed",
54
+ failureCount: 0,
55
+ successCount: 0,
56
+ lastFailure: null,
57
+ openedAt: null,
58
+ nextRetryAt: null
59
+ };
60
+ }
61
+
62
+ // src/services/in-memory-storage.ts
63
+ var InMemoryCircuitBreakerStorage = class {
64
+ internalStates;
65
+ constructor() {
66
+ this.internalStates = /* @__PURE__ */ new Map();
67
+ }
68
+ async getState(provider) {
69
+ return this.internalStates.get(provider) ?? null;
70
+ }
71
+ async setState(provider, state) {
72
+ this.internalStates.set(provider, { ...state });
73
+ }
74
+ async getAllStates() {
75
+ return new Map(this.internalStates);
76
+ }
77
+ async deleteState(provider) {
78
+ this.internalStates.delete(provider);
79
+ }
80
+ async getOpenCircuits() {
81
+ const open = [];
82
+ for (const [provider, state] of this.internalStates) {
83
+ if (state.state === "open") {
84
+ open.push(provider);
85
+ }
86
+ }
87
+ return open;
88
+ }
89
+ async isHealthy() {
90
+ return true;
91
+ }
92
+ /**
93
+ * Clear all stored states (useful for testing)
94
+ */
95
+ clear() {
96
+ this.internalStates.clear();
97
+ }
98
+ /**
99
+ * Get current state count (useful for testing/debugging)
100
+ */
101
+ get size() {
102
+ return this.internalStates.size;
103
+ }
104
+ /**
105
+ * Synchronous state access (for backward compatibility with CircuitBreaker.isOpen)
106
+ *
107
+ * Note: Only available on InMemoryCircuitBreakerStorage.
108
+ * Database-backed storage cannot provide sync access.
109
+ */
110
+ getStateSync(provider) {
111
+ return this.internalStates.get(provider) ?? null;
112
+ }
113
+ };
114
+
115
+ // src/services/circuit-breaker.ts
116
+ var DEFAULT_CONFIG = {
117
+ failureThreshold: 5,
118
+ resetTimeoutMs: 6e4,
119
+ // 1 minute
120
+ halfOpenMaxRequests: 3
121
+ };
122
+ var CircuitBreaker = class {
123
+ config;
124
+ storage;
125
+ constructor(deps = {}) {
126
+ this.config = { ...DEFAULT_CONFIG, ...deps.config };
127
+ this.storage = deps.storage ?? new InMemoryCircuitBreakerStorage();
128
+ }
129
+ /**
130
+ * Check if a request can be executed for a provider
131
+ */
132
+ async canExecute(provider) {
133
+ const state = await this.getState(provider);
134
+ switch (state.state) {
135
+ case "closed":
136
+ return true;
137
+ case "open":
138
+ if (state.nextRetryAt && Date.now() >= state.nextRetryAt.getTime()) {
139
+ await this.transitionTo(provider, "half_open");
140
+ return true;
141
+ }
142
+ return false;
143
+ case "half_open":
144
+ return state.failureCount < this.config.halfOpenMaxRequests;
145
+ }
146
+ }
147
+ /**
148
+ * Record a successful request
149
+ */
150
+ async recordSuccess(provider) {
151
+ const state = await this.getState(provider);
152
+ if (state.state === "half_open") {
153
+ state.successCount++;
154
+ if (state.successCount >= this.config.halfOpenMaxRequests) {
155
+ await this.transitionTo(provider, "closed");
156
+ return;
157
+ }
158
+ } else if (state.state === "closed") {
159
+ state.failureCount = 0;
160
+ }
161
+ await this.storage.setState(provider, state);
162
+ }
163
+ /**
164
+ * Record a failed request
165
+ */
166
+ async recordFailure(provider) {
167
+ const state = await this.getState(provider);
168
+ state.failureCount++;
169
+ state.lastFailure = /* @__PURE__ */ new Date();
170
+ if (state.state === "half_open") {
171
+ await this.transitionTo(provider, "open");
172
+ } else if (state.state === "closed") {
173
+ if (state.failureCount >= this.config.failureThreshold) {
174
+ await this.transitionTo(provider, "open");
175
+ } else {
176
+ await this.storage.setState(provider, state);
177
+ }
178
+ } else {
179
+ await this.storage.setState(provider, state);
180
+ }
181
+ }
182
+ /**
183
+ * Get current state for a provider
184
+ */
185
+ async getState(provider) {
186
+ const stored = await this.storage.getState(provider);
187
+ return stored ?? createDefaultState(provider);
188
+ }
189
+ /**
190
+ * Check if circuit is open (provider unavailable)
191
+ */
192
+ isOpen(provider) {
193
+ return this.isOpenSync(provider);
194
+ }
195
+ /**
196
+ * Async version of isOpen
197
+ */
198
+ async isOpenAsync(provider) {
199
+ const state = await this.getState(provider);
200
+ return state.state === "open";
201
+ }
202
+ /**
203
+ * Manually reset a provider's circuit
204
+ */
205
+ async reset(provider) {
206
+ await this.storage.setState(provider, createDefaultState(provider));
207
+ }
208
+ /**
209
+ * Get all providers with open circuits
210
+ */
211
+ async getOpenCircuits() {
212
+ return this.storage.getOpenCircuits();
213
+ }
214
+ /**
215
+ * Get the storage instance (useful for testing)
216
+ */
217
+ getStorage() {
218
+ return this.storage;
219
+ }
220
+ /**
221
+ * Get configuration (useful for testing)
222
+ */
223
+ getConfig() {
224
+ return { ...this.config };
225
+ }
226
+ // ==========================================================================
227
+ // Private Methods
228
+ // ==========================================================================
229
+ async transitionTo(provider, newState) {
230
+ const state = await this.getState(provider);
231
+ const now = /* @__PURE__ */ new Date();
232
+ state.state = newState;
233
+ if (newState === "open") {
234
+ state.openedAt = now;
235
+ state.nextRetryAt = new Date(now.getTime() + this.config.resetTimeoutMs);
236
+ console.warn(
237
+ `[CIRCUIT_BREAKER] Circuit OPENED for ${provider}. Retry at: ${state.nextRetryAt.toISOString()}`
238
+ );
239
+ } else if (newState === "half_open") {
240
+ state.failureCount = 0;
241
+ state.successCount = 0;
242
+ console.info(`[CIRCUIT_BREAKER] Circuit HALF_OPEN for ${provider}`);
243
+ } else if (newState === "closed") {
244
+ state.failureCount = 0;
245
+ state.successCount = 0;
246
+ state.openedAt = null;
247
+ state.nextRetryAt = null;
248
+ console.info(`[CIRCUIT_BREAKER] Circuit CLOSED for ${provider}`);
249
+ }
250
+ await this.storage.setState(provider, state);
251
+ }
252
+ /**
253
+ * Synchronous check for backward compatibility
254
+ * Note: This always returns false for database-backed storage
255
+ */
256
+ isOpenSync(provider) {
257
+ if (this.storage instanceof InMemoryCircuitBreakerStorage) {
258
+ const state = this.storage.getStateSync(provider);
259
+ return state?.state === "open";
260
+ }
261
+ return false;
262
+ }
263
+ };
264
+ var circuitBreakerInstance = null;
265
+ var defaultStorage = null;
266
+ function getCircuitBreaker(config) {
267
+ if (!circuitBreakerInstance) {
268
+ if (!defaultStorage) {
269
+ defaultStorage = new InMemoryCircuitBreakerStorage();
270
+ }
271
+ circuitBreakerInstance = new CircuitBreaker({
272
+ storage: defaultStorage,
273
+ config
274
+ });
275
+ }
276
+ return circuitBreakerInstance;
277
+ }
278
+ function createCircuitBreaker(deps = {}) {
279
+ return new CircuitBreaker(deps);
280
+ }
281
+ function resetCircuitBreaker() {
282
+ circuitBreakerInstance = null;
283
+ if (defaultStorage) {
284
+ defaultStorage.clear();
285
+ }
286
+ defaultStorage = null;
287
+ }
288
+
289
+ // src/services/routing-engine.ts
290
+ function matchCardBinToRule(bin, rules) {
291
+ if (!bin || bin.length < 6) return null;
292
+ const binPrefix = bin.substring(0, 6);
293
+ for (const rule of rules) {
294
+ for (const range of rule.ranges) {
295
+ if (binPrefix >= range.start && binPrefix <= range.end) {
296
+ return { rule, issuer: range.issuer, country: range.country };
297
+ }
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+ function getOptimalProviderFromPriorities(matchedBin, currency, requiresRecurring, availableProviders, priorities) {
303
+ const candidates = priorities.filter(
304
+ (p) => availableProviders.includes(p.provider)
305
+ );
306
+ if (candidates.length === 0) return availableProviders[0] ?? null;
307
+ const suitable = candidates.filter((p) => {
308
+ if (!p.supportsCurrency.includes(currency)) return false;
309
+ if (requiresRecurring && !p.supportsRecurring) return false;
310
+ return true;
311
+ });
312
+ if (suitable.length === 0) {
313
+ return candidates.sort((a, b) => a.priority - b.priority)[0]?.provider ?? null;
314
+ }
315
+ if (matchedBin) {
316
+ const localProviders = suitable.filter((p) => p.isLocalGateway);
317
+ if (localProviders.length > 0) {
318
+ return localProviders.sort((a, b) => a.priority - b.priority)[0].provider;
319
+ }
320
+ }
321
+ return suitable.sort((a, b) => a.priority - b.priority)[0].provider;
322
+ }
323
+ function getFallbackProvidersFromPriorities(primaryProvider, availableProviders, priorities) {
324
+ return priorities.filter(
325
+ (p) => p.provider !== primaryProvider && availableProviders.includes(p.provider)
326
+ ).sort((a, b) => a.priority - b.priority).map((p) => p.provider);
327
+ }
328
+ function getProviderFeeFromPriorities(provider, priorities) {
329
+ const config = priorities.find((p) => p.provider === provider);
330
+ return config?.maxFeePercent ?? 3;
331
+ }
332
+ var RoutingEngine = class {
333
+ config;
334
+ circuitBreaker;
335
+ routingRules;
336
+ constructor(deps = {}) {
337
+ this.config = deps.config ?? createPartialConfig({});
338
+ this.circuitBreaker = deps.circuitBreaker ?? getCircuitBreaker();
339
+ this.routingRules = deps.routingRules ?? {};
340
+ }
341
+ /**
342
+ * Determine optimal provider for a transaction
343
+ */
344
+ async route(context) {
345
+ const availability = getConfiguredProviders(this.config);
346
+ const availableProviders = this.getProviderList(availability);
347
+ if (availableProviders.length === 0) {
348
+ throw new Error("No payment providers configured");
349
+ }
350
+ if (context.savedPaymentMethodId && context.savedPaymentMethodProvider) {
351
+ return this.routeToSavedMethodProvider(context, availableProviders);
352
+ }
353
+ const binMatch = context.cardBin && this.routingRules.cardBinRules ? matchCardBinToRule(context.cardBin, this.routingRules.cardBinRules) : null;
354
+ const currencyRule = this.routingRules.currencyRules?.find(
355
+ (r) => r.currency === context.amount.currency
356
+ );
357
+ const healthyProviders = await this.getHealthyProviders(availableProviders);
358
+ const availableHealthy = availableProviders.filter((p) => healthyProviders.includes(p));
359
+ const effectiveProviders = availableHealthy.length > 0 ? availableHealthy : availableProviders;
360
+ if (binMatch && effectiveProviders.includes(binMatch.rule.preferredProvider)) {
361
+ const provider2 = binMatch.rule.preferredProvider;
362
+ const fallbacks2 = this.getFallbackProviders(provider2, availableProviders);
363
+ const feePercent = this.getProviderFee(provider2);
364
+ return {
365
+ provider: provider2,
366
+ reason: binMatch.issuer ? `Card (${binMatch.issuer}) matched BIN rule, routed to preferred provider` : "Card matched BIN rule, routed to preferred provider",
367
+ fallbackProviders: fallbacks2,
368
+ estimatedFeePercent: feePercent,
369
+ metadata: {
370
+ matchedBinRule: true,
371
+ cardIssuer: binMatch.issuer,
372
+ cardCountry: binMatch.country
373
+ }
374
+ };
375
+ }
376
+ if (currencyRule && effectiveProviders.includes(currencyRule.preferredProvider)) {
377
+ const provider2 = currencyRule.preferredProvider;
378
+ const fallbacks2 = this.getFallbackProviders(provider2, availableProviders);
379
+ const feePercent = this.getProviderFee(provider2);
380
+ return {
381
+ provider: provider2,
382
+ reason: `Currency ${context.amount.currency} routed to preferred provider`,
383
+ fallbackProviders: fallbacks2,
384
+ estimatedFeePercent: feePercent
385
+ };
386
+ }
387
+ const priorities = this.routingRules.providerPriorities;
388
+ if (priorities && priorities.length > 0) {
389
+ const provider2 = getOptimalProviderFromPriorities(
390
+ !!binMatch,
391
+ context.amount.currency,
392
+ context.isRecurring,
393
+ effectiveProviders,
394
+ priorities
395
+ );
396
+ if (provider2) {
397
+ const fallbacks2 = getFallbackProvidersFromPriorities(provider2, availableProviders, priorities);
398
+ const feePercent = getProviderFeeFromPriorities(provider2, priorities);
399
+ return {
400
+ provider: provider2,
401
+ reason: `Selected ${provider2} based on priority rules`,
402
+ fallbackProviders: fallbacks2,
403
+ estimatedFeePercent: feePercent,
404
+ metadata: {
405
+ matchedBinRule: !!binMatch,
406
+ cardIssuer: binMatch?.issuer,
407
+ cardCountry: binMatch?.country
408
+ }
409
+ };
410
+ }
411
+ }
412
+ const provider = effectiveProviders[0];
413
+ const fallbacks = effectiveProviders.slice(1);
414
+ return {
415
+ provider,
416
+ reason: `Default routing to ${provider}`,
417
+ fallbackProviders: fallbacks,
418
+ estimatedFeePercent: this.getProviderFee(provider)
419
+ };
420
+ }
421
+ /**
422
+ * Get next provider after a failure
423
+ */
424
+ async getFailoverProvider(failedProvider, _context) {
425
+ const availability = getConfiguredProviders(this.config);
426
+ const availableProviders = this.getProviderList(availability);
427
+ const healthyProviders = await this.getHealthyProviders(availableProviders);
428
+ const fallbacks = this.getFallbackProviders(failedProvider, availableProviders);
429
+ for (const provider of fallbacks) {
430
+ if (healthyProviders.includes(provider)) {
431
+ return provider;
432
+ }
433
+ }
434
+ return fallbacks[0] ?? null;
435
+ }
436
+ /**
437
+ * Check if a card BIN matches any configured rule
438
+ */
439
+ matchCardBin(bin) {
440
+ if (!this.routingRules.cardBinRules) return false;
441
+ return matchCardBinToRule(bin, this.routingRules.cardBinRules) !== null;
442
+ }
443
+ /**
444
+ * Get all available (healthy) providers
445
+ */
446
+ async getAvailableProviders() {
447
+ const availability = getConfiguredProviders(this.config);
448
+ const configured = this.getProviderList(availability);
449
+ return this.getHealthyProviders(configured);
450
+ }
451
+ /**
452
+ * Quick recommendation without full context
453
+ */
454
+ async getQuickRecommendation(currency, isRecurring) {
455
+ const availability = getConfiguredProviders(this.config);
456
+ const available = this.getProviderList(availability);
457
+ if (available.length === 0) return null;
458
+ const currencyRule = this.routingRules.currencyRules?.find(
459
+ (r) => r.currency === currency
460
+ );
461
+ if (currencyRule && available.includes(currencyRule.preferredProvider)) {
462
+ return currencyRule.preferredProvider;
463
+ }
464
+ const priorities = this.routingRules.providerPriorities;
465
+ if (priorities && priorities.length > 0) {
466
+ return getOptimalProviderFromPriorities(false, currency, isRecurring, available, priorities);
467
+ }
468
+ return available[0] ?? null;
469
+ }
470
+ /**
471
+ * Get current configuration (for testing/debugging)
472
+ */
473
+ getConfig() {
474
+ return this.config;
475
+ }
476
+ /**
477
+ * Get circuit breaker instance (for testing/debugging)
478
+ */
479
+ getCircuitBreaker() {
480
+ return this.circuitBreaker;
481
+ }
482
+ /**
483
+ * Get routing rules (for testing/debugging)
484
+ */
485
+ getRoutingRules() {
486
+ return this.routingRules;
487
+ }
488
+ // ==========================================================================
489
+ // Private Methods
490
+ // ==========================================================================
491
+ getProviderList(availability) {
492
+ const providers = [];
493
+ for (const [name, isAvailable] of Object.entries(availability)) {
494
+ if (isAvailable) providers.push(name);
495
+ }
496
+ return providers;
497
+ }
498
+ async getHealthyProviders(providers) {
499
+ const healthy = [];
500
+ for (const provider of providers) {
501
+ const isOpen = await this.circuitBreaker.isOpenAsync(provider);
502
+ if (!isOpen) {
503
+ healthy.push(provider);
504
+ }
505
+ }
506
+ return healthy;
507
+ }
508
+ routeToSavedMethodProvider(context, availableProviders) {
509
+ const provider = context.savedPaymentMethodProvider;
510
+ if (!availableProviders.includes(provider)) {
511
+ throw new Error(`Saved payment method provider '${provider}' is not available`);
512
+ }
513
+ return {
514
+ provider,
515
+ reason: "Using saved payment method provider",
516
+ fallbackProviders: [],
517
+ estimatedFeePercent: this.getProviderFee(provider)
518
+ };
519
+ }
520
+ getFallbackProviders(primaryProvider, availableProviders) {
521
+ const priorities = this.routingRules.providerPriorities;
522
+ if (priorities && priorities.length > 0) {
523
+ return getFallbackProvidersFromPriorities(primaryProvider, availableProviders, priorities);
524
+ }
525
+ return availableProviders.filter((p) => p !== primaryProvider);
526
+ }
527
+ getProviderFee(provider) {
528
+ const priorities = this.routingRules.providerPriorities;
529
+ if (priorities && priorities.length > 0) {
530
+ return getProviderFeeFromPriorities(provider, priorities);
531
+ }
532
+ return 3;
533
+ }
534
+ };
535
+ var routingEngineInstance = null;
536
+ function getRoutingEngine() {
537
+ if (!routingEngineInstance) {
538
+ routingEngineInstance = new RoutingEngine();
539
+ }
540
+ return routingEngineInstance;
541
+ }
542
+ function createRoutingEngine(deps = {}) {
543
+ return new RoutingEngine(deps);
544
+ }
545
+ function resetRoutingEngine() {
546
+ routingEngineInstance = null;
547
+ }
548
+
549
+ // src/services/payment-orchestrator.ts
550
+ var PaymentOrchestrator = class {
551
+ providers;
552
+ routingEngine;
553
+ circuitBreaker;
554
+ constructor(deps) {
555
+ this.providers = deps.providers;
556
+ this.routingEngine = deps.routingEngine ?? getRoutingEngine();
557
+ this.circuitBreaker = deps.circuitBreaker ?? getCircuitBreaker();
558
+ }
559
+ /**
560
+ * Initiate a new payment
561
+ */
562
+ async initiatePayment(params) {
563
+ const internalPaymentId = `pay_${(0, import_crypto.randomUUID)()}`;
564
+ const idempotencyKey = `idem_${(0, import_crypto.randomUUID)()}`;
565
+ try {
566
+ const routing = await this.routingEngine.route({
567
+ userId: params.userId,
568
+ amount: params.amount,
569
+ cardBin: params.cardBin,
570
+ preferredProvider: params.preferredProvider,
571
+ isRecurring: params.transactionType !== "one_time_purchase"
572
+ });
573
+ const provider = this.getProvider(routing.provider);
574
+ if (!provider) {
575
+ return this.tryFailover(params, routing, internalPaymentId);
576
+ }
577
+ if (!await this.circuitBreaker.canExecute(routing.provider)) {
578
+ return this.tryFailover(params, routing, internalPaymentId);
579
+ }
580
+ const result = await provider.createPaymentIntent({
581
+ amount: params.amount,
582
+ userId: params.userId,
583
+ idempotencyKey,
584
+ description: params.description,
585
+ metadata: params.metadata,
586
+ returnUrl: params.returnUrl,
587
+ captureMethod: params.autoCapture ? "automatic" : "manual"
588
+ });
589
+ if (!result.success) {
590
+ await this.circuitBreaker.recordFailure(routing.provider);
591
+ return this.tryFailover(params, routing, internalPaymentId);
592
+ }
593
+ await this.circuitBreaker.recordSuccess(routing.provider);
594
+ return {
595
+ success: true,
596
+ transactionId: result.providerIntentId,
597
+ internalPaymentId,
598
+ provider: routing.provider,
599
+ clientSecret: result.clientSecret,
600
+ redirectUrl: result.redirectUrl
601
+ };
602
+ } catch (error) {
603
+ return {
604
+ success: false,
605
+ transactionId: "",
606
+ internalPaymentId,
607
+ provider: "unknown",
608
+ error: error instanceof Error ? error.message : "Unknown error"
609
+ };
610
+ }
611
+ }
612
+ /**
613
+ * Confirm a payment after user authorization
614
+ */
615
+ async confirmPayment(params) {
616
+ const provider = this.getProvider(params.provider);
617
+ if (!provider) {
618
+ return {
619
+ success: false,
620
+ transactionId: params.transactionId,
621
+ status: "failed",
622
+ error: `Provider ${params.provider} not available`
623
+ };
624
+ }
625
+ const result = await provider.authorize({
626
+ providerIntentId: params.providerIntentId,
627
+ idempotencyKey: `auth_${params.internalPaymentId}`
628
+ });
629
+ if (!result.success) {
630
+ return {
631
+ success: false,
632
+ transactionId: params.transactionId,
633
+ status: "failed",
634
+ error: result.error
635
+ };
636
+ }
637
+ return {
638
+ success: true,
639
+ transactionId: params.transactionId,
640
+ status: result.status ?? "authorized"
641
+ };
642
+ }
643
+ /**
644
+ * Capture an authorized payment (J5 completion)
645
+ */
646
+ async capturePayment(params) {
647
+ const provider = this.getProvider(params.provider);
648
+ if (!provider) {
649
+ return {
650
+ success: false,
651
+ status: "failed",
652
+ error: `Provider ${params.provider} not available`
653
+ };
654
+ }
655
+ const result = await provider.capture({
656
+ providerIntentId: params.providerIntentId,
657
+ authorizationCode: params.providerIntentId,
658
+ amount: params.amount,
659
+ idempotencyKey: `cap_${params.transactionId}`
660
+ });
661
+ if (!result.success) {
662
+ return {
663
+ success: false,
664
+ status: "failed",
665
+ error: result.error
666
+ };
667
+ }
668
+ return {
669
+ success: true,
670
+ status: "captured",
671
+ capturedAmount: result.capturedAmount
672
+ };
673
+ }
674
+ /**
675
+ * Get the routing engine (for testing/debugging)
676
+ */
677
+ getRoutingEngine() {
678
+ return this.routingEngine;
679
+ }
680
+ /**
681
+ * Get the circuit breaker (for testing/debugging)
682
+ */
683
+ getCircuitBreaker() {
684
+ return this.circuitBreaker;
685
+ }
686
+ /**
687
+ * Get available providers (for testing/debugging)
688
+ */
689
+ getProviders() {
690
+ return new Map(this.providers);
691
+ }
692
+ // ==========================================================================
693
+ // Private Methods
694
+ // ==========================================================================
695
+ getProvider(name) {
696
+ return this.providers.get(name);
697
+ }
698
+ async tryFailover(params, routing, internalPaymentId) {
699
+ for (const fallback of routing.fallbackProviders) {
700
+ const provider = this.getProvider(fallback);
701
+ if (!provider) continue;
702
+ if (!await this.circuitBreaker.canExecute(fallback)) continue;
703
+ const result = await provider.createPaymentIntent({
704
+ amount: params.amount,
705
+ userId: params.userId,
706
+ idempotencyKey: `idem_${(0, import_crypto.randomUUID)()}`,
707
+ description: params.description,
708
+ metadata: params.metadata,
709
+ returnUrl: params.returnUrl,
710
+ captureMethod: params.autoCapture ? "automatic" : "manual"
711
+ });
712
+ if (result.success) {
713
+ await this.circuitBreaker.recordSuccess(fallback);
714
+ return {
715
+ success: true,
716
+ transactionId: result.providerIntentId,
717
+ internalPaymentId,
718
+ provider: fallback,
719
+ clientSecret: result.clientSecret
720
+ };
721
+ }
722
+ await this.circuitBreaker.recordFailure(fallback);
723
+ }
724
+ return {
725
+ success: false,
726
+ transactionId: "",
727
+ internalPaymentId,
728
+ provider: routing.provider,
729
+ error: "All payment providers failed"
730
+ };
731
+ }
732
+ };
733
+
734
+ // src/factory.ts
735
+ function createPaymentServices(options) {
736
+ const config = options.config ?? createPartialConfig({});
737
+ const providers = options.providers;
738
+ const webhookHandlers = options.webhookHandlers ?? /* @__PURE__ */ new Map();
739
+ if (providers.size === 0) {
740
+ throw new Error("No payment providers provided. Pass at least one provider instance.");
741
+ }
742
+ const circuitBreakerStorage = options.circuitBreakerStorage ?? new InMemoryCircuitBreakerStorage();
743
+ const circuitBreaker = createCircuitBreaker({
744
+ storage: circuitBreakerStorage,
745
+ config: options.circuitBreaker
746
+ });
747
+ const routingEngine = createRoutingEngine({
748
+ config,
749
+ circuitBreaker,
750
+ routingRules: options.routingRules
751
+ });
752
+ const orchestrator = new PaymentOrchestrator({
753
+ providers,
754
+ routingEngine,
755
+ circuitBreaker
756
+ });
757
+ return {
758
+ orchestrator,
759
+ routingEngine,
760
+ circuitBreaker,
761
+ providers,
762
+ webhookHandlers,
763
+ config
764
+ };
765
+ }
766
+ function registerProvider(services, name, provider, webhookHandler) {
767
+ const newProviders = new Map(services.providers);
768
+ newProviders.set(name, provider);
769
+ const newWebhookHandlers = new Map(services.webhookHandlers);
770
+ if (webhookHandler) {
771
+ newWebhookHandlers.set(name, webhookHandler);
772
+ }
773
+ const orchestrator = new PaymentOrchestrator({
774
+ providers: newProviders,
775
+ routingEngine: services.routingEngine,
776
+ circuitBreaker: services.circuitBreaker
777
+ });
778
+ return {
779
+ ...services,
780
+ orchestrator,
781
+ providers: newProviders,
782
+ webhookHandlers: newWebhookHandlers
783
+ };
784
+ }
785
+ var servicesInstance = null;
786
+ function getPaymentServices(config) {
787
+ if (!servicesInstance) {
788
+ if (!config) {
789
+ throw new Error("PaymentServices not initialized. Call with config on first use.");
790
+ }
791
+ servicesInstance = createPaymentServices(config);
792
+ }
793
+ return servicesInstance;
794
+ }
795
+ function resetPaymentServices() {
796
+ servicesInstance = null;
797
+ resetCircuitBreaker();
798
+ resetRoutingEngine();
799
+ }
800
+ // Annotate the CommonJS export names for ESM import in node:
801
+ 0 && (module.exports = {
802
+ createPaymentServices,
803
+ getPaymentServices,
804
+ registerProvider,
805
+ resetPaymentServices
806
+ });
807
+ //# sourceMappingURL=factory.cjs.map