@classytic/revenue 0.2.4 → 1.0.1

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 (111) hide show
  1. package/README.md +498 -501
  2. package/dist/actions-CwG-b7fR.d.ts +519 -0
  3. package/dist/core/index.d.ts +884 -0
  4. package/dist/core/index.js +2941 -0
  5. package/dist/core/index.js.map +1 -0
  6. package/dist/enums/index.d.ts +130 -0
  7. package/dist/enums/index.js +167 -0
  8. package/dist/enums/index.js.map +1 -0
  9. package/dist/index-BnJWVXuw.d.ts +378 -0
  10. package/dist/index-ChVD3P9k.d.ts +504 -0
  11. package/dist/index.d.ts +42 -0
  12. package/dist/index.js +4362 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/providers/index.d.ts +132 -0
  15. package/dist/providers/index.js +122 -0
  16. package/dist/providers/index.js.map +1 -0
  17. package/dist/retry-80lBCmSe.d.ts +234 -0
  18. package/dist/schemas/index.d.ts +906 -0
  19. package/dist/schemas/index.js +533 -0
  20. package/dist/schemas/index.js.map +1 -0
  21. package/dist/schemas/validation.d.ts +309 -0
  22. package/dist/schemas/validation.js +249 -0
  23. package/dist/schemas/validation.js.map +1 -0
  24. package/dist/services/index.d.ts +3 -0
  25. package/dist/services/index.js +1632 -0
  26. package/dist/services/index.js.map +1 -0
  27. package/dist/split.enums-DHdM1YAV.d.ts +255 -0
  28. package/dist/split.schema-CETjPq10.d.ts +976 -0
  29. package/dist/utils/index.d.ts +24 -0
  30. package/dist/utils/index.js +1067 -0
  31. package/dist/utils/index.js.map +1 -0
  32. package/package.json +48 -32
  33. package/core/builder.js +0 -219
  34. package/core/container.js +0 -119
  35. package/core/errors.js +0 -262
  36. package/dist/types/core/builder.d.ts +0 -97
  37. package/dist/types/core/container.d.ts +0 -57
  38. package/dist/types/core/errors.d.ts +0 -122
  39. package/dist/types/enums/escrow.enums.d.ts +0 -24
  40. package/dist/types/enums/index.d.ts +0 -69
  41. package/dist/types/enums/monetization.enums.d.ts +0 -6
  42. package/dist/types/enums/payment.enums.d.ts +0 -16
  43. package/dist/types/enums/split.enums.d.ts +0 -25
  44. package/dist/types/enums/subscription.enums.d.ts +0 -15
  45. package/dist/types/enums/transaction.enums.d.ts +0 -24
  46. package/dist/types/index.d.ts +0 -22
  47. package/dist/types/providers/base.d.ts +0 -128
  48. package/dist/types/schemas/escrow/hold.schema.d.ts +0 -54
  49. package/dist/types/schemas/escrow/index.d.ts +0 -6
  50. package/dist/types/schemas/index.d.ts +0 -506
  51. package/dist/types/schemas/split/index.d.ts +0 -8
  52. package/dist/types/schemas/split/split.schema.d.ts +0 -142
  53. package/dist/types/schemas/subscription/index.d.ts +0 -152
  54. package/dist/types/schemas/subscription/info.schema.d.ts +0 -128
  55. package/dist/types/schemas/subscription/plan.schema.d.ts +0 -39
  56. package/dist/types/schemas/transaction/common.schema.d.ts +0 -12
  57. package/dist/types/schemas/transaction/gateway.schema.d.ts +0 -86
  58. package/dist/types/schemas/transaction/index.d.ts +0 -202
  59. package/dist/types/schemas/transaction/payment.schema.d.ts +0 -145
  60. package/dist/types/services/escrow.service.d.ts +0 -51
  61. package/dist/types/services/monetization.service.d.ts +0 -193
  62. package/dist/types/services/payment.service.d.ts +0 -117
  63. package/dist/types/services/transaction.service.d.ts +0 -40
  64. package/dist/types/utils/category-resolver.d.ts +0 -46
  65. package/dist/types/utils/commission-split.d.ts +0 -56
  66. package/dist/types/utils/commission.d.ts +0 -29
  67. package/dist/types/utils/hooks.d.ts +0 -17
  68. package/dist/types/utils/index.d.ts +0 -6
  69. package/dist/types/utils/logger.d.ts +0 -12
  70. package/dist/types/utils/subscription/actions.d.ts +0 -28
  71. package/dist/types/utils/subscription/index.d.ts +0 -2
  72. package/dist/types/utils/subscription/period.d.ts +0 -47
  73. package/dist/types/utils/transaction-type.d.ts +0 -102
  74. package/enums/escrow.enums.js +0 -36
  75. package/enums/index.d.ts +0 -116
  76. package/enums/index.js +0 -110
  77. package/enums/monetization.enums.js +0 -15
  78. package/enums/payment.enums.js +0 -64
  79. package/enums/split.enums.js +0 -37
  80. package/enums/subscription.enums.js +0 -33
  81. package/enums/transaction.enums.js +0 -69
  82. package/index.js +0 -76
  83. package/providers/base.js +0 -162
  84. package/schemas/escrow/hold.schema.js +0 -62
  85. package/schemas/escrow/index.js +0 -15
  86. package/schemas/index.d.ts +0 -33
  87. package/schemas/index.js +0 -27
  88. package/schemas/split/index.js +0 -16
  89. package/schemas/split/split.schema.js +0 -86
  90. package/schemas/subscription/index.js +0 -17
  91. package/schemas/subscription/info.schema.js +0 -115
  92. package/schemas/subscription/plan.schema.js +0 -48
  93. package/schemas/transaction/common.schema.js +0 -22
  94. package/schemas/transaction/gateway.schema.js +0 -69
  95. package/schemas/transaction/index.js +0 -20
  96. package/schemas/transaction/payment.schema.js +0 -110
  97. package/services/escrow.service.js +0 -353
  98. package/services/monetization.service.js +0 -675
  99. package/services/payment.service.js +0 -535
  100. package/services/transaction.service.js +0 -142
  101. package/utils/category-resolver.js +0 -74
  102. package/utils/commission-split.js +0 -180
  103. package/utils/commission.js +0 -83
  104. package/utils/hooks.js +0 -44
  105. package/utils/index.d.ts +0 -164
  106. package/utils/index.js +0 -16
  107. package/utils/logger.js +0 -36
  108. package/utils/subscription/actions.js +0 -68
  109. package/utils/subscription/index.js +0 -20
  110. package/utils/subscription/period.js +0 -123
  111. package/utils/transaction-type.js +0 -254
package/dist/index.js ADDED
@@ -0,0 +1,4362 @@
1
+ import { nanoid } from 'nanoid';
2
+ import * as z from 'zod';
3
+ export { z };
4
+ import { Schema } from 'mongoose';
5
+
6
+ // @classytic/revenue - Enterprise Revenue Management System
7
+
8
+
9
+ // src/core/container.ts
10
+ var Container = class _Container {
11
+ _services;
12
+ _singletons;
13
+ constructor() {
14
+ this._services = /* @__PURE__ */ new Map();
15
+ this._singletons = /* @__PURE__ */ new Map();
16
+ }
17
+ /**
18
+ * Register a service
19
+ * @param name - Service name
20
+ * @param implementation - Service implementation or factory
21
+ * @param options - Registration options
22
+ */
23
+ register(name, implementation, options = {}) {
24
+ this._services.set(name, {
25
+ implementation,
26
+ singleton: options.singleton !== false,
27
+ // Default to singleton
28
+ factory: options.factory ?? false
29
+ });
30
+ return this;
31
+ }
32
+ /**
33
+ * Register a singleton service
34
+ * @param name - Service name
35
+ * @param implementation - Service implementation
36
+ */
37
+ singleton(name, implementation) {
38
+ return this.register(name, implementation, { singleton: true });
39
+ }
40
+ /**
41
+ * Register a transient service (new instance each time)
42
+ * @param name - Service name
43
+ * @param factory - Factory function
44
+ */
45
+ transient(name, factory) {
46
+ return this.register(name, factory, { singleton: false, factory: true });
47
+ }
48
+ /**
49
+ * Get a service from the container
50
+ * @param name - Service name
51
+ * @returns Service instance
52
+ */
53
+ get(name) {
54
+ if (this._singletons.has(name)) {
55
+ return this._singletons.get(name);
56
+ }
57
+ const service = this._services.get(name);
58
+ if (!service) {
59
+ throw new Error(`Service "${name}" not registered in container`);
60
+ }
61
+ if (service.factory) {
62
+ const factory = service.implementation;
63
+ const instance2 = factory(this);
64
+ if (service.singleton) {
65
+ this._singletons.set(name, instance2);
66
+ }
67
+ return instance2;
68
+ }
69
+ const instance = service.implementation;
70
+ if (service.singleton) {
71
+ this._singletons.set(name, instance);
72
+ }
73
+ return instance;
74
+ }
75
+ /**
76
+ * Check if service is registered
77
+ * @param name - Service name
78
+ */
79
+ has(name) {
80
+ return this._services.has(name);
81
+ }
82
+ /**
83
+ * Get all registered service names
84
+ */
85
+ keys() {
86
+ return Array.from(this._services.keys());
87
+ }
88
+ /**
89
+ * Clear all services (useful for testing)
90
+ */
91
+ clear() {
92
+ this._services.clear();
93
+ this._singletons.clear();
94
+ }
95
+ /**
96
+ * Create a child container (for scoped dependencies)
97
+ */
98
+ createScope() {
99
+ const scope = new _Container();
100
+ this._services.forEach((value, key) => {
101
+ scope._services.set(key, value);
102
+ });
103
+ return scope;
104
+ }
105
+ };
106
+
107
+ // src/core/events.ts
108
+ var EventBus = class {
109
+ handlers = /* @__PURE__ */ new Map();
110
+ onceHandlers = /* @__PURE__ */ new Map();
111
+ /**
112
+ * Subscribe to an event
113
+ */
114
+ on(event, handler) {
115
+ if (!this.handlers.has(event)) {
116
+ this.handlers.set(event, /* @__PURE__ */ new Set());
117
+ }
118
+ this.handlers.get(event).add(handler);
119
+ return () => this.off(event, handler);
120
+ }
121
+ /**
122
+ * Subscribe to an event once
123
+ */
124
+ once(event, handler) {
125
+ if (!this.onceHandlers.has(event)) {
126
+ this.onceHandlers.set(event, /* @__PURE__ */ new Set());
127
+ }
128
+ this.onceHandlers.get(event).add(handler);
129
+ return () => this.onceHandlers.get(event)?.delete(handler);
130
+ }
131
+ /**
132
+ * Unsubscribe from an event
133
+ */
134
+ off(event, handler) {
135
+ this.handlers.get(event)?.delete(handler);
136
+ this.onceHandlers.get(event)?.delete(handler);
137
+ }
138
+ /**
139
+ * Emit an event (fire and forget, non-blocking)
140
+ */
141
+ emit(event, payload) {
142
+ const fullPayload = {
143
+ ...payload,
144
+ timestamp: /* @__PURE__ */ new Date()
145
+ };
146
+ const handlers = this.handlers.get(event);
147
+ if (handlers) {
148
+ for (const handler of handlers) {
149
+ Promise.resolve(handler(fullPayload)).catch((err2) => {
150
+ console.error(`[Revenue] Event handler error for "${event}":`, err2);
151
+ });
152
+ }
153
+ }
154
+ const onceHandlers = this.onceHandlers.get(event);
155
+ if (onceHandlers) {
156
+ for (const handler of onceHandlers) {
157
+ Promise.resolve(handler(fullPayload)).catch((err2) => {
158
+ console.error(`[Revenue] Once handler error for "${event}":`, err2);
159
+ });
160
+ }
161
+ this.onceHandlers.delete(event);
162
+ }
163
+ if (event !== "*") {
164
+ const wildcardHandlers = this.handlers.get("*");
165
+ if (wildcardHandlers) {
166
+ for (const handler of wildcardHandlers) {
167
+ Promise.resolve(handler(fullPayload)).catch((err2) => {
168
+ console.error(`[Revenue] Wildcard handler error:`, err2);
169
+ });
170
+ }
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * Emit and wait for all handlers to complete
176
+ */
177
+ async emitAsync(event, payload) {
178
+ const fullPayload = {
179
+ ...payload,
180
+ timestamp: /* @__PURE__ */ new Date()
181
+ };
182
+ const promises = [];
183
+ const handlers = this.handlers.get(event);
184
+ if (handlers) {
185
+ for (const handler of handlers) {
186
+ promises.push(Promise.resolve(handler(fullPayload)));
187
+ }
188
+ }
189
+ const onceHandlers = this.onceHandlers.get(event);
190
+ if (onceHandlers) {
191
+ for (const handler of onceHandlers) {
192
+ promises.push(Promise.resolve(handler(fullPayload)));
193
+ }
194
+ this.onceHandlers.delete(event);
195
+ }
196
+ if (event !== "*") {
197
+ const wildcardHandlers = this.handlers.get("*");
198
+ if (wildcardHandlers) {
199
+ for (const handler of wildcardHandlers) {
200
+ promises.push(Promise.resolve(handler(fullPayload)));
201
+ }
202
+ }
203
+ }
204
+ await Promise.all(promises);
205
+ }
206
+ /**
207
+ * Remove all handlers
208
+ */
209
+ clear() {
210
+ this.handlers.clear();
211
+ this.onceHandlers.clear();
212
+ }
213
+ /**
214
+ * Get handler count for an event
215
+ */
216
+ listenerCount(event) {
217
+ return (this.handlers.get(event)?.size ?? 0) + (this.onceHandlers.get(event)?.size ?? 0);
218
+ }
219
+ };
220
+ function createEventBus() {
221
+ return new EventBus();
222
+ }
223
+
224
+ // src/core/plugin.ts
225
+ var PluginManager = class {
226
+ plugins = /* @__PURE__ */ new Map();
227
+ hooks = /* @__PURE__ */ new Map();
228
+ initialized = false;
229
+ /**
230
+ * Register a plugin
231
+ */
232
+ register(plugin) {
233
+ if (this.plugins.has(plugin.name)) {
234
+ throw new Error(`Plugin "${plugin.name}" is already registered`);
235
+ }
236
+ if (plugin.dependencies) {
237
+ for (const dep of plugin.dependencies) {
238
+ if (!this.plugins.has(dep)) {
239
+ throw new Error(
240
+ `Plugin "${plugin.name}" requires "${dep}" to be registered first`
241
+ );
242
+ }
243
+ }
244
+ }
245
+ this.plugins.set(plugin.name, plugin);
246
+ if (plugin.hooks) {
247
+ for (const [hookName, hookFn] of Object.entries(plugin.hooks)) {
248
+ if (!this.hooks.has(hookName)) {
249
+ this.hooks.set(hookName, []);
250
+ }
251
+ this.hooks.get(hookName).push(hookFn);
252
+ }
253
+ }
254
+ return this;
255
+ }
256
+ /**
257
+ * Initialize all plugins
258
+ */
259
+ async init(ctx) {
260
+ if (this.initialized) return;
261
+ for (const plugin of this.plugins.values()) {
262
+ if (plugin.init) {
263
+ await plugin.init(ctx);
264
+ }
265
+ if (plugin.events) {
266
+ for (const [event, handler] of Object.entries(plugin.events)) {
267
+ ctx.events.on(event, handler);
268
+ }
269
+ }
270
+ }
271
+ this.initialized = true;
272
+ }
273
+ /**
274
+ * Execute a hook chain
275
+ */
276
+ async executeHook(hookName, ctx, input, execute) {
277
+ const hooks = this.hooks.get(hookName) ?? [];
278
+ if (hooks.length === 0) {
279
+ return execute();
280
+ }
281
+ let index = 0;
282
+ const next = async () => {
283
+ if (index >= hooks.length) {
284
+ return execute();
285
+ }
286
+ const hook = hooks[index++];
287
+ return hook(ctx, input, next);
288
+ };
289
+ return next();
290
+ }
291
+ /**
292
+ * Check if plugin is registered
293
+ */
294
+ has(name) {
295
+ return this.plugins.has(name);
296
+ }
297
+ /**
298
+ * Get a plugin by name
299
+ */
300
+ get(name) {
301
+ return this.plugins.get(name);
302
+ }
303
+ /**
304
+ * Get all registered plugins
305
+ */
306
+ list() {
307
+ return Array.from(this.plugins.values());
308
+ }
309
+ /**
310
+ * Destroy all plugins
311
+ */
312
+ async destroy() {
313
+ for (const plugin of this.plugins.values()) {
314
+ if (plugin.destroy) {
315
+ await plugin.destroy();
316
+ }
317
+ }
318
+ this.plugins.clear();
319
+ this.hooks.clear();
320
+ this.initialized = false;
321
+ }
322
+ };
323
+ function loggingPlugin(options = {}) {
324
+ const level = options.level ?? "info";
325
+ return {
326
+ name: "logging",
327
+ version: "1.0.0",
328
+ description: "Logs all revenue operations",
329
+ hooks: {
330
+ "payment.create.before": async (ctx, input, next) => {
331
+ ctx.logger[level]("Creating payment", { amount: input.amount, currency: input.currency });
332
+ const result = await next();
333
+ ctx.logger[level]("Payment created", { transactionId: result?.transactionId });
334
+ return result;
335
+ },
336
+ "payment.verify.before": async (ctx, input, next) => {
337
+ ctx.logger[level]("Verifying payment", { id: input.id });
338
+ const result = await next();
339
+ ctx.logger[level]("Payment verified", { verified: result?.verified });
340
+ return result;
341
+ },
342
+ "payment.refund.before": async (ctx, input, next) => {
343
+ ctx.logger[level]("Processing refund", { transactionId: input.transactionId, amount: input.amount });
344
+ const result = await next();
345
+ ctx.logger[level]("Refund processed", { refundId: result?.refundId });
346
+ return result;
347
+ }
348
+ }
349
+ };
350
+ }
351
+ function auditPlugin(options = {}) {
352
+ const store = options.store ?? (async (entry) => {
353
+ });
354
+ return {
355
+ name: "audit",
356
+ version: "1.0.0",
357
+ description: "Audit trail for all operations",
358
+ hooks: {
359
+ "payment.create.after": async (ctx, input, next) => {
360
+ const result = await next();
361
+ await store({
362
+ action: "payment.create",
363
+ requestId: ctx.meta.requestId,
364
+ timestamp: ctx.meta.timestamp,
365
+ input: sanitizeInput(input),
366
+ output: sanitizeOutput(result),
367
+ idempotencyKey: ctx.meta.idempotencyKey
368
+ });
369
+ return result;
370
+ },
371
+ "payment.refund.after": async (ctx, input, next) => {
372
+ const result = await next();
373
+ await store({
374
+ action: "payment.refund",
375
+ requestId: ctx.meta.requestId,
376
+ timestamp: ctx.meta.timestamp,
377
+ input: sanitizeInput(input),
378
+ output: sanitizeOutput(result),
379
+ idempotencyKey: ctx.meta.idempotencyKey
380
+ });
381
+ return result;
382
+ }
383
+ }
384
+ };
385
+ }
386
+ function sanitizeInput(input) {
387
+ if (typeof input !== "object" || !input) return {};
388
+ const sanitized = { ...input };
389
+ delete sanitized.apiKey;
390
+ delete sanitized.secretKey;
391
+ delete sanitized.password;
392
+ return sanitized;
393
+ }
394
+ function sanitizeOutput(output) {
395
+ if (typeof output !== "object" || !output) return {};
396
+ return { ...output };
397
+ }
398
+ function metricsPlugin(options = {}) {
399
+ const record2 = options.onMetric ?? ((metric) => {
400
+ });
401
+ return {
402
+ name: "metrics",
403
+ version: "1.0.0",
404
+ description: "Collects operation metrics",
405
+ hooks: {
406
+ "payment.create.before": async (_ctx, input, next) => {
407
+ const start = Date.now();
408
+ try {
409
+ const result = await next();
410
+ record2({
411
+ name: "payment.create",
412
+ duration: Date.now() - start,
413
+ success: true,
414
+ amount: input.amount,
415
+ currency: input.currency
416
+ });
417
+ return result;
418
+ } catch (error) {
419
+ record2({
420
+ name: "payment.create",
421
+ duration: Date.now() - start,
422
+ success: false,
423
+ error: error.message
424
+ });
425
+ throw error;
426
+ }
427
+ }
428
+ }
429
+ };
430
+ }
431
+ function definePlugin(plugin) {
432
+ return plugin;
433
+ }
434
+
435
+ // src/core/result.ts
436
+ function ok(value) {
437
+ return { ok: true, value };
438
+ }
439
+ function err(error) {
440
+ return { ok: false, error };
441
+ }
442
+ function isOk(result) {
443
+ return result.ok === true;
444
+ }
445
+ function isErr(result) {
446
+ return result.ok === false;
447
+ }
448
+ function unwrap(result) {
449
+ if (result.ok) return result.value;
450
+ throw result.error;
451
+ }
452
+ function unwrapOr(result, defaultValue) {
453
+ return result.ok ? result.value : defaultValue;
454
+ }
455
+ function map(result, fn) {
456
+ return result.ok ? ok(fn(result.value)) : result;
457
+ }
458
+ function mapErr(result, fn) {
459
+ return result.ok ? result : err(fn(result.error));
460
+ }
461
+ function flatMap(result, fn) {
462
+ return result.ok ? fn(result.value) : result;
463
+ }
464
+ async function tryCatch(fn, mapError) {
465
+ try {
466
+ const value = await fn();
467
+ return ok(value);
468
+ } catch (e) {
469
+ const error = mapError ? mapError(e) : e;
470
+ return err(error);
471
+ }
472
+ }
473
+ function tryCatchSync(fn, mapError) {
474
+ try {
475
+ const value = fn();
476
+ return ok(value);
477
+ } catch (e) {
478
+ const error = mapError ? mapError(e) : e;
479
+ return err(error);
480
+ }
481
+ }
482
+ function all(results) {
483
+ const values = [];
484
+ for (const result of results) {
485
+ if (!result.ok) return result;
486
+ values.push(result.value);
487
+ }
488
+ return ok(values);
489
+ }
490
+ function match(result, handlers) {
491
+ return result.ok ? handlers.ok(result.value) : handlers.err(result.error);
492
+ }
493
+ var Result = {
494
+ ok,
495
+ err,
496
+ isOk,
497
+ isErr,
498
+ unwrap,
499
+ unwrapOr,
500
+ map,
501
+ mapErr,
502
+ flatMap,
503
+ tryCatch,
504
+ tryCatchSync,
505
+ all,
506
+ match
507
+ };
508
+ var MemoryIdempotencyStore = class {
509
+ records = /* @__PURE__ */ new Map();
510
+ cleanupInterval = null;
511
+ constructor(cleanupIntervalMs = 6e4) {
512
+ this.cleanupInterval = setInterval(() => {
513
+ this.cleanup();
514
+ }, cleanupIntervalMs);
515
+ }
516
+ async get(key) {
517
+ const record2 = this.records.get(key);
518
+ if (!record2) return null;
519
+ if (record2.expiresAt < /* @__PURE__ */ new Date()) {
520
+ this.records.delete(key);
521
+ return null;
522
+ }
523
+ return record2;
524
+ }
525
+ async set(key, record2) {
526
+ this.records.set(key, record2);
527
+ }
528
+ async delete(key) {
529
+ this.records.delete(key);
530
+ }
531
+ async exists(key) {
532
+ const record2 = await this.get(key);
533
+ return record2 !== null;
534
+ }
535
+ cleanup() {
536
+ const now = /* @__PURE__ */ new Date();
537
+ for (const [key, record2] of this.records) {
538
+ if (record2.expiresAt < now) {
539
+ this.records.delete(key);
540
+ }
541
+ }
542
+ }
543
+ destroy() {
544
+ if (this.cleanupInterval) {
545
+ clearInterval(this.cleanupInterval);
546
+ this.cleanupInterval = null;
547
+ }
548
+ this.records.clear();
549
+ }
550
+ };
551
+ var IdempotencyError = class extends Error {
552
+ constructor(message, code) {
553
+ super(message);
554
+ this.code = code;
555
+ this.name = "IdempotencyError";
556
+ }
557
+ };
558
+ var IdempotencyManager = class {
559
+ store;
560
+ ttl;
561
+ prefix;
562
+ constructor(config = {}) {
563
+ this.store = config.store ?? new MemoryIdempotencyStore();
564
+ this.ttl = config.ttl ?? 24 * 60 * 60 * 1e3;
565
+ this.prefix = config.prefix ?? "idem:";
566
+ }
567
+ /**
568
+ * Generate a unique idempotency key
569
+ */
570
+ generateKey() {
571
+ return `${this.prefix}${nanoid(21)}`;
572
+ }
573
+ /**
574
+ * Hash request parameters for validation
575
+ */
576
+ hashRequest(params) {
577
+ const json = JSON.stringify(params, Object.keys(params).sort());
578
+ let hash = 0;
579
+ for (let i = 0; i < json.length; i++) {
580
+ const char = json.charCodeAt(i);
581
+ hash = (hash << 5) - hash + char;
582
+ hash = hash & hash;
583
+ }
584
+ return hash.toString(36);
585
+ }
586
+ /**
587
+ * Execute operation with idempotency protection
588
+ */
589
+ async execute(key, params, operation) {
590
+ const fullKey = key.startsWith(this.prefix) ? key : `${this.prefix}${key}`;
591
+ const requestHash = this.hashRequest(params);
592
+ const existing = await this.store.get(fullKey);
593
+ if (existing) {
594
+ if (existing.requestHash !== requestHash) {
595
+ return err(new IdempotencyError(
596
+ "Idempotency key used with different request parameters",
597
+ "REQUEST_MISMATCH"
598
+ ));
599
+ }
600
+ if (existing.status === "completed" && existing.result !== void 0) {
601
+ return ok(existing.result);
602
+ }
603
+ if (existing.status === "pending") {
604
+ return err(new IdempotencyError(
605
+ "Request with this idempotency key is already in progress",
606
+ "REQUEST_IN_PROGRESS"
607
+ ));
608
+ }
609
+ if (existing.status === "failed") {
610
+ await this.store.delete(fullKey);
611
+ }
612
+ }
613
+ const record2 = {
614
+ key: fullKey,
615
+ status: "pending",
616
+ createdAt: /* @__PURE__ */ new Date(),
617
+ requestHash,
618
+ expiresAt: new Date(Date.now() + this.ttl)
619
+ };
620
+ await this.store.set(fullKey, record2);
621
+ try {
622
+ const result = await operation();
623
+ record2.status = "completed";
624
+ record2.result = result;
625
+ record2.completedAt = /* @__PURE__ */ new Date();
626
+ await this.store.set(fullKey, record2);
627
+ return ok(result);
628
+ } catch (error) {
629
+ record2.status = "failed";
630
+ await this.store.set(fullKey, record2);
631
+ throw error;
632
+ }
633
+ }
634
+ /**
635
+ * Check if operation with key was already completed
636
+ */
637
+ async wasCompleted(key) {
638
+ const fullKey = key.startsWith(this.prefix) ? key : `${this.prefix}${key}`;
639
+ const record2 = await this.store.get(fullKey);
640
+ return record2?.status === "completed";
641
+ }
642
+ /**
643
+ * Get cached result for key
644
+ */
645
+ async getCached(key) {
646
+ const fullKey = key.startsWith(this.prefix) ? key : `${this.prefix}${key}`;
647
+ const record2 = await this.store.get(fullKey);
648
+ return record2?.status === "completed" ? record2.result ?? null : null;
649
+ }
650
+ /**
651
+ * Invalidate a key (force re-execution on next call)
652
+ */
653
+ async invalidate(key) {
654
+ const fullKey = key.startsWith(this.prefix) ? key : `${this.prefix}${key}`;
655
+ await this.store.delete(fullKey);
656
+ }
657
+ };
658
+ function createIdempotencyManager(config) {
659
+ return new IdempotencyManager(config);
660
+ }
661
+
662
+ // src/utils/retry.ts
663
+ var DEFAULT_CONFIG = {
664
+ maxAttempts: 3,
665
+ baseDelay: 1e3,
666
+ maxDelay: 3e4,
667
+ backoffMultiplier: 2,
668
+ jitter: 0.1,
669
+ retryIf: isRetryableError
670
+ };
671
+ function calculateDelay(attempt, config) {
672
+ const exponentialDelay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt);
673
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelay);
674
+ const jitterRange = cappedDelay * config.jitter;
675
+ const jitter = Math.random() * jitterRange * 2 - jitterRange;
676
+ return Math.round(Math.max(0, cappedDelay + jitter));
677
+ }
678
+ function sleep(ms) {
679
+ return new Promise((resolve) => setTimeout(resolve, ms));
680
+ }
681
+ function isRetryableError(error) {
682
+ if (!(error instanceof Error)) return false;
683
+ if (error.message.includes("ECONNREFUSED")) return true;
684
+ if (error.message.includes("ETIMEDOUT")) return true;
685
+ if (error.message.includes("ENOTFOUND")) return true;
686
+ if (error.message.includes("network")) return true;
687
+ if (error.message.includes("timeout")) return true;
688
+ if (error.message.includes("429")) return true;
689
+ if (error.message.includes("rate limit")) return true;
690
+ if (error.message.includes("500")) return true;
691
+ if (error.message.includes("502")) return true;
692
+ if (error.message.includes("503")) return true;
693
+ if (error.message.includes("504")) return true;
694
+ if ("retryable" in error && error.retryable === true) return true;
695
+ return false;
696
+ }
697
+ async function retry(operation, config = {}) {
698
+ const fullConfig = { ...DEFAULT_CONFIG, ...config };
699
+ const state = {
700
+ attempt: 0,
701
+ totalDelay: 0,
702
+ errors: []
703
+ };
704
+ while (state.attempt < fullConfig.maxAttempts) {
705
+ try {
706
+ return await operation();
707
+ } catch (error) {
708
+ state.errors.push(error instanceof Error ? error : new Error(String(error)));
709
+ state.attempt++;
710
+ const shouldRetry = fullConfig.retryIf?.(error) ?? isRetryableError(error);
711
+ if (!shouldRetry || state.attempt >= fullConfig.maxAttempts) {
712
+ throw new RetryExhaustedError(
713
+ `Operation failed after ${state.attempt} attempts`,
714
+ state.errors
715
+ );
716
+ }
717
+ const delay = calculateDelay(state.attempt - 1, fullConfig);
718
+ state.totalDelay += delay;
719
+ fullConfig.onRetry?.(error, state.attempt, delay);
720
+ await sleep(delay);
721
+ }
722
+ }
723
+ throw new RetryExhaustedError(
724
+ `Operation failed after ${state.attempt} attempts`,
725
+ state.errors
726
+ );
727
+ }
728
+ async function retryWithResult(operation, config = {}) {
729
+ try {
730
+ const result = await retry(operation, config);
731
+ return ok(result);
732
+ } catch (error) {
733
+ if (error instanceof RetryExhaustedError) {
734
+ return err(error);
735
+ }
736
+ return err(new RetryExhaustedError("Operation failed", [
737
+ error instanceof Error ? error : new Error(String(error))
738
+ ]));
739
+ }
740
+ }
741
+ var RetryExhaustedError = class extends Error {
742
+ attempts;
743
+ errors;
744
+ constructor(message, errors) {
745
+ super(message);
746
+ this.name = "RetryExhaustedError";
747
+ this.attempts = errors.length;
748
+ this.errors = errors;
749
+ }
750
+ /**
751
+ * Get the last error
752
+ */
753
+ get lastError() {
754
+ return this.errors[this.errors.length - 1];
755
+ }
756
+ /**
757
+ * Get the first error
758
+ */
759
+ get firstError() {
760
+ return this.errors[0];
761
+ }
762
+ };
763
+ var DEFAULT_CIRCUIT_CONFIG = {
764
+ failureThreshold: 5,
765
+ resetTimeout: 3e4,
766
+ successThreshold: 3,
767
+ monitorWindow: 6e4
768
+ };
769
+ var CircuitBreaker = class {
770
+ state = "closed";
771
+ failures = [];
772
+ successes = 0;
773
+ lastFailure;
774
+ config;
775
+ constructor(config = {}) {
776
+ this.config = { ...DEFAULT_CIRCUIT_CONFIG, ...config };
777
+ }
778
+ /**
779
+ * Execute operation through circuit breaker
780
+ */
781
+ async execute(operation) {
782
+ if (this.state === "open") {
783
+ if (this.shouldAttemptReset()) {
784
+ this.state = "half-open";
785
+ } else {
786
+ throw new CircuitOpenError("Circuit is open, request rejected");
787
+ }
788
+ }
789
+ try {
790
+ const result = await operation();
791
+ this.onSuccess();
792
+ return result;
793
+ } catch (error) {
794
+ this.onFailure();
795
+ throw error;
796
+ }
797
+ }
798
+ /**
799
+ * Execute with Result type
800
+ */
801
+ async executeWithResult(operation) {
802
+ try {
803
+ const result = await this.execute(operation);
804
+ return ok(result);
805
+ } catch (error) {
806
+ return err(error);
807
+ }
808
+ }
809
+ onSuccess() {
810
+ if (this.state === "half-open") {
811
+ this.successes++;
812
+ if (this.successes >= this.config.successThreshold) {
813
+ this.reset();
814
+ }
815
+ }
816
+ this.cleanOldFailures();
817
+ }
818
+ onFailure() {
819
+ this.failures.push(/* @__PURE__ */ new Date());
820
+ this.lastFailure = /* @__PURE__ */ new Date();
821
+ this.successes = 0;
822
+ this.cleanOldFailures();
823
+ if (this.failures.length >= this.config.failureThreshold) {
824
+ this.state = "open";
825
+ }
826
+ }
827
+ shouldAttemptReset() {
828
+ if (!this.lastFailure) return true;
829
+ return Date.now() - this.lastFailure.getTime() >= this.config.resetTimeout;
830
+ }
831
+ cleanOldFailures() {
832
+ const cutoff = Date.now() - this.config.monitorWindow;
833
+ this.failures = this.failures.filter((f) => f.getTime() > cutoff);
834
+ }
835
+ reset() {
836
+ this.state = "closed";
837
+ this.failures = [];
838
+ this.successes = 0;
839
+ }
840
+ /**
841
+ * Get current circuit state
842
+ */
843
+ getState() {
844
+ return this.state;
845
+ }
846
+ /**
847
+ * Manually reset circuit
848
+ */
849
+ forceReset() {
850
+ this.reset();
851
+ }
852
+ /**
853
+ * Get circuit statistics
854
+ */
855
+ getStats() {
856
+ return {
857
+ state: this.state,
858
+ failures: this.failures.length,
859
+ successes: this.successes,
860
+ lastFailure: this.lastFailure
861
+ };
862
+ }
863
+ };
864
+ var CircuitOpenError = class extends Error {
865
+ constructor(message) {
866
+ super(message);
867
+ this.name = "CircuitOpenError";
868
+ }
869
+ };
870
+ function createCircuitBreaker(config) {
871
+ return new CircuitBreaker(config);
872
+ }
873
+ async function resilientExecute(operation, options = {}) {
874
+ const { retry: retryConfig, circuitBreaker } = options;
875
+ const wrappedOperation = async () => {
876
+ if (circuitBreaker) {
877
+ return circuitBreaker.execute(operation);
878
+ }
879
+ return operation();
880
+ };
881
+ if (retryConfig) {
882
+ return retry(wrappedOperation, retryConfig);
883
+ }
884
+ return wrappedOperation();
885
+ }
886
+
887
+ // src/core/errors.ts
888
+ var RevenueError = class extends Error {
889
+ code;
890
+ retryable;
891
+ metadata;
892
+ constructor(message, code, options = {}) {
893
+ super(message);
894
+ this.name = this.constructor.name;
895
+ this.code = code;
896
+ this.retryable = options.retryable ?? false;
897
+ this.metadata = options.metadata ?? {};
898
+ Error.captureStackTrace(this, this.constructor);
899
+ }
900
+ toJSON() {
901
+ return {
902
+ name: this.name,
903
+ message: this.message,
904
+ code: this.code,
905
+ retryable: this.retryable,
906
+ metadata: this.metadata
907
+ };
908
+ }
909
+ };
910
+ var ConfigurationError = class extends RevenueError {
911
+ constructor(message, metadata = {}) {
912
+ super(message, "CONFIGURATION_ERROR", { retryable: false, metadata });
913
+ }
914
+ };
915
+ var ModelNotRegisteredError = class extends ConfigurationError {
916
+ constructor(modelName) {
917
+ super(
918
+ `Model "${modelName}" is not registered. Register it via createRevenue({ models: { ${modelName}: ... } })`,
919
+ { modelName }
920
+ );
921
+ }
922
+ };
923
+ var ProviderError = class extends RevenueError {
924
+ constructor(message, code, options = {}) {
925
+ super(message, code, options);
926
+ }
927
+ };
928
+ var ProviderNotFoundError = class extends ProviderError {
929
+ constructor(providerName, availableProviders = []) {
930
+ super(
931
+ `Payment provider "${providerName}" not found. Available: ${availableProviders.join(", ")}`,
932
+ "PROVIDER_NOT_FOUND",
933
+ { retryable: false, metadata: { providerName, availableProviders } }
934
+ );
935
+ }
936
+ };
937
+ var ProviderCapabilityError = class extends ProviderError {
938
+ constructor(providerName, capability) {
939
+ super(
940
+ `Provider "${providerName}" does not support ${capability}`,
941
+ "PROVIDER_CAPABILITY_NOT_SUPPORTED",
942
+ { retryable: false, metadata: { providerName, capability } }
943
+ );
944
+ }
945
+ };
946
+ var PaymentIntentCreationError = class extends ProviderError {
947
+ constructor(providerName, originalError) {
948
+ super(
949
+ `Failed to create payment intent with provider "${providerName}": ${originalError.message}`,
950
+ "PAYMENT_INTENT_CREATION_FAILED",
951
+ { retryable: true, metadata: { providerName, originalError: originalError.message } }
952
+ );
953
+ }
954
+ };
955
+ var PaymentVerificationError = class extends ProviderError {
956
+ constructor(paymentIntentId, reason) {
957
+ super(
958
+ `Payment verification failed for intent "${paymentIntentId}": ${reason}`,
959
+ "PAYMENT_VERIFICATION_FAILED",
960
+ { retryable: true, metadata: { paymentIntentId, reason } }
961
+ );
962
+ }
963
+ };
964
+ var NotFoundError = class extends RevenueError {
965
+ constructor(message, code, metadata = {}) {
966
+ super(message, code, { retryable: false, metadata });
967
+ }
968
+ };
969
+ var SubscriptionNotFoundError = class extends NotFoundError {
970
+ constructor(subscriptionId) {
971
+ super(
972
+ `Subscription not found: ${subscriptionId}`,
973
+ "SUBSCRIPTION_NOT_FOUND",
974
+ { subscriptionId }
975
+ );
976
+ }
977
+ };
978
+ var TransactionNotFoundError = class extends NotFoundError {
979
+ constructor(transactionId) {
980
+ super(
981
+ `Transaction not found: ${transactionId}`,
982
+ "TRANSACTION_NOT_FOUND",
983
+ { transactionId }
984
+ );
985
+ }
986
+ };
987
+ var ValidationError = class extends RevenueError {
988
+ constructor(message, metadata = {}) {
989
+ super(message, "VALIDATION_ERROR", { retryable: false, metadata });
990
+ }
991
+ };
992
+ var InvalidAmountError = class extends ValidationError {
993
+ constructor(amount, message) {
994
+ super(
995
+ message ?? `Invalid amount: ${amount}. Amount must be non-negative`,
996
+ { amount }
997
+ );
998
+ }
999
+ };
1000
+ var MissingRequiredFieldError = class extends ValidationError {
1001
+ constructor(fieldName) {
1002
+ super(`Missing required field: ${fieldName}`, { fieldName });
1003
+ }
1004
+ };
1005
+ var StateError = class extends RevenueError {
1006
+ constructor(message, code, metadata = {}) {
1007
+ super(message, code, { retryable: false, metadata });
1008
+ }
1009
+ };
1010
+ var AlreadyVerifiedError = class extends StateError {
1011
+ constructor(transactionId) {
1012
+ super(
1013
+ `Transaction ${transactionId} is already verified`,
1014
+ "ALREADY_VERIFIED",
1015
+ { transactionId }
1016
+ );
1017
+ }
1018
+ };
1019
+ var InvalidStateTransitionError = class extends StateError {
1020
+ constructor(resourceType, resourceId, fromState, toState) {
1021
+ super(
1022
+ `Invalid state transition for ${resourceType} ${resourceId}: ${fromState} \u2192 ${toState}`,
1023
+ "INVALID_STATE_TRANSITION",
1024
+ { resourceType, resourceId, fromState, toState }
1025
+ );
1026
+ }
1027
+ };
1028
+ var SubscriptionNotActiveError = class extends StateError {
1029
+ constructor(subscriptionId, message) {
1030
+ super(
1031
+ message ?? `Subscription ${subscriptionId} is not active`,
1032
+ "SUBSCRIPTION_NOT_ACTIVE",
1033
+ { subscriptionId }
1034
+ );
1035
+ }
1036
+ };
1037
+ var OperationError = class extends RevenueError {
1038
+ constructor(message, code, options = {}) {
1039
+ super(message, code, options);
1040
+ }
1041
+ };
1042
+ var RefundNotSupportedError = class extends OperationError {
1043
+ constructor(providerName) {
1044
+ super(
1045
+ `Refunds are not supported by provider "${providerName}"`,
1046
+ "REFUND_NOT_SUPPORTED",
1047
+ { retryable: false, metadata: { providerName } }
1048
+ );
1049
+ }
1050
+ };
1051
+ var RefundError = class extends OperationError {
1052
+ constructor(transactionId, reason) {
1053
+ super(
1054
+ `Refund failed for transaction ${transactionId}: ${reason}`,
1055
+ "REFUND_FAILED",
1056
+ { retryable: true, metadata: { transactionId, reason } }
1057
+ );
1058
+ }
1059
+ };
1060
+ var ERROR_CODES = {
1061
+ // Configuration
1062
+ CONFIGURATION_ERROR: "CONFIGURATION_ERROR",
1063
+ MODEL_NOT_REGISTERED: "MODEL_NOT_REGISTERED",
1064
+ // Provider
1065
+ PROVIDER_NOT_FOUND: "PROVIDER_NOT_FOUND",
1066
+ PROVIDER_CAPABILITY_NOT_SUPPORTED: "PROVIDER_CAPABILITY_NOT_SUPPORTED",
1067
+ PAYMENT_INTENT_CREATION_FAILED: "PAYMENT_INTENT_CREATION_FAILED",
1068
+ PAYMENT_VERIFICATION_FAILED: "PAYMENT_VERIFICATION_FAILED",
1069
+ // Not Found
1070
+ SUBSCRIPTION_NOT_FOUND: "SUBSCRIPTION_NOT_FOUND",
1071
+ TRANSACTION_NOT_FOUND: "TRANSACTION_NOT_FOUND",
1072
+ // Validation
1073
+ VALIDATION_ERROR: "VALIDATION_ERROR",
1074
+ INVALID_AMOUNT: "INVALID_AMOUNT",
1075
+ MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
1076
+ // State
1077
+ ALREADY_VERIFIED: "ALREADY_VERIFIED",
1078
+ INVALID_STATE_TRANSITION: "INVALID_STATE_TRANSITION",
1079
+ SUBSCRIPTION_NOT_ACTIVE: "SUBSCRIPTION_NOT_ACTIVE",
1080
+ // Operations
1081
+ REFUND_NOT_SUPPORTED: "REFUND_NOT_SUPPORTED",
1082
+ REFUND_FAILED: "REFUND_FAILED"
1083
+ };
1084
+ function isRetryable(error) {
1085
+ return error instanceof RevenueError && error.retryable;
1086
+ }
1087
+ function isRevenueError(error) {
1088
+ return error instanceof RevenueError;
1089
+ }
1090
+
1091
+ // src/utils/hooks.ts
1092
+ function triggerHook(hooks, event, data, logger2) {
1093
+ const handlers = hooks[event] ?? [];
1094
+ if (handlers.length === 0) {
1095
+ return;
1096
+ }
1097
+ Promise.all(
1098
+ handlers.map(
1099
+ (handler) => Promise.resolve(handler(data)).catch((error) => {
1100
+ logger2.error(`Hook "${event}" failed:`, {
1101
+ error: error.message,
1102
+ stack: error.stack,
1103
+ event,
1104
+ // Don't log full data (could be huge)
1105
+ dataKeys: Object.keys(data)
1106
+ });
1107
+ })
1108
+ )
1109
+ ).catch(() => {
1110
+ });
1111
+ }
1112
+
1113
+ // src/enums/transaction.enums.ts
1114
+ var TRANSACTION_TYPE = {
1115
+ INCOME: "income",
1116
+ EXPENSE: "expense"
1117
+ };
1118
+ var TRANSACTION_TYPE_VALUES = Object.values(TRANSACTION_TYPE);
1119
+ var TRANSACTION_STATUS = {
1120
+ PENDING: "pending",
1121
+ PAYMENT_INITIATED: "payment_initiated",
1122
+ PROCESSING: "processing",
1123
+ REQUIRES_ACTION: "requires_action",
1124
+ VERIFIED: "verified",
1125
+ COMPLETED: "completed",
1126
+ FAILED: "failed",
1127
+ CANCELLED: "cancelled",
1128
+ EXPIRED: "expired",
1129
+ REFUNDED: "refunded",
1130
+ PARTIALLY_REFUNDED: "partially_refunded"
1131
+ };
1132
+ var TRANSACTION_STATUS_VALUES = Object.values(TRANSACTION_STATUS);
1133
+ var LIBRARY_CATEGORIES = {
1134
+ SUBSCRIPTION: "subscription",
1135
+ PURCHASE: "purchase"
1136
+ };
1137
+ var LIBRARY_CATEGORY_VALUES = Object.values(LIBRARY_CATEGORIES);
1138
+
1139
+ // src/utils/category-resolver.ts
1140
+ function resolveCategory(entity, monetizationType, categoryMappings = {}) {
1141
+ if (entity && categoryMappings[entity]) {
1142
+ return categoryMappings[entity];
1143
+ }
1144
+ switch (monetizationType) {
1145
+ case "subscription":
1146
+ return LIBRARY_CATEGORIES.SUBSCRIPTION;
1147
+ // 'subscription'
1148
+ case "purchase":
1149
+ return LIBRARY_CATEGORIES.PURCHASE;
1150
+ // 'purchase'
1151
+ default:
1152
+ return LIBRARY_CATEGORIES.SUBSCRIPTION;
1153
+ }
1154
+ }
1155
+ function isCategoryValid(category, allowedCategories = []) {
1156
+ return allowedCategories.includes(category);
1157
+ }
1158
+
1159
+ // src/utils/commission.ts
1160
+ function calculateCommission(amount, commissionRate, gatewayFeeRate = 0) {
1161
+ if (!commissionRate || commissionRate <= 0) {
1162
+ return null;
1163
+ }
1164
+ if (amount < 0) {
1165
+ throw new Error("Transaction amount cannot be negative");
1166
+ }
1167
+ if (commissionRate < 0 || commissionRate > 1) {
1168
+ throw new Error("Commission rate must be between 0 and 1");
1169
+ }
1170
+ if (gatewayFeeRate < 0 || gatewayFeeRate > 1) {
1171
+ throw new Error("Gateway fee rate must be between 0 and 1");
1172
+ }
1173
+ const grossAmount = Math.round(amount * commissionRate * 100) / 100;
1174
+ const gatewayFeeAmount = Math.round(amount * gatewayFeeRate * 100) / 100;
1175
+ const netAmount = Math.max(0, Math.round((grossAmount - gatewayFeeAmount) * 100) / 100);
1176
+ return {
1177
+ rate: commissionRate,
1178
+ grossAmount,
1179
+ gatewayFeeRate,
1180
+ gatewayFeeAmount,
1181
+ netAmount,
1182
+ status: "pending"
1183
+ };
1184
+ }
1185
+ function reverseCommission(originalCommission, originalAmount, refundAmount) {
1186
+ if (!originalCommission?.netAmount) {
1187
+ return null;
1188
+ }
1189
+ const refundRatio = refundAmount / originalAmount;
1190
+ const reversedNetAmount = Math.round(originalCommission.netAmount * refundRatio * 100) / 100;
1191
+ const reversedGrossAmount = Math.round(originalCommission.grossAmount * refundRatio * 100) / 100;
1192
+ const reversedGatewayFee = Math.round(originalCommission.gatewayFeeAmount * refundRatio * 100) / 100;
1193
+ return {
1194
+ rate: originalCommission.rate,
1195
+ grossAmount: reversedGrossAmount,
1196
+ gatewayFeeRate: originalCommission.gatewayFeeRate,
1197
+ gatewayFeeAmount: reversedGatewayFee,
1198
+ netAmount: reversedNetAmount,
1199
+ status: "waived"
1200
+ // Commission waived due to refund
1201
+ };
1202
+ }
1203
+
1204
+ // src/enums/monetization.enums.ts
1205
+ var MONETIZATION_TYPES = {
1206
+ FREE: "free",
1207
+ PURCHASE: "purchase",
1208
+ SUBSCRIPTION: "subscription"
1209
+ };
1210
+ var MONETIZATION_TYPE_VALUES = Object.values(MONETIZATION_TYPES);
1211
+
1212
+ // src/services/monetization.service.ts
1213
+ var MonetizationService = class {
1214
+ models;
1215
+ providers;
1216
+ config;
1217
+ hooks;
1218
+ logger;
1219
+ constructor(container) {
1220
+ this.models = container.get("models");
1221
+ this.providers = container.get("providers");
1222
+ this.config = container.get("config");
1223
+ this.hooks = container.get("hooks");
1224
+ this.logger = container.get("logger");
1225
+ }
1226
+ /**
1227
+ * Create a new monetization (purchase, subscription, or free item)
1228
+ *
1229
+ * @param params - Monetization parameters
1230
+ *
1231
+ * @example
1232
+ * // One-time purchase
1233
+ * await revenue.monetization.create({
1234
+ * data: {
1235
+ * organizationId: '...',
1236
+ * customerId: '...',
1237
+ * referenceId: order._id,
1238
+ * referenceModel: 'Order',
1239
+ * },
1240
+ * planKey: 'one_time',
1241
+ * monetizationType: 'purchase',
1242
+ * gateway: 'bkash',
1243
+ * amount: 1500,
1244
+ * });
1245
+ *
1246
+ * // Recurring subscription
1247
+ * await revenue.monetization.create({
1248
+ * data: {
1249
+ * organizationId: '...',
1250
+ * customerId: '...',
1251
+ * referenceId: subscription._id,
1252
+ * referenceModel: 'Subscription',
1253
+ * },
1254
+ * planKey: 'monthly',
1255
+ * monetizationType: 'subscription',
1256
+ * gateway: 'stripe',
1257
+ * amount: 2000,
1258
+ * });
1259
+ *
1260
+ * @returns Result with subscription, transaction, and paymentIntent
1261
+ */
1262
+ async create(params) {
1263
+ const {
1264
+ data,
1265
+ planKey,
1266
+ amount,
1267
+ currency = "BDT",
1268
+ gateway = "manual",
1269
+ entity = null,
1270
+ monetizationType = MONETIZATION_TYPES.SUBSCRIPTION,
1271
+ paymentData,
1272
+ metadata = {},
1273
+ idempotencyKey = null
1274
+ } = params;
1275
+ if (!planKey) {
1276
+ throw new MissingRequiredFieldError("planKey");
1277
+ }
1278
+ if (amount < 0) {
1279
+ throw new InvalidAmountError(amount);
1280
+ }
1281
+ const isFree = amount === 0;
1282
+ const provider = this.providers[gateway];
1283
+ if (!provider) {
1284
+ throw new ProviderNotFoundError(gateway, Object.keys(this.providers));
1285
+ }
1286
+ let paymentIntent = null;
1287
+ let transaction = null;
1288
+ if (!isFree) {
1289
+ try {
1290
+ paymentIntent = await provider.createIntent({
1291
+ amount,
1292
+ currency,
1293
+ metadata: {
1294
+ ...metadata,
1295
+ type: "subscription",
1296
+ planKey
1297
+ }
1298
+ });
1299
+ } catch (error) {
1300
+ throw new PaymentIntentCreationError(gateway, error);
1301
+ }
1302
+ const category = resolveCategory(entity, monetizationType, this.config.categoryMappings);
1303
+ const transactionType = this.config.transactionTypeMapping?.subscription ?? this.config.transactionTypeMapping?.[monetizationType] ?? TRANSACTION_TYPE.INCOME;
1304
+ const commissionRate = this.config.commissionRates?.[category] ?? 0;
1305
+ const gatewayFeeRate = this.config.gatewayFeeRates?.[gateway] ?? 0;
1306
+ const commission = calculateCommission(amount, commissionRate, gatewayFeeRate);
1307
+ const TransactionModel = this.models.Transaction;
1308
+ transaction = await TransactionModel.create({
1309
+ organizationId: data.organizationId,
1310
+ customerId: data.customerId ?? null,
1311
+ amount,
1312
+ currency,
1313
+ category,
1314
+ type: transactionType,
1315
+ method: paymentData?.method ?? "manual",
1316
+ status: paymentIntent.status === "succeeded" ? "verified" : "pending",
1317
+ gateway: {
1318
+ type: gateway,
1319
+ sessionId: paymentIntent.sessionId,
1320
+ paymentIntentId: paymentIntent.paymentIntentId,
1321
+ provider: paymentIntent.provider,
1322
+ metadata: paymentIntent.metadata
1323
+ },
1324
+ paymentDetails: {
1325
+ provider: gateway,
1326
+ ...paymentData
1327
+ },
1328
+ ...commission && { commission },
1329
+ // Only include if commission exists
1330
+ // Polymorphic reference (top-level, not metadata)
1331
+ ...data.referenceId && { referenceId: data.referenceId },
1332
+ ...data.referenceModel && { referenceModel: data.referenceModel },
1333
+ metadata: {
1334
+ ...metadata,
1335
+ planKey,
1336
+ entity,
1337
+ monetizationType,
1338
+ paymentIntentId: paymentIntent.id
1339
+ },
1340
+ idempotencyKey: idempotencyKey ?? `sub_${nanoid(16)}`
1341
+ });
1342
+ }
1343
+ let subscription = null;
1344
+ if (this.models.Subscription) {
1345
+ const SubscriptionModel = this.models.Subscription;
1346
+ const subscriptionData = {
1347
+ organizationId: data.organizationId,
1348
+ customerId: data.customerId ?? null,
1349
+ planKey,
1350
+ amount,
1351
+ currency,
1352
+ status: isFree ? "active" : "pending",
1353
+ isActive: isFree,
1354
+ gateway,
1355
+ transactionId: transaction?._id ?? null,
1356
+ paymentIntentId: paymentIntent?.id ?? null,
1357
+ metadata: {
1358
+ ...metadata,
1359
+ isFree,
1360
+ entity,
1361
+ monetizationType
1362
+ },
1363
+ ...data
1364
+ };
1365
+ delete subscriptionData.referenceId;
1366
+ delete subscriptionData.referenceModel;
1367
+ subscription = await SubscriptionModel.create(subscriptionData);
1368
+ }
1369
+ const eventData = {
1370
+ subscription,
1371
+ transaction,
1372
+ paymentIntent,
1373
+ isFree,
1374
+ monetizationType
1375
+ };
1376
+ if (monetizationType === MONETIZATION_TYPES.PURCHASE) {
1377
+ this._triggerHook("purchase.created", eventData);
1378
+ } else if (monetizationType === MONETIZATION_TYPES.SUBSCRIPTION) {
1379
+ this._triggerHook("subscription.created", eventData);
1380
+ } else if (monetizationType === MONETIZATION_TYPES.FREE) {
1381
+ this._triggerHook("free.created", eventData);
1382
+ }
1383
+ this._triggerHook("monetization.created", eventData);
1384
+ return {
1385
+ subscription,
1386
+ transaction,
1387
+ paymentIntent
1388
+ };
1389
+ }
1390
+ /**
1391
+ * Activate subscription after payment verification
1392
+ *
1393
+ * @param subscriptionId - Subscription ID or transaction ID
1394
+ * @param options - Activation options
1395
+ * @returns Updated subscription
1396
+ */
1397
+ async activate(subscriptionId, options = {}) {
1398
+ const { timestamp = /* @__PURE__ */ new Date() } = options;
1399
+ if (!this.models.Subscription) {
1400
+ throw new ModelNotRegisteredError("Subscription");
1401
+ }
1402
+ const SubscriptionModel = this.models.Subscription;
1403
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1404
+ if (!subscription) {
1405
+ throw new SubscriptionNotFoundError(subscriptionId);
1406
+ }
1407
+ if (subscription.isActive) {
1408
+ this.logger.warn("Subscription already active", { subscriptionId });
1409
+ return subscription;
1410
+ }
1411
+ const periodEnd = this._calculatePeriodEnd(subscription.planKey, timestamp);
1412
+ subscription.isActive = true;
1413
+ subscription.status = "active";
1414
+ subscription.startDate = timestamp;
1415
+ subscription.endDate = periodEnd;
1416
+ subscription.activatedAt = timestamp;
1417
+ await subscription.save();
1418
+ this._triggerHook("subscription.activated", {
1419
+ subscription,
1420
+ activatedAt: timestamp
1421
+ });
1422
+ return subscription;
1423
+ }
1424
+ /**
1425
+ * Renew subscription
1426
+ *
1427
+ * @param subscriptionId - Subscription ID
1428
+ * @param params - Renewal parameters
1429
+ * @returns { subscription, transaction, paymentIntent }
1430
+ */
1431
+ async renew(subscriptionId, params = {}) {
1432
+ const {
1433
+ gateway = "manual",
1434
+ entity = null,
1435
+ paymentData,
1436
+ metadata = {},
1437
+ idempotencyKey = null
1438
+ } = params;
1439
+ if (!this.models.Subscription) {
1440
+ throw new ModelNotRegisteredError("Subscription");
1441
+ }
1442
+ const SubscriptionModel = this.models.Subscription;
1443
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1444
+ if (!subscription) {
1445
+ throw new SubscriptionNotFoundError(subscriptionId);
1446
+ }
1447
+ if (subscription.amount === 0) {
1448
+ throw new InvalidAmountError(0, "Free subscriptions do not require renewal");
1449
+ }
1450
+ const provider = this.providers[gateway];
1451
+ if (!provider) {
1452
+ throw new ProviderNotFoundError(gateway, Object.keys(this.providers));
1453
+ }
1454
+ let paymentIntent = null;
1455
+ try {
1456
+ paymentIntent = await provider.createIntent({
1457
+ amount: subscription.amount,
1458
+ currency: subscription.currency ?? "BDT",
1459
+ metadata: {
1460
+ ...metadata,
1461
+ type: "subscription_renewal",
1462
+ subscriptionId: subscription._id.toString()
1463
+ }
1464
+ });
1465
+ } catch (error) {
1466
+ this.logger.error("Failed to create payment intent for renewal:", error);
1467
+ throw new PaymentIntentCreationError(gateway, error);
1468
+ }
1469
+ const effectiveEntity = entity ?? subscription.metadata?.entity;
1470
+ const effectiveMonetizationType = subscription.metadata?.monetizationType ?? MONETIZATION_TYPES.SUBSCRIPTION;
1471
+ const category = resolveCategory(effectiveEntity, effectiveMonetizationType, this.config.categoryMappings);
1472
+ const transactionType = this.config.transactionTypeMapping?.subscription_renewal ?? this.config.transactionTypeMapping?.subscription ?? this.config.transactionTypeMapping?.[effectiveMonetizationType] ?? TRANSACTION_TYPE.INCOME;
1473
+ const commissionRate = this.config.commissionRates?.[category] ?? 0;
1474
+ const gatewayFeeRate = this.config.gatewayFeeRates?.[gateway] ?? 0;
1475
+ const commission = calculateCommission(subscription.amount, commissionRate, gatewayFeeRate);
1476
+ const TransactionModel = this.models.Transaction;
1477
+ const transaction = await TransactionModel.create({
1478
+ organizationId: subscription.organizationId,
1479
+ customerId: subscription.customerId,
1480
+ amount: subscription.amount,
1481
+ currency: subscription.currency ?? "BDT",
1482
+ category,
1483
+ type: transactionType,
1484
+ method: paymentData?.method ?? "manual",
1485
+ status: paymentIntent.status === "succeeded" ? "verified" : "pending",
1486
+ gateway: {
1487
+ type: gateway,
1488
+ sessionId: paymentIntent.sessionId,
1489
+ paymentIntentId: paymentIntent.paymentIntentId,
1490
+ provider: paymentIntent.provider,
1491
+ metadata: paymentIntent.metadata
1492
+ },
1493
+ paymentDetails: {
1494
+ provider: gateway,
1495
+ ...paymentData
1496
+ },
1497
+ ...commission && { commission },
1498
+ // Only include if commission exists
1499
+ // Polymorphic reference to subscription
1500
+ referenceId: subscription._id,
1501
+ referenceModel: "Subscription",
1502
+ metadata: {
1503
+ ...metadata,
1504
+ subscriptionId: subscription._id.toString(),
1505
+ // Keep for backward compat
1506
+ entity: effectiveEntity,
1507
+ monetizationType: effectiveMonetizationType,
1508
+ isRenewal: true,
1509
+ paymentIntentId: paymentIntent.id
1510
+ },
1511
+ idempotencyKey: idempotencyKey ?? `renewal_${nanoid(16)}`
1512
+ });
1513
+ subscription.status = "pending_renewal";
1514
+ subscription.renewalTransactionId = transaction._id;
1515
+ subscription.renewalCount = (subscription.renewalCount ?? 0) + 1;
1516
+ await subscription.save();
1517
+ this._triggerHook("subscription.renewed", {
1518
+ subscription,
1519
+ transaction,
1520
+ paymentIntent,
1521
+ renewalCount: subscription.renewalCount
1522
+ });
1523
+ return {
1524
+ subscription,
1525
+ transaction,
1526
+ paymentIntent
1527
+ };
1528
+ }
1529
+ /**
1530
+ * Cancel subscription
1531
+ *
1532
+ * @param subscriptionId - Subscription ID
1533
+ * @param options - Cancellation options
1534
+ * @returns Updated subscription
1535
+ */
1536
+ async cancel(subscriptionId, options = {}) {
1537
+ const { immediate = false, reason = null } = options;
1538
+ if (!this.models.Subscription) {
1539
+ throw new ModelNotRegisteredError("Subscription");
1540
+ }
1541
+ const SubscriptionModel = this.models.Subscription;
1542
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1543
+ if (!subscription) {
1544
+ throw new SubscriptionNotFoundError(subscriptionId);
1545
+ }
1546
+ const now = /* @__PURE__ */ new Date();
1547
+ if (immediate) {
1548
+ subscription.isActive = false;
1549
+ subscription.status = "cancelled";
1550
+ subscription.canceledAt = now;
1551
+ subscription.cancellationReason = reason;
1552
+ } else {
1553
+ subscription.cancelAt = subscription.endDate ?? now;
1554
+ subscription.cancellationReason = reason;
1555
+ }
1556
+ await subscription.save();
1557
+ this._triggerHook("subscription.cancelled", {
1558
+ subscription,
1559
+ immediate,
1560
+ reason,
1561
+ canceledAt: immediate ? now : subscription.cancelAt
1562
+ });
1563
+ return subscription;
1564
+ }
1565
+ /**
1566
+ * Pause subscription
1567
+ *
1568
+ * @param subscriptionId - Subscription ID
1569
+ * @param options - Pause options
1570
+ * @returns Updated subscription
1571
+ */
1572
+ async pause(subscriptionId, options = {}) {
1573
+ const { reason = null } = options;
1574
+ if (!this.models.Subscription) {
1575
+ throw new ModelNotRegisteredError("Subscription");
1576
+ }
1577
+ const SubscriptionModel = this.models.Subscription;
1578
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1579
+ if (!subscription) {
1580
+ throw new SubscriptionNotFoundError(subscriptionId);
1581
+ }
1582
+ if (!subscription.isActive) {
1583
+ throw new SubscriptionNotActiveError(subscriptionId, "Only active subscriptions can be paused");
1584
+ }
1585
+ const pausedAt = /* @__PURE__ */ new Date();
1586
+ subscription.isActive = false;
1587
+ subscription.status = "paused";
1588
+ subscription.pausedAt = pausedAt;
1589
+ subscription.pauseReason = reason;
1590
+ await subscription.save();
1591
+ this._triggerHook("subscription.paused", {
1592
+ subscription,
1593
+ reason,
1594
+ pausedAt
1595
+ });
1596
+ return subscription;
1597
+ }
1598
+ /**
1599
+ * Resume subscription
1600
+ *
1601
+ * @param subscriptionId - Subscription ID
1602
+ * @param options - Resume options
1603
+ * @returns Updated subscription
1604
+ */
1605
+ async resume(subscriptionId, options = {}) {
1606
+ const { extendPeriod = false } = options;
1607
+ if (!this.models.Subscription) {
1608
+ throw new ModelNotRegisteredError("Subscription");
1609
+ }
1610
+ const SubscriptionModel = this.models.Subscription;
1611
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1612
+ if (!subscription) {
1613
+ throw new SubscriptionNotFoundError(subscriptionId);
1614
+ }
1615
+ if (!subscription.pausedAt) {
1616
+ throw new InvalidStateTransitionError(
1617
+ "resume",
1618
+ "paused",
1619
+ subscription.status,
1620
+ "Only paused subscriptions can be resumed"
1621
+ );
1622
+ }
1623
+ const now = /* @__PURE__ */ new Date();
1624
+ const pausedAt = new Date(subscription.pausedAt);
1625
+ const pauseDuration = now.getTime() - pausedAt.getTime();
1626
+ subscription.isActive = true;
1627
+ subscription.status = "active";
1628
+ subscription.pausedAt = null;
1629
+ subscription.pauseReason = null;
1630
+ if (extendPeriod && subscription.endDate) {
1631
+ const currentEnd = new Date(subscription.endDate);
1632
+ subscription.endDate = new Date(currentEnd.getTime() + pauseDuration);
1633
+ }
1634
+ await subscription.save();
1635
+ this._triggerHook("subscription.resumed", {
1636
+ subscription,
1637
+ extendPeriod,
1638
+ pauseDuration,
1639
+ resumedAt: now
1640
+ });
1641
+ return subscription;
1642
+ }
1643
+ /**
1644
+ * List subscriptions with filters
1645
+ *
1646
+ * @param filters - Query filters
1647
+ * @param options - Query options (limit, skip, sort)
1648
+ * @returns Subscriptions
1649
+ */
1650
+ async list(filters = {}, options = {}) {
1651
+ if (!this.models.Subscription) {
1652
+ throw new ModelNotRegisteredError("Subscription");
1653
+ }
1654
+ const SubscriptionModel = this.models.Subscription;
1655
+ const { limit = 50, skip = 0, sort = { createdAt: -1 } } = options;
1656
+ const subscriptions = await SubscriptionModel.find(filters).limit(limit).skip(skip).sort(sort);
1657
+ return subscriptions;
1658
+ }
1659
+ /**
1660
+ * Get subscription by ID
1661
+ *
1662
+ * @param subscriptionId - Subscription ID
1663
+ * @returns Subscription
1664
+ */
1665
+ async get(subscriptionId) {
1666
+ if (!this.models.Subscription) {
1667
+ throw new ModelNotRegisteredError("Subscription");
1668
+ }
1669
+ const SubscriptionModel = this.models.Subscription;
1670
+ const subscription = await SubscriptionModel.findById(subscriptionId);
1671
+ if (!subscription) {
1672
+ throw new SubscriptionNotFoundError(subscriptionId);
1673
+ }
1674
+ return subscription;
1675
+ }
1676
+ /**
1677
+ * Calculate period end date based on plan key
1678
+ * @private
1679
+ */
1680
+ _calculatePeriodEnd(planKey, startDate = /* @__PURE__ */ new Date()) {
1681
+ const start = new Date(startDate);
1682
+ const end = new Date(start);
1683
+ switch (planKey) {
1684
+ case "monthly":
1685
+ end.setMonth(end.getMonth() + 1);
1686
+ break;
1687
+ case "quarterly":
1688
+ end.setMonth(end.getMonth() + 3);
1689
+ break;
1690
+ case "yearly":
1691
+ end.setFullYear(end.getFullYear() + 1);
1692
+ break;
1693
+ default:
1694
+ end.setDate(end.getDate() + 30);
1695
+ }
1696
+ return end;
1697
+ }
1698
+ /**
1699
+ * Trigger event hook (fire-and-forget, non-blocking)
1700
+ * @private
1701
+ */
1702
+ _triggerHook(event, data) {
1703
+ triggerHook(this.hooks, event, data, this.logger);
1704
+ }
1705
+ };
1706
+
1707
+ // src/services/payment.service.ts
1708
+ var PaymentService = class {
1709
+ models;
1710
+ providers;
1711
+ config;
1712
+ hooks;
1713
+ logger;
1714
+ constructor(container) {
1715
+ this.models = container.get("models");
1716
+ this.providers = container.get("providers");
1717
+ this.config = container.get("config");
1718
+ this.hooks = container.get("hooks");
1719
+ this.logger = container.get("logger");
1720
+ }
1721
+ /**
1722
+ * Verify a payment
1723
+ *
1724
+ * @param paymentIntentId - Payment intent ID, session ID, or transaction ID
1725
+ * @param options - Verification options
1726
+ * @returns { transaction, status }
1727
+ */
1728
+ async verify(paymentIntentId, options = {}) {
1729
+ const { verifiedBy = null } = options;
1730
+ const TransactionModel = this.models.Transaction;
1731
+ const transaction = await this._findTransaction(TransactionModel, paymentIntentId);
1732
+ if (!transaction) {
1733
+ throw new TransactionNotFoundError(paymentIntentId);
1734
+ }
1735
+ if (transaction.status === "verified" || transaction.status === "completed") {
1736
+ throw new AlreadyVerifiedError(transaction._id.toString());
1737
+ }
1738
+ const gatewayType = transaction.gateway?.type ?? "manual";
1739
+ const provider = this.providers[gatewayType];
1740
+ if (!provider) {
1741
+ throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
1742
+ }
1743
+ let paymentResult = null;
1744
+ try {
1745
+ paymentResult = await provider.verifyPayment(paymentIntentId);
1746
+ } catch (error) {
1747
+ this.logger.error("Payment verification failed:", error);
1748
+ transaction.status = "failed";
1749
+ transaction.failureReason = error.message;
1750
+ transaction.metadata = {
1751
+ ...transaction.metadata,
1752
+ verificationError: error.message,
1753
+ failedAt: (/* @__PURE__ */ new Date()).toISOString()
1754
+ };
1755
+ await transaction.save();
1756
+ this._triggerHook("payment.failed", {
1757
+ transaction,
1758
+ error: error.message,
1759
+ provider: gatewayType,
1760
+ paymentIntentId
1761
+ });
1762
+ throw new PaymentVerificationError(paymentIntentId, error.message);
1763
+ }
1764
+ if (paymentResult.amount && paymentResult.amount !== transaction.amount) {
1765
+ throw new ValidationError(
1766
+ `Amount mismatch: expected ${transaction.amount}, got ${paymentResult.amount}`,
1767
+ { expected: transaction.amount, actual: paymentResult.amount }
1768
+ );
1769
+ }
1770
+ if (paymentResult.currency && paymentResult.currency.toUpperCase() !== transaction.currency.toUpperCase()) {
1771
+ throw new ValidationError(
1772
+ `Currency mismatch: expected ${transaction.currency}, got ${paymentResult.currency}`,
1773
+ { expected: transaction.currency, actual: paymentResult.currency }
1774
+ );
1775
+ }
1776
+ transaction.status = paymentResult.status === "succeeded" ? "verified" : paymentResult.status;
1777
+ transaction.verifiedAt = paymentResult.paidAt ?? /* @__PURE__ */ new Date();
1778
+ transaction.verifiedBy = verifiedBy;
1779
+ transaction.gateway = {
1780
+ ...transaction.gateway,
1781
+ type: transaction.gateway?.type ?? "manual",
1782
+ verificationData: paymentResult.metadata
1783
+ };
1784
+ await transaction.save();
1785
+ this._triggerHook("payment.verified", {
1786
+ transaction,
1787
+ paymentResult,
1788
+ verifiedBy
1789
+ });
1790
+ return {
1791
+ transaction,
1792
+ paymentResult,
1793
+ status: transaction.status
1794
+ };
1795
+ }
1796
+ /**
1797
+ * Get payment status
1798
+ *
1799
+ * @param paymentIntentId - Payment intent ID, session ID, or transaction ID
1800
+ * @returns { transaction, status }
1801
+ */
1802
+ async getStatus(paymentIntentId) {
1803
+ const TransactionModel = this.models.Transaction;
1804
+ const transaction = await this._findTransaction(TransactionModel, paymentIntentId);
1805
+ if (!transaction) {
1806
+ throw new TransactionNotFoundError(paymentIntentId);
1807
+ }
1808
+ const gatewayType = transaction.gateway?.type ?? "manual";
1809
+ const provider = this.providers[gatewayType];
1810
+ if (!provider) {
1811
+ throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
1812
+ }
1813
+ let paymentResult = null;
1814
+ try {
1815
+ paymentResult = await provider.getStatus(paymentIntentId);
1816
+ } catch (error) {
1817
+ this.logger.warn("Failed to get payment status from provider:", error);
1818
+ return {
1819
+ transaction,
1820
+ status: transaction.status,
1821
+ provider: gatewayType
1822
+ };
1823
+ }
1824
+ return {
1825
+ transaction,
1826
+ paymentResult,
1827
+ status: paymentResult.status,
1828
+ provider: gatewayType
1829
+ };
1830
+ }
1831
+ /**
1832
+ * Refund a payment
1833
+ *
1834
+ * @param paymentId - Payment intent ID, session ID, or transaction ID
1835
+ * @param amount - Amount to refund (optional, full refund if not provided)
1836
+ * @param options - Refund options
1837
+ * @returns { transaction, refundResult }
1838
+ */
1839
+ async refund(paymentId, amount = null, options = {}) {
1840
+ const { reason = null } = options;
1841
+ const TransactionModel = this.models.Transaction;
1842
+ const transaction = await this._findTransaction(TransactionModel, paymentId);
1843
+ if (!transaction) {
1844
+ throw new TransactionNotFoundError(paymentId);
1845
+ }
1846
+ if (transaction.status !== "verified" && transaction.status !== "completed") {
1847
+ throw new RefundError(transaction._id.toString(), "Only verified/completed transactions can be refunded");
1848
+ }
1849
+ const gatewayType = transaction.gateway?.type ?? "manual";
1850
+ const provider = this.providers[gatewayType];
1851
+ if (!provider) {
1852
+ throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
1853
+ }
1854
+ const capabilities = provider.getCapabilities();
1855
+ if (!capabilities.supportsRefunds) {
1856
+ throw new RefundNotSupportedError(gatewayType);
1857
+ }
1858
+ const refundedSoFar = transaction.refundedAmount ?? 0;
1859
+ const refundableAmount = transaction.amount - refundedSoFar;
1860
+ const refundAmount = amount ?? refundableAmount;
1861
+ if (refundAmount <= 0) {
1862
+ throw new ValidationError(`Refund amount must be positive, got ${refundAmount}`);
1863
+ }
1864
+ if (refundAmount > refundableAmount) {
1865
+ throw new ValidationError(
1866
+ `Refund amount (${refundAmount}) exceeds refundable balance (${refundableAmount})`,
1867
+ { refundAmount, refundableAmount, alreadyRefunded: refundedSoFar }
1868
+ );
1869
+ }
1870
+ let refundResult;
1871
+ try {
1872
+ refundResult = await provider.refund(paymentId, refundAmount, { reason: reason ?? void 0 });
1873
+ } catch (error) {
1874
+ this.logger.error("Refund failed:", error);
1875
+ throw new RefundError(paymentId, error.message);
1876
+ }
1877
+ const refundTransactionType = this.config.transactionTypeMapping?.refund ?? TRANSACTION_TYPE.EXPENSE;
1878
+ const refundCommission = transaction.commission ? reverseCommission(transaction.commission, transaction.amount, refundAmount) : null;
1879
+ const refundTransaction = await TransactionModel.create({
1880
+ organizationId: transaction.organizationId,
1881
+ customerId: transaction.customerId,
1882
+ amount: refundAmount,
1883
+ currency: transaction.currency,
1884
+ category: transaction.category,
1885
+ type: refundTransactionType,
1886
+ // EXPENSE - money going out
1887
+ method: transaction.method ?? "manual",
1888
+ status: "completed",
1889
+ gateway: {
1890
+ type: transaction.gateway?.type ?? "manual",
1891
+ paymentIntentId: refundResult.id,
1892
+ provider: refundResult.provider
1893
+ },
1894
+ paymentDetails: transaction.paymentDetails,
1895
+ ...refundCommission && { commission: refundCommission },
1896
+ // Reversed commission
1897
+ // Polymorphic reference (copy from original transaction)
1898
+ ...transaction.referenceId && { referenceId: transaction.referenceId },
1899
+ ...transaction.referenceModel && { referenceModel: transaction.referenceModel },
1900
+ metadata: {
1901
+ ...transaction.metadata,
1902
+ isRefund: true,
1903
+ originalTransactionId: transaction._id.toString(),
1904
+ refundReason: reason,
1905
+ refundResult: refundResult.metadata
1906
+ },
1907
+ idempotencyKey: `refund_${transaction._id}_${Date.now()}`
1908
+ });
1909
+ const isPartialRefund = refundAmount < transaction.amount;
1910
+ transaction.status = isPartialRefund ? "partially_refunded" : "refunded";
1911
+ transaction.refundedAmount = (transaction.refundedAmount ?? 0) + refundAmount;
1912
+ transaction.refundedAt = refundResult.refundedAt ?? /* @__PURE__ */ new Date();
1913
+ transaction.metadata = {
1914
+ ...transaction.metadata,
1915
+ refundTransactionId: refundTransaction._id.toString(),
1916
+ refundReason: reason
1917
+ };
1918
+ await transaction.save();
1919
+ this._triggerHook("payment.refunded", {
1920
+ transaction,
1921
+ refundTransaction,
1922
+ refundResult,
1923
+ refundAmount,
1924
+ reason,
1925
+ isPartialRefund
1926
+ });
1927
+ return {
1928
+ transaction,
1929
+ refundTransaction,
1930
+ refundResult,
1931
+ status: transaction.status
1932
+ };
1933
+ }
1934
+ /**
1935
+ * Handle webhook from payment provider
1936
+ *
1937
+ * @param provider - Provider name
1938
+ * @param payload - Webhook payload
1939
+ * @param headers - Request headers
1940
+ * @returns { event, transaction }
1941
+ */
1942
+ async handleWebhook(providerName, payload, headers = {}) {
1943
+ const provider = this.providers[providerName];
1944
+ if (!provider) {
1945
+ throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
1946
+ }
1947
+ const capabilities = provider.getCapabilities();
1948
+ if (!capabilities.supportsWebhooks) {
1949
+ throw new ProviderCapabilityError(providerName, "webhooks");
1950
+ }
1951
+ let webhookEvent;
1952
+ try {
1953
+ webhookEvent = await provider.handleWebhook(payload, headers);
1954
+ } catch (error) {
1955
+ this.logger.error("Webhook processing failed:", error);
1956
+ throw new ProviderError(
1957
+ `Webhook processing failed for ${providerName}: ${error.message}`,
1958
+ "WEBHOOK_PROCESSING_FAILED",
1959
+ { retryable: false }
1960
+ );
1961
+ }
1962
+ if (!webhookEvent?.data?.sessionId && !webhookEvent?.data?.paymentIntentId) {
1963
+ throw new ValidationError(
1964
+ `Invalid webhook event structure from ${providerName}: missing sessionId or paymentIntentId`,
1965
+ { provider: providerName, eventType: webhookEvent?.type }
1966
+ );
1967
+ }
1968
+ const TransactionModel = this.models.Transaction;
1969
+ let transaction = null;
1970
+ if (webhookEvent.data.sessionId) {
1971
+ transaction = await TransactionModel.findOne({
1972
+ "gateway.sessionId": webhookEvent.data.sessionId
1973
+ });
1974
+ }
1975
+ if (!transaction && webhookEvent.data.paymentIntentId) {
1976
+ transaction = await TransactionModel.findOne({
1977
+ "gateway.paymentIntentId": webhookEvent.data.paymentIntentId
1978
+ });
1979
+ }
1980
+ if (!transaction) {
1981
+ this.logger.warn("Transaction not found for webhook event", {
1982
+ provider: providerName,
1983
+ eventId: webhookEvent.id,
1984
+ sessionId: webhookEvent.data.sessionId,
1985
+ paymentIntentId: webhookEvent.data.paymentIntentId
1986
+ });
1987
+ throw new TransactionNotFoundError(
1988
+ webhookEvent.data.sessionId ?? webhookEvent.data.paymentIntentId ?? "unknown"
1989
+ );
1990
+ }
1991
+ if (webhookEvent.data.sessionId && !transaction.gateway?.sessionId) {
1992
+ transaction.gateway = {
1993
+ ...transaction.gateway,
1994
+ type: transaction.gateway?.type ?? "manual",
1995
+ sessionId: webhookEvent.data.sessionId
1996
+ };
1997
+ }
1998
+ if (webhookEvent.data.paymentIntentId && !transaction.gateway?.paymentIntentId) {
1999
+ transaction.gateway = {
2000
+ ...transaction.gateway,
2001
+ type: transaction.gateway?.type ?? "manual",
2002
+ paymentIntentId: webhookEvent.data.paymentIntentId
2003
+ };
2004
+ }
2005
+ if (transaction.webhook?.eventId === webhookEvent.id && transaction.webhook?.processedAt) {
2006
+ this.logger.warn("Webhook already processed", {
2007
+ transactionId: transaction._id,
2008
+ eventId: webhookEvent.id
2009
+ });
2010
+ return {
2011
+ event: webhookEvent,
2012
+ transaction,
2013
+ status: "already_processed"
2014
+ };
2015
+ }
2016
+ transaction.webhook = {
2017
+ eventId: webhookEvent.id,
2018
+ eventType: webhookEvent.type,
2019
+ receivedAt: /* @__PURE__ */ new Date(),
2020
+ processedAt: /* @__PURE__ */ new Date(),
2021
+ data: webhookEvent.data
2022
+ };
2023
+ if (webhookEvent.type === "payment.succeeded") {
2024
+ transaction.status = "verified";
2025
+ transaction.verifiedAt = webhookEvent.createdAt;
2026
+ } else if (webhookEvent.type === "payment.failed") {
2027
+ transaction.status = "failed";
2028
+ } else if (webhookEvent.type === "refund.succeeded") {
2029
+ transaction.status = "refunded";
2030
+ transaction.refundedAt = webhookEvent.createdAt;
2031
+ }
2032
+ await transaction.save();
2033
+ this._triggerHook(`payment.webhook.${webhookEvent.type}`, {
2034
+ event: webhookEvent,
2035
+ transaction
2036
+ });
2037
+ return {
2038
+ event: webhookEvent,
2039
+ transaction,
2040
+ status: "processed"
2041
+ };
2042
+ }
2043
+ /**
2044
+ * List payments/transactions with filters
2045
+ *
2046
+ * @param filters - Query filters
2047
+ * @param options - Query options (limit, skip, sort)
2048
+ * @returns Transactions
2049
+ */
2050
+ async list(filters = {}, options = {}) {
2051
+ const TransactionModel = this.models.Transaction;
2052
+ const { limit = 50, skip = 0, sort = { createdAt: -1 } } = options;
2053
+ const transactions = await TransactionModel.find(filters).limit(limit).skip(skip).sort(sort);
2054
+ return transactions;
2055
+ }
2056
+ /**
2057
+ * Get payment/transaction by ID
2058
+ *
2059
+ * @param transactionId - Transaction ID
2060
+ * @returns Transaction
2061
+ */
2062
+ async get(transactionId) {
2063
+ const TransactionModel = this.models.Transaction;
2064
+ const transaction = await TransactionModel.findById(transactionId);
2065
+ if (!transaction) {
2066
+ throw new TransactionNotFoundError(transactionId);
2067
+ }
2068
+ return transaction;
2069
+ }
2070
+ /**
2071
+ * Get provider instance
2072
+ *
2073
+ * @param providerName - Provider name
2074
+ * @returns Provider instance
2075
+ */
2076
+ getProvider(providerName) {
2077
+ const provider = this.providers[providerName];
2078
+ if (!provider) {
2079
+ throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
2080
+ }
2081
+ return provider;
2082
+ }
2083
+ /**
2084
+ * Trigger event hook (fire-and-forget, non-blocking)
2085
+ * @private
2086
+ */
2087
+ _triggerHook(event, data) {
2088
+ triggerHook(this.hooks, event, data, this.logger);
2089
+ }
2090
+ /**
2091
+ * Find transaction by sessionId, paymentIntentId, or transaction ID
2092
+ * @private
2093
+ */
2094
+ async _findTransaction(TransactionModel, identifier) {
2095
+ let transaction = await TransactionModel.findOne({
2096
+ "gateway.sessionId": identifier
2097
+ });
2098
+ if (!transaction) {
2099
+ transaction = await TransactionModel.findOne({
2100
+ "gateway.paymentIntentId": identifier
2101
+ });
2102
+ }
2103
+ if (!transaction) {
2104
+ transaction = await TransactionModel.findById(identifier);
2105
+ }
2106
+ return transaction;
2107
+ }
2108
+ };
2109
+
2110
+ // src/services/transaction.service.ts
2111
+ var TransactionService = class {
2112
+ models;
2113
+ hooks;
2114
+ logger;
2115
+ constructor(container) {
2116
+ this.models = container.get("models");
2117
+ this.hooks = container.get("hooks");
2118
+ this.logger = container.get("logger");
2119
+ }
2120
+ /**
2121
+ * Get transaction by ID
2122
+ *
2123
+ * @param transactionId - Transaction ID
2124
+ * @returns Transaction
2125
+ */
2126
+ async get(transactionId) {
2127
+ const TransactionModel = this.models.Transaction;
2128
+ const transaction = await TransactionModel.findById(transactionId);
2129
+ if (!transaction) {
2130
+ throw new TransactionNotFoundError(transactionId);
2131
+ }
2132
+ return transaction;
2133
+ }
2134
+ /**
2135
+ * List transactions with filters
2136
+ *
2137
+ * @param filters - Query filters
2138
+ * @param options - Query options (limit, skip, sort, populate)
2139
+ * @returns { transactions, total, page, limit }
2140
+ */
2141
+ async list(filters = {}, options = {}) {
2142
+ const TransactionModel = this.models.Transaction;
2143
+ const {
2144
+ limit = 50,
2145
+ skip = 0,
2146
+ page = null,
2147
+ sort = { createdAt: -1 },
2148
+ populate = []
2149
+ } = options;
2150
+ const actualSkip = page ? (page - 1) * limit : skip;
2151
+ let query = TransactionModel.find(filters).limit(limit).skip(actualSkip).sort(sort);
2152
+ if (populate.length > 0 && typeof query.populate === "function") {
2153
+ populate.forEach((field) => {
2154
+ query = query.populate(field);
2155
+ });
2156
+ }
2157
+ const transactions = await query;
2158
+ const model = TransactionModel;
2159
+ const total = await (model.countDocuments ? model.countDocuments(filters) : model.count?.(filters)) ?? 0;
2160
+ return {
2161
+ transactions,
2162
+ total,
2163
+ page: page ?? Math.floor(actualSkip / limit) + 1,
2164
+ limit,
2165
+ pages: Math.ceil(total / limit)
2166
+ };
2167
+ }
2168
+ /**
2169
+ * Update transaction
2170
+ *
2171
+ * @param transactionId - Transaction ID
2172
+ * @param updates - Fields to update
2173
+ * @returns Updated transaction
2174
+ */
2175
+ async update(transactionId, updates) {
2176
+ const TransactionModel = this.models.Transaction;
2177
+ const model = TransactionModel;
2178
+ let transaction;
2179
+ if (typeof model.update === "function") {
2180
+ transaction = await model.update(transactionId, updates);
2181
+ } else if (typeof model.findByIdAndUpdate === "function") {
2182
+ transaction = await model.findByIdAndUpdate(
2183
+ transactionId,
2184
+ { $set: updates },
2185
+ { new: true }
2186
+ );
2187
+ } else {
2188
+ throw new Error("Transaction model does not support update operations");
2189
+ }
2190
+ if (!transaction) {
2191
+ throw new TransactionNotFoundError(transactionId);
2192
+ }
2193
+ this._triggerHook("transaction.updated", {
2194
+ transaction,
2195
+ updates
2196
+ });
2197
+ return transaction;
2198
+ }
2199
+ /**
2200
+ * Trigger event hook (fire-and-forget, non-blocking)
2201
+ * @private
2202
+ */
2203
+ _triggerHook(event, data) {
2204
+ triggerHook(this.hooks, event, data, this.logger);
2205
+ }
2206
+ };
2207
+
2208
+ // src/enums/escrow.enums.ts
2209
+ var HOLD_STATUS = {
2210
+ PENDING: "pending",
2211
+ HELD: "held",
2212
+ RELEASED: "released",
2213
+ CANCELLED: "cancelled",
2214
+ EXPIRED: "expired",
2215
+ PARTIALLY_RELEASED: "partially_released"
2216
+ };
2217
+ var HOLD_STATUS_VALUES = Object.values(HOLD_STATUS);
2218
+ var RELEASE_REASON = {
2219
+ PAYMENT_VERIFIED: "payment_verified",
2220
+ MANUAL_RELEASE: "manual_release",
2221
+ AUTO_RELEASE: "auto_release",
2222
+ DISPUTE_RESOLVED: "dispute_resolved"
2223
+ };
2224
+ var RELEASE_REASON_VALUES = Object.values(RELEASE_REASON);
2225
+ var HOLD_REASON = {
2226
+ PAYMENT_VERIFICATION: "payment_verification",
2227
+ FRAUD_CHECK: "fraud_check",
2228
+ MANUAL_REVIEW: "manual_review",
2229
+ DISPUTE: "dispute",
2230
+ COMPLIANCE: "compliance"
2231
+ };
2232
+ var HOLD_REASON_VALUES = Object.values(HOLD_REASON);
2233
+
2234
+ // src/enums/split.enums.ts
2235
+ var SPLIT_TYPE = {
2236
+ PLATFORM_COMMISSION: "platform_commission",
2237
+ AFFILIATE_COMMISSION: "affiliate_commission",
2238
+ REFERRAL_COMMISSION: "referral_commission",
2239
+ PARTNER_COMMISSION: "partner_commission",
2240
+ CUSTOM: "custom"
2241
+ };
2242
+ var SPLIT_TYPE_VALUES = Object.values(SPLIT_TYPE);
2243
+ var SPLIT_STATUS = {
2244
+ PENDING: "pending",
2245
+ DUE: "due",
2246
+ PAID: "paid",
2247
+ WAIVED: "waived",
2248
+ CANCELLED: "cancelled"
2249
+ };
2250
+ var SPLIT_STATUS_VALUES = Object.values(SPLIT_STATUS);
2251
+ var PAYOUT_METHOD = {
2252
+ BANK_TRANSFER: "bank_transfer",
2253
+ MOBILE_WALLET: "mobile_wallet",
2254
+ PLATFORM_BALANCE: "platform_balance",
2255
+ CRYPTO: "crypto",
2256
+ CHECK: "check",
2257
+ MANUAL: "manual"
2258
+ };
2259
+ var PAYOUT_METHOD_VALUES = Object.values(PAYOUT_METHOD);
2260
+
2261
+ // src/utils/commission-split.ts
2262
+ function calculateSplits(amount, splitRules = [], gatewayFeeRate = 0) {
2263
+ if (!splitRules || splitRules.length === 0) {
2264
+ return [];
2265
+ }
2266
+ if (amount < 0) {
2267
+ throw new Error("Transaction amount cannot be negative");
2268
+ }
2269
+ if (gatewayFeeRate < 0 || gatewayFeeRate > 1) {
2270
+ throw new Error("Gateway fee rate must be between 0 and 1");
2271
+ }
2272
+ const totalRate = splitRules.reduce((sum, rule) => sum + rule.rate, 0);
2273
+ if (totalRate > 1) {
2274
+ throw new Error(`Total split rate (${totalRate}) cannot exceed 1.0`);
2275
+ }
2276
+ return splitRules.map((rule, index) => {
2277
+ if (rule.rate < 0 || rule.rate > 1) {
2278
+ throw new Error(`Split rate must be between 0 and 1 for split ${index}`);
2279
+ }
2280
+ const grossAmount = Math.round(amount * rule.rate * 100) / 100;
2281
+ const gatewayFeeAmount = index === 0 && gatewayFeeRate > 0 ? Math.round(amount * gatewayFeeRate * 100) / 100 : 0;
2282
+ const netAmount = Math.max(0, Math.round((grossAmount - gatewayFeeAmount) * 100) / 100);
2283
+ return {
2284
+ type: rule.type ?? SPLIT_TYPE.CUSTOM,
2285
+ recipientId: rule.recipientId,
2286
+ recipientType: rule.recipientType,
2287
+ rate: rule.rate,
2288
+ grossAmount,
2289
+ gatewayFeeRate: gatewayFeeAmount > 0 ? gatewayFeeRate : 0,
2290
+ gatewayFeeAmount,
2291
+ netAmount,
2292
+ status: SPLIT_STATUS.PENDING,
2293
+ dueDate: rule.dueDate ?? null,
2294
+ metadata: rule.metadata ?? {}
2295
+ };
2296
+ });
2297
+ }
2298
+ function calculateOrganizationPayout(amount, splits = []) {
2299
+ const totalSplitAmount = splits.reduce((sum, split) => sum + split.grossAmount, 0);
2300
+ return Math.max(0, Math.round((amount - totalSplitAmount) * 100) / 100);
2301
+ }
2302
+ function reverseSplits(originalSplits, originalAmount, refundAmount) {
2303
+ if (!originalSplits || originalSplits.length === 0) {
2304
+ return [];
2305
+ }
2306
+ const refundRatio = refundAmount / originalAmount;
2307
+ return originalSplits.map((split) => ({
2308
+ ...split,
2309
+ grossAmount: Math.round(split.grossAmount * refundRatio * 100) / 100,
2310
+ gatewayFeeAmount: Math.round(split.gatewayFeeAmount * refundRatio * 100) / 100,
2311
+ netAmount: Math.round(split.netAmount * refundRatio * 100) / 100,
2312
+ status: SPLIT_STATUS.WAIVED
2313
+ }));
2314
+ }
2315
+ function calculateCommissionWithSplits(amount, commissionRate, gatewayFeeRate = 0, options = {}) {
2316
+ const { affiliateRate = 0, affiliateId = null, affiliateType = "user" } = options;
2317
+ if (commissionRate <= 0 && affiliateRate <= 0) {
2318
+ return null;
2319
+ }
2320
+ const splitRules = [];
2321
+ if (commissionRate > 0) {
2322
+ splitRules.push({
2323
+ type: SPLIT_TYPE.PLATFORM_COMMISSION,
2324
+ recipientId: "platform",
2325
+ recipientType: "platform",
2326
+ rate: commissionRate
2327
+ });
2328
+ }
2329
+ if (affiliateRate > 0 && affiliateId) {
2330
+ splitRules.push({
2331
+ type: SPLIT_TYPE.AFFILIATE_COMMISSION,
2332
+ recipientId: affiliateId,
2333
+ recipientType: affiliateType,
2334
+ rate: affiliateRate
2335
+ });
2336
+ }
2337
+ const splits = calculateSplits(amount, splitRules, gatewayFeeRate);
2338
+ const platformSplit = splits.find((s) => s.type === SPLIT_TYPE.PLATFORM_COMMISSION);
2339
+ const affiliateSplit = splits.find((s) => s.type === SPLIT_TYPE.AFFILIATE_COMMISSION);
2340
+ return {
2341
+ rate: commissionRate,
2342
+ grossAmount: platformSplit?.grossAmount ?? 0,
2343
+ gatewayFeeRate: platformSplit?.gatewayFeeRate ?? 0,
2344
+ gatewayFeeAmount: platformSplit?.gatewayFeeAmount ?? 0,
2345
+ netAmount: platformSplit?.netAmount ?? 0,
2346
+ status: "pending",
2347
+ ...splits.length > 0 && { splits },
2348
+ ...affiliateSplit && {
2349
+ affiliate: {
2350
+ recipientId: affiliateSplit.recipientId,
2351
+ recipientType: affiliateSplit.recipientType,
2352
+ rate: affiliateSplit.rate,
2353
+ grossAmount: affiliateSplit.grossAmount,
2354
+ netAmount: affiliateSplit.netAmount
2355
+ }
2356
+ }
2357
+ };
2358
+ }
2359
+
2360
+ // src/services/escrow.service.ts
2361
+ var EscrowService = class {
2362
+ models;
2363
+ hooks;
2364
+ logger;
2365
+ constructor(container) {
2366
+ this.models = container.get("models");
2367
+ this.hooks = container.get("hooks");
2368
+ this.logger = container.get("logger");
2369
+ }
2370
+ /**
2371
+ * Hold funds in escrow
2372
+ *
2373
+ * @param transactionId - Transaction to hold
2374
+ * @param options - Hold options
2375
+ * @returns Updated transaction
2376
+ */
2377
+ async hold(transactionId, options = {}) {
2378
+ const {
2379
+ reason = HOLD_REASON.PAYMENT_VERIFICATION,
2380
+ holdUntil = null,
2381
+ metadata = {}
2382
+ } = options;
2383
+ const TransactionModel = this.models.Transaction;
2384
+ const transaction = await TransactionModel.findById(transactionId);
2385
+ if (!transaction) {
2386
+ throw new TransactionNotFoundError(transactionId);
2387
+ }
2388
+ if (transaction.status !== TRANSACTION_STATUS.VERIFIED) {
2389
+ throw new Error(`Cannot hold transaction with status: ${transaction.status}. Must be verified.`);
2390
+ }
2391
+ transaction.hold = {
2392
+ status: HOLD_STATUS.HELD,
2393
+ heldAmount: transaction.amount,
2394
+ releasedAmount: 0,
2395
+ reason,
2396
+ heldAt: /* @__PURE__ */ new Date(),
2397
+ ...holdUntil && { holdUntil },
2398
+ releases: [],
2399
+ metadata
2400
+ };
2401
+ await transaction.save();
2402
+ this._triggerHook("escrow.held", {
2403
+ transaction,
2404
+ heldAmount: transaction.amount,
2405
+ reason
2406
+ });
2407
+ return transaction;
2408
+ }
2409
+ /**
2410
+ * Release funds from escrow to recipient
2411
+ *
2412
+ * @param transactionId - Transaction to release
2413
+ * @param options - Release options
2414
+ * @returns { transaction, releaseTransaction }
2415
+ */
2416
+ async release(transactionId, options) {
2417
+ const {
2418
+ amount = null,
2419
+ recipientId,
2420
+ recipientType = "organization",
2421
+ reason = RELEASE_REASON.PAYMENT_VERIFIED,
2422
+ releasedBy = null,
2423
+ createTransaction = true,
2424
+ metadata = {}
2425
+ } = options;
2426
+ const TransactionModel = this.models.Transaction;
2427
+ const transaction = await TransactionModel.findById(transactionId);
2428
+ if (!transaction) {
2429
+ throw new TransactionNotFoundError(transactionId);
2430
+ }
2431
+ if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
2432
+ throw new Error(`Transaction is not in held status. Current: ${transaction.hold?.status ?? "none"}`);
2433
+ }
2434
+ if (!recipientId) {
2435
+ throw new Error("recipientId is required for release");
2436
+ }
2437
+ const releaseAmount = amount ?? transaction.hold.heldAmount - transaction.hold.releasedAmount;
2438
+ const availableAmount = transaction.hold.heldAmount - transaction.hold.releasedAmount;
2439
+ if (releaseAmount > availableAmount) {
2440
+ throw new Error(`Release amount (${releaseAmount}) exceeds available held amount (${availableAmount})`);
2441
+ }
2442
+ const releaseRecord = {
2443
+ amount: releaseAmount,
2444
+ recipientId,
2445
+ recipientType,
2446
+ releasedAt: /* @__PURE__ */ new Date(),
2447
+ releasedBy,
2448
+ reason,
2449
+ metadata
2450
+ };
2451
+ transaction.hold.releases.push(releaseRecord);
2452
+ transaction.hold.releasedAmount += releaseAmount;
2453
+ const isFullRelease = transaction.hold.releasedAmount >= transaction.hold.heldAmount;
2454
+ const isPartialRelease = transaction.hold.releasedAmount > 0 && transaction.hold.releasedAmount < transaction.hold.heldAmount;
2455
+ if (isFullRelease) {
2456
+ transaction.hold.status = HOLD_STATUS.RELEASED;
2457
+ transaction.hold.releasedAt = /* @__PURE__ */ new Date();
2458
+ transaction.status = TRANSACTION_STATUS.COMPLETED;
2459
+ } else if (isPartialRelease) {
2460
+ transaction.hold.status = HOLD_STATUS.PARTIALLY_RELEASED;
2461
+ }
2462
+ await transaction.save();
2463
+ let releaseTransaction = null;
2464
+ if (createTransaction) {
2465
+ releaseTransaction = await TransactionModel.create({
2466
+ organizationId: transaction.organizationId,
2467
+ customerId: recipientId,
2468
+ amount: releaseAmount,
2469
+ currency: transaction.currency,
2470
+ category: transaction.category,
2471
+ type: TRANSACTION_TYPE.INCOME,
2472
+ method: transaction.method,
2473
+ status: TRANSACTION_STATUS.COMPLETED,
2474
+ gateway: transaction.gateway,
2475
+ referenceId: transaction.referenceId,
2476
+ referenceModel: transaction.referenceModel,
2477
+ metadata: {
2478
+ ...metadata,
2479
+ isRelease: true,
2480
+ heldTransactionId: transaction._id.toString(),
2481
+ releaseReason: reason,
2482
+ recipientType
2483
+ },
2484
+ idempotencyKey: `release_${transaction._id}_${Date.now()}`
2485
+ });
2486
+ }
2487
+ this._triggerHook("escrow.released", {
2488
+ transaction,
2489
+ releaseTransaction,
2490
+ releaseAmount,
2491
+ recipientId,
2492
+ recipientType,
2493
+ reason,
2494
+ isFullRelease,
2495
+ isPartialRelease
2496
+ });
2497
+ return {
2498
+ transaction,
2499
+ releaseTransaction,
2500
+ releaseAmount,
2501
+ isFullRelease,
2502
+ isPartialRelease
2503
+ };
2504
+ }
2505
+ /**
2506
+ * Cancel hold and release back to customer
2507
+ *
2508
+ * @param transactionId - Transaction to cancel hold
2509
+ * @param options - Cancel options
2510
+ * @returns Updated transaction
2511
+ */
2512
+ async cancel(transactionId, options = {}) {
2513
+ const { reason = "Hold cancelled", metadata = {} } = options;
2514
+ const TransactionModel = this.models.Transaction;
2515
+ const transaction = await TransactionModel.findById(transactionId);
2516
+ if (!transaction) {
2517
+ throw new TransactionNotFoundError(transactionId);
2518
+ }
2519
+ if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
2520
+ throw new Error(`Transaction is not in held status. Current: ${transaction.hold?.status ?? "none"}`);
2521
+ }
2522
+ transaction.hold.status = HOLD_STATUS.CANCELLED;
2523
+ transaction.hold.cancelledAt = /* @__PURE__ */ new Date();
2524
+ transaction.hold.metadata = {
2525
+ ...transaction.hold.metadata,
2526
+ ...metadata,
2527
+ cancelReason: reason
2528
+ };
2529
+ transaction.status = TRANSACTION_STATUS.CANCELLED;
2530
+ await transaction.save();
2531
+ this._triggerHook("escrow.cancelled", {
2532
+ transaction,
2533
+ reason
2534
+ });
2535
+ return transaction;
2536
+ }
2537
+ /**
2538
+ * Split payment to multiple recipients
2539
+ * Deducts splits from held amount and releases remainder to organization
2540
+ *
2541
+ * @param transactionId - Transaction to split
2542
+ * @param splitRules - Split configuration
2543
+ * @returns { transaction, splitTransactions, organizationTransaction }
2544
+ */
2545
+ async split(transactionId, splitRules = []) {
2546
+ const TransactionModel = this.models.Transaction;
2547
+ const transaction = await TransactionModel.findById(transactionId);
2548
+ if (!transaction) {
2549
+ throw new TransactionNotFoundError(transactionId);
2550
+ }
2551
+ if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
2552
+ throw new Error(`Transaction must be held before splitting. Current: ${transaction.hold?.status ?? "none"}`);
2553
+ }
2554
+ if (!splitRules || splitRules.length === 0) {
2555
+ throw new Error("splitRules cannot be empty");
2556
+ }
2557
+ const splits = calculateSplits(
2558
+ transaction.amount,
2559
+ splitRules,
2560
+ transaction.commission?.gatewayFeeRate ?? 0
2561
+ );
2562
+ transaction.splits = splits;
2563
+ await transaction.save();
2564
+ const splitTransactions = [];
2565
+ for (const split of splits) {
2566
+ const splitTransaction = await TransactionModel.create({
2567
+ organizationId: transaction.organizationId,
2568
+ customerId: split.recipientId,
2569
+ amount: split.netAmount,
2570
+ currency: transaction.currency,
2571
+ category: split.type,
2572
+ type: TRANSACTION_TYPE.EXPENSE,
2573
+ method: transaction.method,
2574
+ status: TRANSACTION_STATUS.COMPLETED,
2575
+ gateway: transaction.gateway,
2576
+ referenceId: transaction.referenceId,
2577
+ referenceModel: transaction.referenceModel,
2578
+ metadata: {
2579
+ isSplit: true,
2580
+ splitType: split.type,
2581
+ recipientType: split.recipientType,
2582
+ originalTransactionId: transaction._id.toString(),
2583
+ grossAmount: split.grossAmount,
2584
+ gatewayFeeAmount: split.gatewayFeeAmount
2585
+ },
2586
+ idempotencyKey: `split_${transaction._id}_${split.recipientId}_${Date.now()}`
2587
+ });
2588
+ split.payoutTransactionId = splitTransaction._id.toString();
2589
+ split.status = SPLIT_STATUS.PAID;
2590
+ split.paidDate = /* @__PURE__ */ new Date();
2591
+ splitTransactions.push(splitTransaction);
2592
+ }
2593
+ await transaction.save();
2594
+ const organizationPayout = calculateOrganizationPayout(transaction.amount, splits);
2595
+ const organizationTransaction = await this.release(transactionId, {
2596
+ amount: organizationPayout,
2597
+ recipientId: transaction.organizationId?.toString() ?? "",
2598
+ recipientType: "organization",
2599
+ reason: RELEASE_REASON.PAYMENT_VERIFIED,
2600
+ createTransaction: true,
2601
+ metadata: {
2602
+ afterSplits: true,
2603
+ totalSplits: splits.length,
2604
+ totalSplitAmount: transaction.amount - organizationPayout
2605
+ }
2606
+ });
2607
+ this._triggerHook("escrow.split", {
2608
+ transaction,
2609
+ splits,
2610
+ splitTransactions,
2611
+ organizationTransaction: organizationTransaction.releaseTransaction,
2612
+ organizationPayout
2613
+ });
2614
+ return {
2615
+ transaction,
2616
+ splits,
2617
+ splitTransactions,
2618
+ organizationTransaction: organizationTransaction.releaseTransaction,
2619
+ organizationPayout
2620
+ };
2621
+ }
2622
+ /**
2623
+ * Get escrow status
2624
+ *
2625
+ * @param transactionId - Transaction ID
2626
+ * @returns Escrow status
2627
+ */
2628
+ async getStatus(transactionId) {
2629
+ const TransactionModel = this.models.Transaction;
2630
+ const transaction = await TransactionModel.findById(transactionId);
2631
+ if (!transaction) {
2632
+ throw new TransactionNotFoundError(transactionId);
2633
+ }
2634
+ return {
2635
+ transaction,
2636
+ hold: transaction.hold ?? null,
2637
+ splits: transaction.splits ?? [],
2638
+ hasHold: !!transaction.hold,
2639
+ hasSplits: transaction.splits ? transaction.splits.length > 0 : false
2640
+ };
2641
+ }
2642
+ _triggerHook(event, data) {
2643
+ triggerHook(this.hooks, event, data, this.logger);
2644
+ }
2645
+ };
2646
+
2647
+ // src/providers/base.ts
2648
+ var PaymentIntent = class {
2649
+ id;
2650
+ sessionId;
2651
+ paymentIntentId;
2652
+ provider;
2653
+ status;
2654
+ amount;
2655
+ currency;
2656
+ metadata;
2657
+ clientSecret;
2658
+ paymentUrl;
2659
+ instructions;
2660
+ raw;
2661
+ constructor(data) {
2662
+ this.id = data.id;
2663
+ this.sessionId = data.sessionId ?? null;
2664
+ this.paymentIntentId = data.paymentIntentId ?? null;
2665
+ this.provider = data.provider;
2666
+ this.status = data.status;
2667
+ this.amount = data.amount;
2668
+ this.currency = data.currency ?? "BDT";
2669
+ this.metadata = data.metadata ?? {};
2670
+ this.clientSecret = data.clientSecret;
2671
+ this.paymentUrl = data.paymentUrl;
2672
+ this.instructions = data.instructions;
2673
+ this.raw = data.raw;
2674
+ }
2675
+ };
2676
+ var PaymentResult = class {
2677
+ id;
2678
+ provider;
2679
+ status;
2680
+ amount;
2681
+ currency;
2682
+ paidAt;
2683
+ metadata;
2684
+ raw;
2685
+ constructor(data) {
2686
+ this.id = data.id;
2687
+ this.provider = data.provider;
2688
+ this.status = data.status;
2689
+ this.amount = data.amount;
2690
+ this.currency = data.currency ?? "BDT";
2691
+ this.paidAt = data.paidAt;
2692
+ this.metadata = data.metadata ?? {};
2693
+ this.raw = data.raw;
2694
+ }
2695
+ };
2696
+ var RefundResult = class {
2697
+ id;
2698
+ provider;
2699
+ status;
2700
+ amount;
2701
+ currency;
2702
+ refundedAt;
2703
+ reason;
2704
+ metadata;
2705
+ raw;
2706
+ constructor(data) {
2707
+ this.id = data.id;
2708
+ this.provider = data.provider;
2709
+ this.status = data.status;
2710
+ this.amount = data.amount;
2711
+ this.currency = data.currency ?? "BDT";
2712
+ this.refundedAt = data.refundedAt;
2713
+ this.reason = data.reason;
2714
+ this.metadata = data.metadata ?? {};
2715
+ this.raw = data.raw;
2716
+ }
2717
+ };
2718
+ var WebhookEvent = class {
2719
+ id;
2720
+ provider;
2721
+ type;
2722
+ data;
2723
+ createdAt;
2724
+ raw;
2725
+ constructor(data) {
2726
+ this.id = data.id;
2727
+ this.provider = data.provider;
2728
+ this.type = data.type;
2729
+ this.data = data.data;
2730
+ this.createdAt = data.createdAt;
2731
+ this.raw = data.raw;
2732
+ }
2733
+ };
2734
+ var PaymentProvider = class {
2735
+ config;
2736
+ name;
2737
+ constructor(config = {}) {
2738
+ this.config = config;
2739
+ this.name = "base";
2740
+ }
2741
+ /**
2742
+ * Verify webhook signature (optional)
2743
+ * @param payload - Webhook payload
2744
+ * @param signature - Webhook signature
2745
+ * @returns boolean
2746
+ */
2747
+ verifyWebhookSignature(_payload, _signature) {
2748
+ return true;
2749
+ }
2750
+ /**
2751
+ * Get provider capabilities
2752
+ * @returns ProviderCapabilities
2753
+ */
2754
+ getCapabilities() {
2755
+ return {
2756
+ supportsWebhooks: false,
2757
+ supportsRefunds: false,
2758
+ supportsPartialRefunds: false,
2759
+ requiresManualVerification: true
2760
+ };
2761
+ }
2762
+ };
2763
+
2764
+ // src/core/revenue.ts
2765
+ var Revenue = class {
2766
+ // ============ CORE ============
2767
+ _container;
2768
+ _events;
2769
+ _plugins;
2770
+ _idempotency;
2771
+ _circuitBreaker;
2772
+ _options;
2773
+ _logger;
2774
+ _providers;
2775
+ _config;
2776
+ // ============ SERVICES ============
2777
+ /** Monetization service - purchases, subscriptions, free items */
2778
+ monetization;
2779
+ /** Payment service - verify, refund, webhooks */
2780
+ payments;
2781
+ /** Transaction service - query, update transactions */
2782
+ transactions;
2783
+ /** Escrow service - hold, release, splits */
2784
+ escrow;
2785
+ constructor(container, events, plugins, options, providers, config) {
2786
+ this._container = container;
2787
+ this._events = events;
2788
+ this._plugins = plugins;
2789
+ this._options = options;
2790
+ this._logger = options.logger;
2791
+ this._providers = providers;
2792
+ this._config = config;
2793
+ this._idempotency = createIdempotencyManager({
2794
+ ttl: options.idempotencyTtl
2795
+ });
2796
+ if (options.circuitBreaker) {
2797
+ this._circuitBreaker = createCircuitBreaker();
2798
+ }
2799
+ container.singleton("events", events);
2800
+ container.singleton("plugins", plugins);
2801
+ container.singleton("idempotency", this._idempotency);
2802
+ container.singleton("logger", this._logger);
2803
+ this.monetization = new MonetizationService(container);
2804
+ this.payments = new PaymentService(container);
2805
+ this.transactions = new TransactionService(container);
2806
+ this.escrow = new EscrowService(container);
2807
+ Object.freeze(this._providers);
2808
+ Object.freeze(this._config);
2809
+ }
2810
+ // ============ STATIC FACTORY ============
2811
+ /**
2812
+ * Create a new Revenue builder
2813
+ *
2814
+ * @example
2815
+ * ```typescript
2816
+ * const revenue = Revenue
2817
+ * .create({ defaultCurrency: 'BDT' })
2818
+ * .withModels({ Transaction, Subscription })
2819
+ * .withProvider('manual', new ManualProvider())
2820
+ * .build();
2821
+ * ```
2822
+ */
2823
+ static create(options = {}) {
2824
+ return new RevenueBuilder(options);
2825
+ }
2826
+ // ============ ACCESSORS ============
2827
+ /** DI container (for advanced usage) */
2828
+ get container() {
2829
+ return this._container;
2830
+ }
2831
+ /** Event bus */
2832
+ get events() {
2833
+ return this._events;
2834
+ }
2835
+ /** Registered providers (frozen) */
2836
+ get providers() {
2837
+ return this._providers;
2838
+ }
2839
+ /** Configuration (frozen) */
2840
+ get config() {
2841
+ return this._config;
2842
+ }
2843
+ /** Default currency */
2844
+ get defaultCurrency() {
2845
+ return this._options.defaultCurrency;
2846
+ }
2847
+ /** Current environment */
2848
+ get environment() {
2849
+ return this._options.environment;
2850
+ }
2851
+ // ============ PROVIDER METHODS ============
2852
+ /**
2853
+ * Get a provider by name
2854
+ */
2855
+ getProvider(name) {
2856
+ const provider = this._providers[name];
2857
+ if (!provider) {
2858
+ throw new ConfigurationError(
2859
+ `Provider "${name}" not found. Available: ${Object.keys(this._providers).join(", ")}`
2860
+ );
2861
+ }
2862
+ return provider;
2863
+ }
2864
+ /**
2865
+ * Get all provider names
2866
+ */
2867
+ getProviderNames() {
2868
+ return Object.keys(this._providers);
2869
+ }
2870
+ /**
2871
+ * Check if provider exists
2872
+ */
2873
+ hasProvider(name) {
2874
+ return name in this._providers;
2875
+ }
2876
+ // ============ EVENT SYSTEM ============
2877
+ /**
2878
+ * Subscribe to events
2879
+ *
2880
+ * @example
2881
+ * ```typescript
2882
+ * revenue.on('payment.succeeded', (event) => {
2883
+ * console.log('Payment:', event.transactionId);
2884
+ * });
2885
+ * ```
2886
+ */
2887
+ on = (event, handler) => {
2888
+ return this._events.on(event, handler);
2889
+ };
2890
+ /**
2891
+ * Subscribe once
2892
+ */
2893
+ once = (event, handler) => {
2894
+ return this._events.once(event, handler);
2895
+ };
2896
+ /**
2897
+ * Unsubscribe
2898
+ */
2899
+ off = (event, handler) => {
2900
+ this._events.off(event, handler);
2901
+ };
2902
+ /**
2903
+ * Emit an event
2904
+ */
2905
+ emit = (event, payload) => {
2906
+ this._events.emit(event, payload);
2907
+ };
2908
+ // ============ RESILIENCE ============
2909
+ /**
2910
+ * Execute operation with retry and idempotency
2911
+ */
2912
+ async execute(operation, options = {}) {
2913
+ const { idempotencyKey, params, useRetry = true, useCircuitBreaker = true } = options;
2914
+ const idempotentOp = async () => {
2915
+ if (idempotencyKey) {
2916
+ const result = await this._idempotency.execute(idempotencyKey, params, operation);
2917
+ if (!result.ok) throw result.error;
2918
+ return result.value;
2919
+ }
2920
+ return operation();
2921
+ };
2922
+ const resilientOp = async () => {
2923
+ if (useCircuitBreaker && this._circuitBreaker) {
2924
+ return this._circuitBreaker.execute(idempotentOp);
2925
+ }
2926
+ return idempotentOp();
2927
+ };
2928
+ if (useRetry && this._options.retry) {
2929
+ return tryCatch(() => retry(resilientOp, this._options.retry));
2930
+ }
2931
+ return tryCatch(resilientOp);
2932
+ }
2933
+ /**
2934
+ * Create plugin context (for advanced usage)
2935
+ */
2936
+ createContext(meta = {}) {
2937
+ return {
2938
+ events: this._events,
2939
+ logger: this._logger,
2940
+ get: (key) => this._container.get(key),
2941
+ storage: /* @__PURE__ */ new Map(),
2942
+ meta: {
2943
+ ...meta,
2944
+ requestId: nanoid(12),
2945
+ timestamp: /* @__PURE__ */ new Date()
2946
+ }
2947
+ };
2948
+ }
2949
+ /**
2950
+ * Destroy instance and cleanup
2951
+ */
2952
+ async destroy() {
2953
+ await this._plugins.destroy();
2954
+ this._events.clear();
2955
+ }
2956
+ };
2957
+ var RevenueBuilder = class {
2958
+ options;
2959
+ models = null;
2960
+ providers = {};
2961
+ plugins = [];
2962
+ hooks = {};
2963
+ categoryMappings = {};
2964
+ constructor(options = {}) {
2965
+ this.options = options;
2966
+ }
2967
+ /**
2968
+ * Register models (required)
2969
+ *
2970
+ * @example
2971
+ * ```typescript
2972
+ * .withModels({
2973
+ * Transaction: TransactionModel,
2974
+ * Subscription: SubscriptionModel,
2975
+ * })
2976
+ * ```
2977
+ */
2978
+ withModels(models) {
2979
+ this.models = models;
2980
+ return this;
2981
+ }
2982
+ /**
2983
+ * Register a single model
2984
+ */
2985
+ withModel(name, model) {
2986
+ if (!this.models) {
2987
+ this.models = { Transaction: model };
2988
+ }
2989
+ this.models[name] = model;
2990
+ return this;
2991
+ }
2992
+ /**
2993
+ * Register a payment provider
2994
+ *
2995
+ * @example
2996
+ * ```typescript
2997
+ * .withProvider('stripe', new StripeProvider({ apiKey: '...' }))
2998
+ * .withProvider('manual', new ManualProvider())
2999
+ * ```
3000
+ */
3001
+ withProvider(name, provider) {
3002
+ this.providers[name] = provider;
3003
+ return this;
3004
+ }
3005
+ /**
3006
+ * Register multiple providers at once
3007
+ */
3008
+ withProviders(providers) {
3009
+ this.providers = { ...this.providers, ...providers };
3010
+ return this;
3011
+ }
3012
+ /**
3013
+ * Register a plugin
3014
+ *
3015
+ * @example
3016
+ * ```typescript
3017
+ * .withPlugin(loggingPlugin())
3018
+ * .withPlugin(auditPlugin({ store: saveToDb }))
3019
+ * ```
3020
+ */
3021
+ withPlugin(plugin) {
3022
+ this.plugins.push(plugin);
3023
+ return this;
3024
+ }
3025
+ /**
3026
+ * Register multiple plugins
3027
+ */
3028
+ withPlugins(plugins) {
3029
+ this.plugins.push(...plugins);
3030
+ return this;
3031
+ }
3032
+ /**
3033
+ * Register event hooks (for backward compatibility)
3034
+ *
3035
+ * @example
3036
+ * ```typescript
3037
+ * .withHooks({
3038
+ * onPaymentVerified: async (tx) => { ... },
3039
+ * onSubscriptionRenewed: async (sub) => { ... },
3040
+ * })
3041
+ * ```
3042
+ */
3043
+ withHooks(hooks) {
3044
+ this.hooks = { ...this.hooks, ...hooks };
3045
+ return this;
3046
+ }
3047
+ /**
3048
+ * Set retry configuration
3049
+ *
3050
+ * @example
3051
+ * ```typescript
3052
+ * .withRetry({ maxAttempts: 5, baseDelay: 2000 })
3053
+ * ```
3054
+ */
3055
+ withRetry(config) {
3056
+ this.options.retry = config;
3057
+ return this;
3058
+ }
3059
+ /**
3060
+ * Enable circuit breaker
3061
+ */
3062
+ withCircuitBreaker(enabled = true) {
3063
+ this.options.circuitBreaker = enabled;
3064
+ return this;
3065
+ }
3066
+ /**
3067
+ * Set custom logger
3068
+ */
3069
+ withLogger(logger2) {
3070
+ this.options.logger = logger2;
3071
+ return this;
3072
+ }
3073
+ /**
3074
+ * Set environment
3075
+ */
3076
+ forEnvironment(env) {
3077
+ this.options.environment = env;
3078
+ return this;
3079
+ }
3080
+ /**
3081
+ * Enable debug mode
3082
+ */
3083
+ withDebug(enabled = true) {
3084
+ this.options.debug = enabled;
3085
+ return this;
3086
+ }
3087
+ /**
3088
+ * Set commission rate (0-100)
3089
+ */
3090
+ withCommission(rate, gatewayFeeRate = 0) {
3091
+ this.options.commissionRate = rate;
3092
+ this.options.gatewayFeeRate = gatewayFeeRate;
3093
+ return this;
3094
+ }
3095
+ /**
3096
+ * Set category mappings (entity → category)
3097
+ *
3098
+ * @example
3099
+ * ```typescript
3100
+ * .withCategoryMappings({
3101
+ * PlatformSubscription: 'platform_subscription',
3102
+ * CourseEnrollment: 'course_enrollment',
3103
+ * ProductOrder: 'product_order',
3104
+ * })
3105
+ * ```
3106
+ */
3107
+ withCategoryMappings(mappings) {
3108
+ this.categoryMappings = { ...this.categoryMappings, ...mappings };
3109
+ return this;
3110
+ }
3111
+ /**
3112
+ * Build the Revenue instance
3113
+ */
3114
+ build() {
3115
+ if (!this.models) {
3116
+ throw new ConfigurationError(
3117
+ "Models are required. Use .withModels({ Transaction, Subscription })"
3118
+ );
3119
+ }
3120
+ if (!this.models.Transaction) {
3121
+ throw new ConfigurationError(
3122
+ "Transaction model is required in models configuration"
3123
+ );
3124
+ }
3125
+ if (Object.keys(this.providers).length === 0) {
3126
+ throw new ConfigurationError(
3127
+ "At least one provider is required. Use .withProvider(name, provider)"
3128
+ );
3129
+ }
3130
+ const container = new Container();
3131
+ const defaultLogger = {
3132
+ debug: (msg, data) => this.options.debug && console.debug(`[Revenue] ${msg}`, data ?? ""),
3133
+ info: (msg, data) => console.info(`[Revenue] ${msg}`, data ?? ""),
3134
+ warn: (msg, data) => console.warn(`[Revenue] ${msg}`, data ?? ""),
3135
+ error: (msg, data) => console.error(`[Revenue] ${msg}`, data ?? "")
3136
+ };
3137
+ const resolvedOptions = {
3138
+ defaultCurrency: this.options.defaultCurrency ?? "USD",
3139
+ environment: this.options.environment ?? "development",
3140
+ debug: this.options.debug ?? false,
3141
+ retry: this.options.retry ?? { maxAttempts: 3 },
3142
+ idempotencyTtl: this.options.idempotencyTtl ?? 24 * 60 * 60 * 1e3,
3143
+ circuitBreaker: this.options.circuitBreaker ?? false,
3144
+ logger: this.options.logger ?? defaultLogger,
3145
+ commissionRate: this.options.commissionRate ?? 0,
3146
+ gatewayFeeRate: this.options.gatewayFeeRate ?? 0
3147
+ };
3148
+ const config = {
3149
+ defaultCurrency: resolvedOptions.defaultCurrency,
3150
+ commissionRate: resolvedOptions.commissionRate,
3151
+ gatewayFeeRate: resolvedOptions.gatewayFeeRate,
3152
+ categoryMappings: this.categoryMappings
3153
+ };
3154
+ container.singleton("models", this.models);
3155
+ container.singleton("providers", this.providers);
3156
+ container.singleton("hooks", this.hooks);
3157
+ container.singleton("config", config);
3158
+ const events = createEventBus();
3159
+ const pluginManager = new PluginManager();
3160
+ for (const plugin of this.plugins) {
3161
+ pluginManager.register(plugin);
3162
+ }
3163
+ const revenue = new Revenue(
3164
+ container,
3165
+ events,
3166
+ pluginManager,
3167
+ resolvedOptions,
3168
+ this.providers,
3169
+ config
3170
+ );
3171
+ const ctx = revenue.createContext();
3172
+ pluginManager.init(ctx).catch((err2) => {
3173
+ resolvedOptions.logger.error("Failed to initialize plugins", err2);
3174
+ });
3175
+ return revenue;
3176
+ }
3177
+ };
3178
+ function createRevenue(config) {
3179
+ let builder = Revenue.create(config.options);
3180
+ builder = builder.withModels(config.models);
3181
+ builder = builder.withProviders(config.providers);
3182
+ if (config.plugins) {
3183
+ builder = builder.withPlugins(config.plugins);
3184
+ }
3185
+ if (config.hooks) {
3186
+ builder = builder.withHooks(config.hooks);
3187
+ }
3188
+ return builder.build();
3189
+ }
3190
+
3191
+ // src/utils/money.ts
3192
+ var CURRENCIES = {
3193
+ USD: { code: "USD", decimals: 2, symbol: "$", name: "US Dollar" },
3194
+ EUR: { code: "EUR", decimals: 2, symbol: "\u20AC", name: "Euro" },
3195
+ GBP: { code: "GBP", decimals: 2, symbol: "\xA3", name: "British Pound" },
3196
+ BDT: { code: "BDT", decimals: 2, symbol: "\u09F3", name: "Bangladeshi Taka" },
3197
+ INR: { code: "INR", decimals: 2, symbol: "\u20B9", name: "Indian Rupee" },
3198
+ JPY: { code: "JPY", decimals: 0, symbol: "\xA5", name: "Japanese Yen" },
3199
+ CNY: { code: "CNY", decimals: 2, symbol: "\xA5", name: "Chinese Yuan" },
3200
+ AED: { code: "AED", decimals: 2, symbol: "\u062F.\u0625", name: "UAE Dirham" },
3201
+ SAR: { code: "SAR", decimals: 2, symbol: "\uFDFC", name: "Saudi Riyal" },
3202
+ SGD: { code: "SGD", decimals: 2, symbol: "S$", name: "Singapore Dollar" },
3203
+ AUD: { code: "AUD", decimals: 2, symbol: "A$", name: "Australian Dollar" },
3204
+ CAD: { code: "CAD", decimals: 2, symbol: "C$", name: "Canadian Dollar" }
3205
+ };
3206
+ var Money = class _Money {
3207
+ amount;
3208
+ currency;
3209
+ constructor(amount, currency) {
3210
+ if (!Number.isInteger(amount)) {
3211
+ throw new Error(`Money amount must be integer (smallest unit). Got: ${amount}`);
3212
+ }
3213
+ this.amount = amount;
3214
+ this.currency = currency.toUpperCase();
3215
+ }
3216
+ // ============ FACTORY METHODS ============
3217
+ /**
3218
+ * Create money from smallest unit (cents, paisa)
3219
+ * @example Money.cents(1999, 'USD') // $19.99
3220
+ */
3221
+ static cents(amount, currency = "USD") {
3222
+ return new _Money(Math.round(amount), currency);
3223
+ }
3224
+ /**
3225
+ * Create money from major unit (dollars, taka)
3226
+ * @example Money.of(19.99, 'USD') // $19.99 (stored as 1999 cents)
3227
+ */
3228
+ static of(amount, currency = "USD") {
3229
+ const config = CURRENCIES[currency.toUpperCase()] ?? { decimals: 2 };
3230
+ const multiplier = Math.pow(10, config.decimals);
3231
+ return new _Money(Math.round(amount * multiplier), currency);
3232
+ }
3233
+ /**
3234
+ * Create zero money
3235
+ */
3236
+ static zero(currency = "USD") {
3237
+ return new _Money(0, currency);
3238
+ }
3239
+ // ============ SHORTHAND FACTORIES ============
3240
+ static usd(cents) {
3241
+ return _Money.cents(cents, "USD");
3242
+ }
3243
+ static eur(cents) {
3244
+ return _Money.cents(cents, "EUR");
3245
+ }
3246
+ static gbp(pence) {
3247
+ return _Money.cents(pence, "GBP");
3248
+ }
3249
+ static bdt(paisa) {
3250
+ return _Money.cents(paisa, "BDT");
3251
+ }
3252
+ static inr(paisa) {
3253
+ return _Money.cents(paisa, "INR");
3254
+ }
3255
+ static jpy(yen) {
3256
+ return _Money.cents(yen, "JPY");
3257
+ }
3258
+ // ============ ARITHMETIC ============
3259
+ /**
3260
+ * Add two money values (must be same currency)
3261
+ */
3262
+ add(other) {
3263
+ this.assertSameCurrency(other);
3264
+ return new _Money(this.amount + other.amount, this.currency);
3265
+ }
3266
+ /**
3267
+ * Subtract money (must be same currency)
3268
+ */
3269
+ subtract(other) {
3270
+ this.assertSameCurrency(other);
3271
+ return new _Money(this.amount - other.amount, this.currency);
3272
+ }
3273
+ /**
3274
+ * Multiply by a factor (rounds to nearest integer)
3275
+ */
3276
+ multiply(factor) {
3277
+ return new _Money(Math.round(this.amount * factor), this.currency);
3278
+ }
3279
+ /**
3280
+ * Divide by a divisor (rounds to nearest integer)
3281
+ */
3282
+ divide(divisor) {
3283
+ if (divisor === 0) throw new Error("Cannot divide by zero");
3284
+ return new _Money(Math.round(this.amount / divisor), this.currency);
3285
+ }
3286
+ /**
3287
+ * Calculate percentage
3288
+ * @example money.percentage(10) // 10% of money
3289
+ */
3290
+ percentage(percent) {
3291
+ return this.multiply(percent / 100);
3292
+ }
3293
+ /**
3294
+ * Allocate money among recipients (handles rounding)
3295
+ * @example Money.usd(100).allocate([1, 1, 1]) // [34, 33, 33] cents
3296
+ */
3297
+ allocate(ratios) {
3298
+ const total = ratios.reduce((a, b) => a + b, 0);
3299
+ if (total === 0) throw new Error("Ratios must sum to more than 0");
3300
+ let remainder = this.amount;
3301
+ const results = [];
3302
+ for (let i = 0; i < ratios.length; i++) {
3303
+ const share = Math.floor(this.amount * ratios[i] / total);
3304
+ results.push(new _Money(share, this.currency));
3305
+ remainder -= share;
3306
+ }
3307
+ for (let i = 0; i < remainder; i++) {
3308
+ results[i] = new _Money(results[i].amount + 1, this.currency);
3309
+ }
3310
+ return results;
3311
+ }
3312
+ /**
3313
+ * Split equally among n recipients
3314
+ */
3315
+ split(parts) {
3316
+ return this.allocate(Array(parts).fill(1));
3317
+ }
3318
+ // ============ COMPARISON ============
3319
+ isZero() {
3320
+ return this.amount === 0;
3321
+ }
3322
+ isPositive() {
3323
+ return this.amount > 0;
3324
+ }
3325
+ isNegative() {
3326
+ return this.amount < 0;
3327
+ }
3328
+ equals(other) {
3329
+ return this.amount === other.amount && this.currency === other.currency;
3330
+ }
3331
+ greaterThan(other) {
3332
+ this.assertSameCurrency(other);
3333
+ return this.amount > other.amount;
3334
+ }
3335
+ lessThan(other) {
3336
+ this.assertSameCurrency(other);
3337
+ return this.amount < other.amount;
3338
+ }
3339
+ greaterThanOrEqual(other) {
3340
+ return this.greaterThan(other) || this.equals(other);
3341
+ }
3342
+ lessThanOrEqual(other) {
3343
+ return this.lessThan(other) || this.equals(other);
3344
+ }
3345
+ // ============ FORMATTING ============
3346
+ /**
3347
+ * Get amount in major unit (dollars, taka)
3348
+ */
3349
+ toUnit() {
3350
+ const config = CURRENCIES[this.currency] ?? { decimals: 2 };
3351
+ return this.amount / Math.pow(10, config.decimals);
3352
+ }
3353
+ /**
3354
+ * Format for display
3355
+ * @example Money.usd(1999).format() // "$19.99"
3356
+ */
3357
+ format(locale = "en-US") {
3358
+ return new Intl.NumberFormat(locale, {
3359
+ style: "currency",
3360
+ currency: this.currency
3361
+ }).format(this.toUnit());
3362
+ }
3363
+ /**
3364
+ * Format without currency symbol
3365
+ */
3366
+ formatAmount(locale = "en-US") {
3367
+ const config = CURRENCIES[this.currency] ?? { decimals: 2 };
3368
+ return new Intl.NumberFormat(locale, {
3369
+ minimumFractionDigits: config.decimals,
3370
+ maximumFractionDigits: config.decimals
3371
+ }).format(this.toUnit());
3372
+ }
3373
+ /**
3374
+ * Convert to JSON-serializable object
3375
+ */
3376
+ toJSON() {
3377
+ return { amount: this.amount, currency: this.currency };
3378
+ }
3379
+ /**
3380
+ * Create from JSON
3381
+ */
3382
+ static fromJSON(json) {
3383
+ return new _Money(json.amount, json.currency);
3384
+ }
3385
+ toString() {
3386
+ return `${this.currency} ${this.amount}`;
3387
+ }
3388
+ // ============ HELPERS ============
3389
+ assertSameCurrency(other) {
3390
+ if (this.currency !== other.currency) {
3391
+ throw new Error(
3392
+ `Currency mismatch: ${this.currency} vs ${other.currency}. Convert first.`
3393
+ );
3394
+ }
3395
+ }
3396
+ };
3397
+ function toSmallestUnit(amount, currency = "USD") {
3398
+ return Money.of(amount, currency).amount;
3399
+ }
3400
+ function fromSmallestUnit(amount, currency = "USD") {
3401
+ return Money.cents(amount, currency).toUnit();
3402
+ }
3403
+ var ObjectIdSchema = z.string().regex(
3404
+ /^[a-fA-F0-9]{24}$/,
3405
+ "Invalid ObjectId format"
3406
+ );
3407
+ var CurrencySchema = z.string().length(3, "Currency must be 3 characters").transform((val) => val.toUpperCase()).default("USD");
3408
+ var MoneyAmountSchema = z.number().int("Amount must be integer (smallest unit)").nonnegative("Amount cannot be negative");
3409
+ var MoneySchema = z.object({
3410
+ amount: MoneyAmountSchema,
3411
+ currency: z.string().length(3).default("USD")
3412
+ });
3413
+ var EmailSchema = z.string().email();
3414
+ var IdempotencyKeySchema = z.string().min(1).max(255).optional();
3415
+ var MetadataSchema = z.record(z.string(), z.unknown()).optional().default({});
3416
+ var CreatePaymentSchema = z.object({
3417
+ /** Amount in smallest currency unit (cents) */
3418
+ amount: MoneyAmountSchema,
3419
+ /** ISO 4217 currency code */
3420
+ currency: z.string().length(3).default("USD"),
3421
+ /** Customer identifier */
3422
+ customerId: z.string().min(1, "Customer ID is required"),
3423
+ /** Organization/merchant identifier */
3424
+ organizationId: z.string().min(1, "Organization ID is required"),
3425
+ /** Payment provider to use */
3426
+ provider: z.string().min(1, "Provider is required"),
3427
+ /** Idempotency key for safe retries */
3428
+ idempotencyKey: IdempotencyKeySchema,
3429
+ /** Description of the payment */
3430
+ description: z.string().optional(),
3431
+ /** Additional metadata */
3432
+ metadata: MetadataSchema,
3433
+ /** Success redirect URL */
3434
+ successUrl: z.string().url().optional(),
3435
+ /** Cancel redirect URL */
3436
+ cancelUrl: z.string().url().optional()
3437
+ });
3438
+ var VerifyPaymentSchema = z.object({
3439
+ /** Transaction ID or payment intent ID */
3440
+ id: z.string().min(1),
3441
+ /** Provider name (optional, auto-detected) */
3442
+ provider: z.string().optional(),
3443
+ /** Additional verification data */
3444
+ data: z.record(z.string(), z.unknown()).optional()
3445
+ });
3446
+ var RefundSchema = z.object({
3447
+ /** Transaction ID to refund */
3448
+ transactionId: z.string().min(1),
3449
+ /** Amount to refund (optional, full refund if not provided) */
3450
+ amount: MoneyAmountSchema.optional(),
3451
+ /** Reason for refund */
3452
+ reason: z.string().optional(),
3453
+ /** Idempotency key */
3454
+ idempotencyKey: IdempotencyKeySchema,
3455
+ /** Additional metadata */
3456
+ metadata: MetadataSchema
3457
+ });
3458
+ var SubscriptionStatusSchema = z.enum([
3459
+ "pending",
3460
+ "active",
3461
+ "paused",
3462
+ "cancelled",
3463
+ "expired",
3464
+ "past_due"
3465
+ ]);
3466
+ var IntervalSchema = z.enum([
3467
+ "day",
3468
+ "week",
3469
+ "month",
3470
+ "year",
3471
+ "one_time"
3472
+ ]);
3473
+ var CreateSubscriptionSchema = z.object({
3474
+ /** Customer ID */
3475
+ customerId: z.string().min(1),
3476
+ /** Organization ID */
3477
+ organizationId: z.string().min(1),
3478
+ /** Plan identifier */
3479
+ planKey: z.string().min(1),
3480
+ /** Amount per period (smallest unit) */
3481
+ amount: MoneyAmountSchema,
3482
+ /** Currency */
3483
+ currency: z.string().length(3).default("USD"),
3484
+ /** Billing interval */
3485
+ interval: IntervalSchema.default("month"),
3486
+ /** Interval count (e.g., 2 for bi-monthly) */
3487
+ intervalCount: z.number().int().positive().default(1),
3488
+ /** Payment provider */
3489
+ provider: z.string().min(1),
3490
+ /** Reference to external entity */
3491
+ referenceId: z.string().optional(),
3492
+ /** Reference model name */
3493
+ referenceModel: z.string().optional(),
3494
+ /** Idempotency key */
3495
+ idempotencyKey: IdempotencyKeySchema,
3496
+ /** Metadata */
3497
+ metadata: MetadataSchema,
3498
+ /** Trial period in days */
3499
+ trialDays: z.number().int().nonnegative().optional()
3500
+ });
3501
+ var CancelSubscriptionSchema = z.object({
3502
+ /** Subscription ID */
3503
+ subscriptionId: z.string().min(1),
3504
+ /** Cancel immediately or at period end */
3505
+ immediate: z.boolean().default(false),
3506
+ /** Cancellation reason */
3507
+ reason: z.string().optional()
3508
+ });
3509
+ var MonetizationTypeSchema = z.enum([
3510
+ "purchase",
3511
+ "subscription",
3512
+ "free"
3513
+ ]);
3514
+ var CreateMonetizationSchema = z.object({
3515
+ /** Type of monetization */
3516
+ type: MonetizationTypeSchema.default("purchase"),
3517
+ /** Amount (smallest unit) - required for purchase/subscription */
3518
+ amount: MoneyAmountSchema.optional(),
3519
+ /** Currency */
3520
+ currency: z.string().length(3).default("USD"),
3521
+ /** Customer ID */
3522
+ customerId: z.string().min(1),
3523
+ /** Organization ID */
3524
+ organizationId: z.string().min(1),
3525
+ /** Payment provider */
3526
+ provider: z.string().min(1),
3527
+ /** Plan key for categorization */
3528
+ planKey: z.string().optional(),
3529
+ /** Reference ID */
3530
+ referenceId: z.string().optional(),
3531
+ /** Reference model */
3532
+ referenceModel: z.string().optional(),
3533
+ /** Idempotency key */
3534
+ idempotencyKey: IdempotencyKeySchema,
3535
+ /** Metadata */
3536
+ metadata: MetadataSchema,
3537
+ /** Subscription-specific: interval */
3538
+ interval: IntervalSchema.optional(),
3539
+ /** Subscription-specific: trial days */
3540
+ trialDays: z.number().int().nonnegative().optional()
3541
+ }).refine(
3542
+ (data) => {
3543
+ if (data.type !== "free" && !data.amount) {
3544
+ return false;
3545
+ }
3546
+ return true;
3547
+ },
3548
+ { message: "Amount is required for non-free monetization types" }
3549
+ );
3550
+ var SplitRecipientSchema = z.object({
3551
+ /** Recipient ID */
3552
+ recipientId: z.string().min(1),
3553
+ /** Recipient type (user, organization, etc.) */
3554
+ recipientType: z.string().default("user"),
3555
+ /** Percentage of net amount (0-100) */
3556
+ percentage: z.number().min(0).max(100),
3557
+ /** Role description */
3558
+ role: z.string().optional()
3559
+ });
3560
+ var CommissionConfigSchema = z.object({
3561
+ /** Platform commission rate (0-100) */
3562
+ platformRate: z.number().min(0).max(100).default(0),
3563
+ /** Gateway fee rate (0-100) */
3564
+ gatewayFeeRate: z.number().min(0).max(100).default(0),
3565
+ /** Fixed gateway fee (smallest unit) */
3566
+ gatewayFixedFee: MoneyAmountSchema.default(0),
3567
+ /** Split recipients */
3568
+ splits: z.array(SplitRecipientSchema).optional(),
3569
+ /** Affiliate configuration */
3570
+ affiliate: z.object({
3571
+ recipientId: z.string(),
3572
+ recipientType: z.string().default("user"),
3573
+ rate: z.number().min(0).max(100)
3574
+ }).optional()
3575
+ });
3576
+ var HoldStatusSchema = z.enum([
3577
+ "none",
3578
+ "held",
3579
+ "partial_release",
3580
+ "released",
3581
+ "cancelled"
3582
+ ]);
3583
+ var CreateHoldSchema = z.object({
3584
+ /** Transaction ID */
3585
+ transactionId: z.string().min(1),
3586
+ /** Hold amount (optional, defaults to full transaction amount) */
3587
+ amount: MoneyAmountSchema.optional(),
3588
+ /** Hold until date */
3589
+ holdUntil: z.date().optional(),
3590
+ /** Reason for hold */
3591
+ reason: z.string().optional()
3592
+ });
3593
+ var ReleaseHoldSchema = z.object({
3594
+ /** Transaction ID */
3595
+ transactionId: z.string().min(1),
3596
+ /** Amount to release (optional, full release if not provided) */
3597
+ amount: MoneyAmountSchema.optional(),
3598
+ /** Recipient ID */
3599
+ recipientId: z.string().min(1),
3600
+ /** Recipient type */
3601
+ recipientType: z.string().default("user"),
3602
+ /** Release notes */
3603
+ notes: z.string().optional()
3604
+ });
3605
+ var ProviderConfigSchema = z.record(z.string(), z.unknown());
3606
+ var RetryConfigSchema = z.object({
3607
+ /** Maximum retry attempts */
3608
+ maxAttempts: z.number().int().positive().default(3),
3609
+ /** Base delay in ms */
3610
+ baseDelay: z.number().positive().default(1e3),
3611
+ /** Maximum delay in ms */
3612
+ maxDelay: z.number().positive().default(3e4),
3613
+ /** Backoff multiplier */
3614
+ backoffMultiplier: z.number().positive().default(2),
3615
+ /** Jitter factor (0-1) */
3616
+ jitter: z.number().min(0).max(1).default(0.1)
3617
+ });
3618
+ var RevenueConfigSchema = z.object({
3619
+ /** Default currency */
3620
+ defaultCurrency: z.string().length(3).default("USD"),
3621
+ /** Commission configuration */
3622
+ commission: CommissionConfigSchema.optional(),
3623
+ /** Retry configuration */
3624
+ retry: RetryConfigSchema.optional(),
3625
+ /** Enable debug logging */
3626
+ debug: z.boolean().default(false),
3627
+ /** Environment */
3628
+ environment: z.enum(["development", "staging", "production"]).default("development")
3629
+ });
3630
+ function validate(schema, data) {
3631
+ return schema.parse(data);
3632
+ }
3633
+ function safeValidate(schema, data) {
3634
+ const result = schema.safeParse(data);
3635
+ if (result.success) {
3636
+ return { success: true, data: result.data };
3637
+ }
3638
+ return { success: false, error: result.error };
3639
+ }
3640
+ function formatZodError(error) {
3641
+ return error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
3642
+ }
3643
+
3644
+ // src/enums/payment.enums.ts
3645
+ var PAYMENT_STATUS = {
3646
+ PENDING: "pending",
3647
+ VERIFIED: "verified",
3648
+ FAILED: "failed",
3649
+ REFUNDED: "refunded",
3650
+ CANCELLED: "cancelled"
3651
+ };
3652
+ var PAYMENT_STATUS_VALUES = Object.values(PAYMENT_STATUS);
3653
+ var PAYMENT_GATEWAY_TYPE = {
3654
+ MANUAL: "manual",
3655
+ STRIPE: "stripe",
3656
+ SSLCOMMERZ: "sslcommerz"
3657
+ };
3658
+ var PAYMENT_GATEWAY_TYPE_VALUES = Object.values(PAYMENT_GATEWAY_TYPE);
3659
+ var GATEWAY_TYPES = PAYMENT_GATEWAY_TYPE;
3660
+ var GATEWAY_TYPE_VALUES = PAYMENT_GATEWAY_TYPE_VALUES;
3661
+
3662
+ // src/enums/subscription.enums.ts
3663
+ var SUBSCRIPTION_STATUS = {
3664
+ ACTIVE: "active",
3665
+ PAUSED: "paused",
3666
+ CANCELLED: "cancelled",
3667
+ EXPIRED: "expired",
3668
+ PENDING: "pending",
3669
+ INACTIVE: "inactive"
3670
+ };
3671
+ var SUBSCRIPTION_STATUS_VALUES = Object.values(SUBSCRIPTION_STATUS);
3672
+ var PLAN_KEYS = {
3673
+ MONTHLY: "monthly",
3674
+ QUARTERLY: "quarterly",
3675
+ YEARLY: "yearly"
3676
+ };
3677
+ var PLAN_KEY_VALUES = Object.values(PLAN_KEYS);
3678
+ var baseMetadataSchema = new Schema(
3679
+ {
3680
+ // Flexible key-value metadata
3681
+ },
3682
+ { _id: false, strict: false }
3683
+ );
3684
+ var referenceSchema = {
3685
+ referenceId: {
3686
+ type: Schema.Types.ObjectId,
3687
+ refPath: "referenceModel",
3688
+ index: true
3689
+ },
3690
+ referenceModel: {
3691
+ type: String,
3692
+ enum: ["Subscription", "Order", "Membership", "Booking", "Invoice"]
3693
+ }
3694
+ };
3695
+ var gatewaySchema = new Schema(
3696
+ {
3697
+ type: {
3698
+ type: String,
3699
+ required: true,
3700
+ index: true
3701
+ },
3702
+ sessionId: {
3703
+ type: String,
3704
+ sparse: true,
3705
+ index: true
3706
+ },
3707
+ paymentIntentId: {
3708
+ type: String,
3709
+ sparse: true,
3710
+ index: true
3711
+ },
3712
+ provider: {
3713
+ type: String
3714
+ },
3715
+ metadata: {
3716
+ type: Schema.Types.Mixed,
3717
+ default: {}
3718
+ },
3719
+ verificationData: {
3720
+ type: Schema.Types.Mixed
3721
+ }
3722
+ },
3723
+ { _id: false }
3724
+ );
3725
+ var currentPaymentSchema = new Schema(
3726
+ {
3727
+ transactionId: {
3728
+ type: Schema.Types.ObjectId,
3729
+ ref: "Transaction",
3730
+ index: true
3731
+ },
3732
+ amount: {
3733
+ type: Number,
3734
+ min: 0
3735
+ },
3736
+ status: {
3737
+ type: String,
3738
+ enum: PAYMENT_STATUS_VALUES,
3739
+ default: "pending",
3740
+ index: true
3741
+ },
3742
+ method: {
3743
+ type: String
3744
+ // Users define payment methods in their transaction model
3745
+ },
3746
+ reference: {
3747
+ type: String,
3748
+ trim: true
3749
+ },
3750
+ verifiedAt: {
3751
+ type: Date
3752
+ },
3753
+ verifiedBy: {
3754
+ type: Schema.Types.ObjectId,
3755
+ ref: "User"
3756
+ },
3757
+ // Refund tracking fields
3758
+ refundedAmount: {
3759
+ type: Number,
3760
+ default: 0,
3761
+ min: 0
3762
+ },
3763
+ refundedAt: {
3764
+ type: Date
3765
+ }
3766
+ },
3767
+ { _id: false }
3768
+ );
3769
+ var paymentSummarySchema = new Schema(
3770
+ {
3771
+ totalPayments: {
3772
+ type: Number,
3773
+ default: 0,
3774
+ min: 0
3775
+ },
3776
+ totalAmountPaid: {
3777
+ type: Number,
3778
+ default: 0,
3779
+ min: 0
3780
+ },
3781
+ lastPaymentDate: {
3782
+ type: Date
3783
+ },
3784
+ lastPaymentAmount: {
3785
+ type: Number,
3786
+ min: 0
3787
+ }
3788
+ },
3789
+ { _id: false }
3790
+ );
3791
+ var paymentDetailsSchema = new Schema(
3792
+ {
3793
+ provider: { type: String },
3794
+ walletNumber: { type: String },
3795
+ walletType: { type: String },
3796
+ trxId: { type: String },
3797
+ bankName: { type: String },
3798
+ accountNumber: { type: String },
3799
+ accountName: { type: String },
3800
+ proofUrl: { type: String }
3801
+ },
3802
+ { _id: false }
3803
+ );
3804
+ var tenantSnapshotSchema = new Schema(
3805
+ {
3806
+ paymentInstructions: { type: String },
3807
+ bkashNumber: { type: String },
3808
+ nagadNumber: { type: String },
3809
+ bankAccount: { type: String }
3810
+ },
3811
+ { _id: false }
3812
+ );
3813
+ var commissionSchema = new Schema(
3814
+ {
3815
+ // Commission rate (e.g., 0.10 for 10%)
3816
+ rate: {
3817
+ type: Number,
3818
+ min: 0,
3819
+ max: 1
3820
+ },
3821
+ // Gross commission amount (before gateway fees)
3822
+ grossAmount: {
3823
+ type: Number,
3824
+ min: 0
3825
+ },
3826
+ // Gateway fee rate (e.g., 0.029 for 2.9%)
3827
+ gatewayFeeRate: {
3828
+ type: Number,
3829
+ min: 0,
3830
+ max: 1
3831
+ },
3832
+ // Gateway fee amount deducted from commission
3833
+ gatewayFeeAmount: {
3834
+ type: Number,
3835
+ min: 0
3836
+ },
3837
+ // Net commission (grossAmount - gatewayFeeAmount)
3838
+ netAmount: {
3839
+ type: Number,
3840
+ min: 0
3841
+ },
3842
+ // Commission status
3843
+ status: {
3844
+ type: String,
3845
+ enum: ["pending", "paid", "waived", "reversed"],
3846
+ default: "pending"
3847
+ },
3848
+ // For affiliate tracking
3849
+ affiliate: {
3850
+ recipientId: String,
3851
+ recipientType: {
3852
+ type: String,
3853
+ enum: ["user", "organization", "partner"]
3854
+ },
3855
+ rate: Number,
3856
+ grossAmount: Number,
3857
+ netAmount: Number
3858
+ },
3859
+ // For multi-party splits
3860
+ splits: [
3861
+ {
3862
+ type: String,
3863
+ recipientId: String,
3864
+ rate: Number,
3865
+ grossAmount: Number,
3866
+ netAmount: Number
3867
+ }
3868
+ ]
3869
+ },
3870
+ { _id: false }
3871
+ );
3872
+ var planSchema = new Schema(
3873
+ {
3874
+ key: {
3875
+ type: String,
3876
+ enum: PLAN_KEY_VALUES,
3877
+ required: true
3878
+ },
3879
+ name: {
3880
+ type: String,
3881
+ required: true
3882
+ },
3883
+ description: {
3884
+ type: String
3885
+ },
3886
+ amount: {
3887
+ type: Number,
3888
+ required: true,
3889
+ min: 0
3890
+ },
3891
+ currency: {
3892
+ type: String,
3893
+ default: "BDT"
3894
+ },
3895
+ interval: {
3896
+ type: String,
3897
+ enum: ["day", "week", "month", "year"],
3898
+ default: "month"
3899
+ },
3900
+ intervalCount: {
3901
+ type: Number,
3902
+ default: 1,
3903
+ min: 1
3904
+ },
3905
+ features: [
3906
+ {
3907
+ type: String
3908
+ }
3909
+ ],
3910
+ metadata: {
3911
+ type: Schema.Types.Mixed,
3912
+ default: {}
3913
+ },
3914
+ isActive: {
3915
+ type: Boolean,
3916
+ default: true
3917
+ }
3918
+ },
3919
+ { _id: false }
3920
+ );
3921
+ var subscriptionInfoSchema = new Schema(
3922
+ {
3923
+ planKey: {
3924
+ type: String,
3925
+ enum: PLAN_KEY_VALUES,
3926
+ required: true
3927
+ },
3928
+ status: {
3929
+ type: String,
3930
+ enum: SUBSCRIPTION_STATUS_VALUES,
3931
+ default: "pending",
3932
+ index: true
3933
+ },
3934
+ isActive: {
3935
+ type: Boolean,
3936
+ default: false,
3937
+ index: true
3938
+ },
3939
+ startDate: {
3940
+ type: Date
3941
+ },
3942
+ endDate: {
3943
+ type: Date,
3944
+ index: true
3945
+ },
3946
+ canceledAt: {
3947
+ type: Date
3948
+ },
3949
+ cancelAt: {
3950
+ type: Date
3951
+ },
3952
+ pausedAt: {
3953
+ type: Date
3954
+ },
3955
+ lastPaymentDate: {
3956
+ type: Date
3957
+ },
3958
+ lastPaymentAmount: {
3959
+ type: Number
3960
+ },
3961
+ renewalCount: {
3962
+ type: Number,
3963
+ default: 0
3964
+ }
3965
+ },
3966
+ { _id: false }
3967
+ );
3968
+
3969
+ // src/schemas/escrow/hold.schema.ts
3970
+ var holdSchema = {
3971
+ status: {
3972
+ type: String,
3973
+ enum: HOLD_STATUS_VALUES,
3974
+ default: HOLD_STATUS.PENDING,
3975
+ index: true
3976
+ },
3977
+ heldAmount: {
3978
+ type: Number,
3979
+ required: false
3980
+ },
3981
+ releasedAmount: {
3982
+ type: Number,
3983
+ default: 0
3984
+ },
3985
+ reason: {
3986
+ type: String,
3987
+ enum: HOLD_REASON_VALUES,
3988
+ required: false
3989
+ },
3990
+ holdUntil: {
3991
+ type: Date,
3992
+ required: false
3993
+ },
3994
+ heldAt: Date,
3995
+ releasedAt: Date,
3996
+ cancelledAt: Date,
3997
+ releases: [
3998
+ {
3999
+ amount: Number,
4000
+ recipientId: String,
4001
+ recipientType: String,
4002
+ releasedAt: Date,
4003
+ releasedBy: String,
4004
+ reason: String,
4005
+ metadata: Object
4006
+ }
4007
+ ],
4008
+ metadata: {
4009
+ type: Object,
4010
+ default: {}
4011
+ }
4012
+ };
4013
+ var splitSchema = new Schema(
4014
+ {
4015
+ type: {
4016
+ type: String,
4017
+ enum: SPLIT_TYPE_VALUES,
4018
+ required: true
4019
+ },
4020
+ recipientId: {
4021
+ type: String,
4022
+ required: true,
4023
+ index: true
4024
+ },
4025
+ recipientType: {
4026
+ type: String,
4027
+ enum: ["platform", "organization", "user", "affiliate", "partner"],
4028
+ required: true
4029
+ },
4030
+ rate: {
4031
+ type: Number,
4032
+ required: true,
4033
+ min: 0,
4034
+ max: 1
4035
+ },
4036
+ grossAmount: {
4037
+ type: Number,
4038
+ required: true,
4039
+ min: 0
4040
+ },
4041
+ gatewayFeeRate: {
4042
+ type: Number,
4043
+ default: 0,
4044
+ min: 0,
4045
+ max: 1
4046
+ },
4047
+ gatewayFeeAmount: {
4048
+ type: Number,
4049
+ default: 0,
4050
+ min: 0
4051
+ },
4052
+ netAmount: {
4053
+ type: Number,
4054
+ required: true,
4055
+ min: 0
4056
+ },
4057
+ status: {
4058
+ type: String,
4059
+ enum: SPLIT_STATUS_VALUES,
4060
+ default: SPLIT_STATUS.PENDING,
4061
+ index: true
4062
+ },
4063
+ dueDate: {
4064
+ type: Date
4065
+ },
4066
+ paidDate: {
4067
+ type: Date
4068
+ },
4069
+ payoutMethod: {
4070
+ type: String,
4071
+ enum: PAYOUT_METHOD_VALUES
4072
+ },
4073
+ payoutTransactionId: {
4074
+ type: String
4075
+ },
4076
+ metadata: {
4077
+ type: Schema.Types.Mixed,
4078
+ default: {}
4079
+ }
4080
+ },
4081
+ { _id: false }
4082
+ );
4083
+
4084
+ // src/utils/transaction-type.ts
4085
+ var TRANSACTION_MANAGEMENT_TYPE = {
4086
+ MONETIZATION: "monetization",
4087
+ // Library-managed (subscriptions, purchases)
4088
+ MANUAL: "manual"
4089
+ // Admin-managed (expenses, income, adjustments)
4090
+ };
4091
+ var DEFAULT_MONETIZATION_CATEGORIES = [
4092
+ "subscription",
4093
+ "purchase"
4094
+ ];
4095
+ function isMonetizationCategory(category, additionalCategories = []) {
4096
+ const allCategories = [...DEFAULT_MONETIZATION_CATEGORIES, ...additionalCategories];
4097
+ return allCategories.includes(category);
4098
+ }
4099
+ function isMonetizationTransaction(transaction, options = {}) {
4100
+ const {
4101
+ targetModels = ["Subscription", "Membership"],
4102
+ additionalCategories = []
4103
+ } = options;
4104
+ if (transaction.referenceModel && targetModels.includes(transaction.referenceModel)) {
4105
+ return true;
4106
+ }
4107
+ if (transaction.category) {
4108
+ return isMonetizationCategory(transaction.category, additionalCategories);
4109
+ }
4110
+ return false;
4111
+ }
4112
+ function isManualTransaction(transaction, options = {}) {
4113
+ return !isMonetizationTransaction(transaction, options);
4114
+ }
4115
+ function getTransactionType(transaction, options = {}) {
4116
+ return isMonetizationTransaction(transaction, options) ? TRANSACTION_MANAGEMENT_TYPE.MONETIZATION : TRANSACTION_MANAGEMENT_TYPE.MANUAL;
4117
+ }
4118
+ var PROTECTED_MONETIZATION_FIELDS = [
4119
+ "status",
4120
+ "amount",
4121
+ "platformCommission",
4122
+ "netAmount",
4123
+ "verifiedAt",
4124
+ "verifiedBy",
4125
+ "gateway",
4126
+ "webhook",
4127
+ "metadata.commission",
4128
+ "metadata.gateway",
4129
+ "type",
4130
+ "category",
4131
+ "referenceModel",
4132
+ "referenceId"
4133
+ ];
4134
+ var EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION = [
4135
+ "reference",
4136
+ "paymentDetails",
4137
+ "notes"
4138
+ ];
4139
+ var MANUAL_TRANSACTION_CREATE_FIELDS = [
4140
+ "organizationId",
4141
+ "type",
4142
+ "category",
4143
+ "amount",
4144
+ "method",
4145
+ "reference",
4146
+ "paymentDetails",
4147
+ "notes",
4148
+ "date",
4149
+ // Transaction date (can be backdated)
4150
+ "description"
4151
+ ];
4152
+ var MANUAL_TRANSACTION_UPDATE_FIELDS = [
4153
+ "amount",
4154
+ "method",
4155
+ "reference",
4156
+ "paymentDetails",
4157
+ "notes",
4158
+ "date",
4159
+ "description"
4160
+ ];
4161
+ function getAllowedUpdateFields(transaction, options = {}) {
4162
+ const type = getTransactionType(transaction, options);
4163
+ if (type === TRANSACTION_MANAGEMENT_TYPE.MONETIZATION) {
4164
+ if (transaction.status === "pending") {
4165
+ return EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION;
4166
+ }
4167
+ return [];
4168
+ }
4169
+ if (transaction.status === "verified" || transaction.status === "completed") {
4170
+ return ["notes"];
4171
+ }
4172
+ return MANUAL_TRANSACTION_UPDATE_FIELDS;
4173
+ }
4174
+ function validateFieldUpdate(transaction, fieldName, options = {}) {
4175
+ const allowedFields = getAllowedUpdateFields(transaction, options);
4176
+ if (allowedFields.includes(fieldName)) {
4177
+ return { allowed: true };
4178
+ }
4179
+ const type = getTransactionType(transaction, options);
4180
+ if (type === TRANSACTION_MANAGEMENT_TYPE.MONETIZATION) {
4181
+ if (PROTECTED_MONETIZATION_FIELDS.includes(fieldName)) {
4182
+ return {
4183
+ allowed: false,
4184
+ reason: `Field "${fieldName}" is protected for monetization transactions. Updates must go through payment flow.`
4185
+ };
4186
+ }
4187
+ }
4188
+ return {
4189
+ allowed: false,
4190
+ reason: `Field "${fieldName}" cannot be updated for ${transaction.status} transactions.`
4191
+ };
4192
+ }
4193
+ function canSelfVerify(transaction, options = {}) {
4194
+ const type = getTransactionType(transaction, options);
4195
+ if (type === TRANSACTION_MANAGEMENT_TYPE.MANUAL) {
4196
+ return transaction.status === "pending";
4197
+ }
4198
+ return false;
4199
+ }
4200
+
4201
+ // src/utils/logger.ts
4202
+ var _logger = console;
4203
+ function setLogger(customLogger) {
4204
+ _logger = customLogger;
4205
+ }
4206
+ var logger = {
4207
+ info: (...args) => {
4208
+ (_logger.info ?? _logger.log)?.call(_logger, ...args);
4209
+ },
4210
+ warn: (...args) => {
4211
+ (_logger.warn ?? _logger.log)?.call(_logger, "WARN:", ...args);
4212
+ },
4213
+ error: (...args) => {
4214
+ (_logger.error ?? _logger.log)?.call(_logger, "ERROR:", ...args);
4215
+ },
4216
+ debug: (...args) => {
4217
+ (_logger.debug ?? _logger.log)?.call(_logger, "DEBUG:", ...args);
4218
+ }
4219
+ };
4220
+
4221
+ // src/utils/subscription/period.ts
4222
+ function addDuration(startDate, duration, unit = "days") {
4223
+ const date2 = new Date(startDate);
4224
+ switch (unit) {
4225
+ case "months":
4226
+ case "month":
4227
+ date2.setMonth(date2.getMonth() + duration);
4228
+ return date2;
4229
+ case "years":
4230
+ case "year":
4231
+ date2.setFullYear(date2.getFullYear() + duration);
4232
+ return date2;
4233
+ case "weeks":
4234
+ case "week":
4235
+ date2.setDate(date2.getDate() + duration * 7);
4236
+ return date2;
4237
+ case "days":
4238
+ case "day":
4239
+ default:
4240
+ date2.setDate(date2.getDate() + duration);
4241
+ return date2;
4242
+ }
4243
+ }
4244
+ function calculatePeriodRange(params) {
4245
+ const {
4246
+ currentEndDate = null,
4247
+ startDate = null,
4248
+ duration,
4249
+ unit = "days",
4250
+ now = /* @__PURE__ */ new Date()
4251
+ } = params;
4252
+ let periodStart;
4253
+ if (startDate) {
4254
+ periodStart = new Date(startDate);
4255
+ } else if (currentEndDate) {
4256
+ const end = new Date(currentEndDate);
4257
+ periodStart = end > now ? end : now;
4258
+ } else {
4259
+ periodStart = now;
4260
+ }
4261
+ const periodEnd = addDuration(periodStart, duration, unit);
4262
+ return { startDate: periodStart, endDate: periodEnd };
4263
+ }
4264
+ function calculateProratedAmount(params) {
4265
+ const {
4266
+ amountPaid,
4267
+ startDate,
4268
+ endDate,
4269
+ asOfDate = /* @__PURE__ */ new Date(),
4270
+ precision = 2
4271
+ } = params;
4272
+ if (!amountPaid || amountPaid <= 0) return 0;
4273
+ const start = new Date(startDate);
4274
+ const end = new Date(endDate);
4275
+ const asOf = new Date(asOfDate);
4276
+ const totalMs = end.getTime() - start.getTime();
4277
+ if (totalMs <= 0) return 0;
4278
+ const remainingMs = Math.max(0, end.getTime() - asOf.getTime());
4279
+ if (remainingMs <= 0) return 0;
4280
+ const ratio = remainingMs / totalMs;
4281
+ const amount = amountPaid * ratio;
4282
+ const factor = 10 ** precision;
4283
+ return Math.round(amount * factor) / factor;
4284
+ }
4285
+ function resolveIntervalToDuration(interval = "month", intervalCount = 1) {
4286
+ const normalized = (interval || "month").toLowerCase();
4287
+ const count = Number(intervalCount) > 0 ? Number(intervalCount) : 1;
4288
+ switch (normalized) {
4289
+ case "year":
4290
+ case "years":
4291
+ return { duration: count, unit: "years" };
4292
+ case "week":
4293
+ case "weeks":
4294
+ return { duration: count, unit: "weeks" };
4295
+ case "quarter":
4296
+ case "quarters":
4297
+ return { duration: count * 3, unit: "months" };
4298
+ case "day":
4299
+ case "days":
4300
+ return { duration: count, unit: "days" };
4301
+ case "month":
4302
+ case "months":
4303
+ default:
4304
+ return { duration: count, unit: "months" };
4305
+ }
4306
+ }
4307
+
4308
+ // src/utils/subscription/actions.ts
4309
+ function isSubscriptionActive(subscription) {
4310
+ if (!subscription) return false;
4311
+ if (!subscription.isActive) return false;
4312
+ if (subscription.endDate) {
4313
+ const now = /* @__PURE__ */ new Date();
4314
+ const endDate = new Date(subscription.endDate);
4315
+ if (endDate < now) return false;
4316
+ }
4317
+ return true;
4318
+ }
4319
+ function canRenewSubscription(entity) {
4320
+ if (!entity?.subscription) return false;
4321
+ return isSubscriptionActive(entity.subscription);
4322
+ }
4323
+ function canCancelSubscription(entity) {
4324
+ if (!entity?.subscription) return false;
4325
+ if (!isSubscriptionActive(entity.subscription)) return false;
4326
+ return !entity.subscription.canceledAt;
4327
+ }
4328
+ function canPauseSubscription(entity) {
4329
+ if (!entity?.subscription) return false;
4330
+ if (entity.status === SUBSCRIPTION_STATUS.PAUSED) return false;
4331
+ if (entity.status === SUBSCRIPTION_STATUS.CANCELLED) return false;
4332
+ return isSubscriptionActive(entity.subscription);
4333
+ }
4334
+ function canResumeSubscription(entity) {
4335
+ if (!entity?.subscription) return false;
4336
+ return entity.status === SUBSCRIPTION_STATUS.PAUSED;
4337
+ }
4338
+
4339
+ // src/index.ts
4340
+ var index_default = {
4341
+ Revenue,
4342
+ createRevenue,
4343
+ PaymentProvider,
4344
+ RevenueError,
4345
+ Money,
4346
+ Result,
4347
+ EventBus
4348
+ };
4349
+ /**
4350
+ * @classytic/revenue
4351
+ * Enterprise Revenue Management System
4352
+ *
4353
+ * Modern • Type-safe • Resilient • Composable
4354
+ *
4355
+ * @version 1.0.0
4356
+ * @author Classytic
4357
+ * @license MIT
4358
+ */
4359
+
4360
+ export { AlreadyVerifiedError, CancelSubscriptionSchema, CircuitBreaker, CircuitOpenError, CommissionConfigSchema, ConfigurationError, Container, CreateHoldSchema, CreateMonetizationSchema, CreatePaymentSchema, CreateSubscriptionSchema, CurrencySchema, EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION, ERROR_CODES, EmailSchema, EscrowService, EventBus, GATEWAY_TYPES, GATEWAY_TYPE_VALUES, HOLD_REASON, HOLD_REASON_VALUES, HOLD_STATUS, HOLD_STATUS_VALUES, HoldStatusSchema, IdempotencyError, IdempotencyKeySchema, IdempotencyManager, IntervalSchema, InvalidAmountError, InvalidStateTransitionError, LIBRARY_CATEGORIES, LIBRARY_CATEGORY_VALUES, MANUAL_TRANSACTION_CREATE_FIELDS, MANUAL_TRANSACTION_UPDATE_FIELDS, MONETIZATION_TYPES, MONETIZATION_TYPE_VALUES, MemoryIdempotencyStore, MetadataSchema, MissingRequiredFieldError, ModelNotRegisteredError, MonetizationService, MonetizationTypeSchema, Money, MoneyAmountSchema, MoneySchema, NotFoundError, ObjectIdSchema, OperationError, PAYMENT_GATEWAY_TYPE, PAYMENT_GATEWAY_TYPE_VALUES, PAYMENT_STATUS, PAYMENT_STATUS_VALUES, PAYOUT_METHOD, PAYOUT_METHOD_VALUES, PLAN_KEYS, PLAN_KEY_VALUES, PROTECTED_MONETIZATION_FIELDS, PaymentIntent, PaymentIntentCreationError, PaymentProvider, PaymentResult, PaymentService, PaymentVerificationError, PluginManager, ProviderCapabilityError, ProviderConfigSchema, ProviderError, ProviderNotFoundError, RELEASE_REASON, RELEASE_REASON_VALUES, RefundError, RefundNotSupportedError, RefundResult, RefundSchema, ReleaseHoldSchema, Result, RetryConfigSchema, RetryExhaustedError, Revenue, RevenueBuilder, RevenueConfigSchema, RevenueError, SPLIT_STATUS, SPLIT_STATUS_VALUES, SPLIT_TYPE, SPLIT_TYPE_VALUES, SUBSCRIPTION_STATUS, SUBSCRIPTION_STATUS_VALUES, SplitRecipientSchema, StateError, SubscriptionNotActiveError, SubscriptionNotFoundError, SubscriptionStatusSchema, TRANSACTION_MANAGEMENT_TYPE, TRANSACTION_STATUS, TRANSACTION_STATUS_VALUES, TRANSACTION_TYPE, TRANSACTION_TYPE_VALUES, TransactionNotFoundError, TransactionService, ValidationError, VerifyPaymentSchema, WebhookEvent, addDuration, all, auditPlugin, baseMetadataSchema, calculateCommission, calculateCommissionWithSplits, calculateDelay, calculateOrganizationPayout, calculatePeriodRange, calculateProratedAmount, calculateSplits, canCancelSubscription, canPauseSubscription, canRenewSubscription, canResumeSubscription, canSelfVerify, commissionSchema, createCircuitBreaker, createEventBus, createIdempotencyManager, createRevenue, currentPaymentSchema, index_default as default, definePlugin, err, flatMap, formatZodError, fromSmallestUnit, gatewaySchema, getAllowedUpdateFields, getTransactionType, holdSchema, isCategoryValid, isErr, isManualTransaction, isMonetizationTransaction, isOk, isRetryable, isRetryableError, isRevenueError, isSubscriptionActive, logger, loggingPlugin, map, mapErr, match, metricsPlugin, ok, paymentDetailsSchema, paymentSummarySchema, planSchema, referenceSchema, resilientExecute, resolveCategory, resolveIntervalToDuration, retry, retryWithResult, reverseCommission, reverseSplits, safeValidate, setLogger, splitSchema, subscriptionInfoSchema, tenantSnapshotSchema, toSmallestUnit, tryCatch, tryCatchSync, unwrap, unwrapOr, validate, validateFieldUpdate };
4361
+ //# sourceMappingURL=index.js.map
4362
+ //# sourceMappingURL=index.js.map