@classytic/revenue 0.2.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +498 -499
  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 +4353 -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 +894 -0
  19. package/dist/schemas/index.js +524 -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-BPdFZMbU.d.ts +958 -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 -126
  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 -112
  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 -671
  99. package/services/payment.service.js +0 -517
  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
@@ -1,517 +0,0 @@
1
- /**
2
- * Payment Service
3
- * @classytic/revenue
4
- *
5
- * Framework-agnostic payment verification and management service with DI
6
- * Handles payment verification, refunds, and status updates
7
- */
8
-
9
- /**
10
- * @typedef {Object} PaymentVerifyResult
11
- * @property {Object} transaction - Updated transaction
12
- * @property {Object} paymentResult - Payment result from provider
13
- * @property {string} status - Payment status
14
- */
15
-
16
- /**
17
- * @typedef {Object} PaymentRefundResult
18
- * @property {Object} transaction - Original transaction (updated)
19
- * @property {Object} refundTransaction - New refund transaction record
20
- * @property {Object} refundResult - Refund result from provider
21
- * @property {string} status - Transaction status after refund
22
- */
23
-
24
- import {
25
- TransactionNotFoundError,
26
- ProviderNotFoundError,
27
- ProviderError,
28
- AlreadyVerifiedError,
29
- PaymentVerificationError,
30
- RefundNotSupportedError,
31
- RefundError,
32
- ProviderCapabilityError,
33
- ValidationError,
34
- } from '../core/errors.js';
35
- import { triggerHook } from '../utils/hooks.js';
36
- import { reverseCommission } from '../utils/commission.js';
37
- import { TRANSACTION_TYPE } from '../enums/transaction.enums.js';
38
-
39
- /**
40
- * Payment Service
41
- * Uses DI container for all dependencies
42
- */
43
- export class PaymentService {
44
- constructor(container) {
45
- this.container = container;
46
- this.models = container.get('models');
47
- this.providers = container.get('providers');
48
- this.config = container.get('config');
49
- this.hooks = container.get('hooks');
50
- this.logger = container.get('logger');
51
- }
52
-
53
- /**
54
- * Verify a payment
55
- *
56
- * @param {String} paymentIntentId - Payment intent ID or transaction ID
57
- * @param {Object} options - Verification options
58
- * @param {String} options.verifiedBy - User ID who verified (for manual verification)
59
- * @returns {Promise<Object>} { transaction, status }
60
- */
61
- async verify(paymentIntentId, options = {}) {
62
- const { verifiedBy = null } = options;
63
-
64
- const TransactionModel = this.models.Transaction;
65
-
66
- // Find transaction by payment intent ID or transaction ID
67
- let transaction = await TransactionModel.findOne({
68
- 'gateway.paymentIntentId': paymentIntentId,
69
- });
70
-
71
- if (!transaction) {
72
- // Try finding by transaction ID directly
73
- transaction = await TransactionModel.findById(paymentIntentId);
74
- }
75
-
76
- if (!transaction) {
77
- throw new TransactionNotFoundError(paymentIntentId);
78
- }
79
-
80
- if (transaction.status === 'verified' || transaction.status === 'completed') {
81
- throw new AlreadyVerifiedError(transaction._id);
82
- }
83
-
84
- // Get provider for verification
85
- const gatewayType = transaction.gateway?.type || 'manual';
86
- const provider = this.providers[gatewayType];
87
-
88
- if (!provider) {
89
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
90
- }
91
-
92
- // Verify payment with provider
93
- let paymentResult = null;
94
- try {
95
- paymentResult = await provider.verifyPayment(paymentIntentId);
96
- } catch (error) {
97
- this.logger.error('Payment verification failed:', error);
98
-
99
- // Update transaction as failed
100
- transaction.status = 'failed';
101
- transaction.failureReason = error.message;
102
- transaction.metadata = {
103
- ...transaction.metadata,
104
- verificationError: error.message,
105
- failedAt: new Date().toISOString(),
106
- };
107
- await transaction.save();
108
-
109
- // Trigger payment.failed hook
110
- this._triggerHook('payment.failed', {
111
- transaction,
112
- error: error.message,
113
- provider: gatewayType,
114
- paymentIntentId,
115
- });
116
-
117
- throw new PaymentVerificationError(paymentIntentId, error.message);
118
- }
119
-
120
- // Validate amount and currency match
121
- if (paymentResult.amount && paymentResult.amount !== transaction.amount) {
122
- throw new ValidationError(
123
- `Amount mismatch: expected ${transaction.amount}, got ${paymentResult.amount}`,
124
- { expected: transaction.amount, actual: paymentResult.amount }
125
- );
126
- }
127
-
128
- if (paymentResult.currency && paymentResult.currency.toUpperCase() !== transaction.currency.toUpperCase()) {
129
- throw new ValidationError(
130
- `Currency mismatch: expected ${transaction.currency}, got ${paymentResult.currency}`,
131
- { expected: transaction.currency, actual: paymentResult.currency }
132
- );
133
- }
134
-
135
- // Update transaction based on verification result
136
- transaction.status = paymentResult.status === 'succeeded' ? 'verified' : paymentResult.status;
137
- transaction.verifiedAt = paymentResult.paidAt || new Date();
138
- transaction.verifiedBy = verifiedBy;
139
- transaction.gateway = {
140
- ...transaction.gateway,
141
- verificationData: paymentResult.metadata,
142
- };
143
-
144
- await transaction.save();
145
-
146
- // Trigger hook
147
- this._triggerHook('payment.verified', {
148
- transaction,
149
- paymentResult,
150
- verifiedBy,
151
- });
152
-
153
- return {
154
- transaction,
155
- paymentResult,
156
- status: transaction.status,
157
- };
158
- }
159
-
160
- /**
161
- * Get payment status
162
- *
163
- * @param {String} paymentIntentId - Payment intent ID or transaction ID
164
- * @returns {Promise<Object>} { transaction, status }
165
- */
166
- async getStatus(paymentIntentId) {
167
- const TransactionModel = this.models.Transaction;
168
-
169
- // Find transaction
170
- let transaction = await TransactionModel.findOne({
171
- 'gateway.paymentIntentId': paymentIntentId,
172
- });
173
-
174
- if (!transaction) {
175
- transaction = await TransactionModel.findById(paymentIntentId);
176
- }
177
-
178
- if (!transaction) {
179
- throw new TransactionNotFoundError(paymentIntentId);
180
- }
181
-
182
- // Get provider
183
- const gatewayType = transaction.gateway?.type || 'manual';
184
- const provider = this.providers[gatewayType];
185
-
186
- if (!provider) {
187
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
188
- }
189
-
190
- // Get status from provider
191
- let paymentResult = null;
192
- try {
193
- paymentResult = await provider.getStatus(paymentIntentId);
194
- } catch (error) {
195
- this.logger.warn('Failed to get payment status from provider:', error);
196
- // Return transaction status as fallback
197
- return {
198
- transaction,
199
- status: transaction.status,
200
- provider: gatewayType,
201
- };
202
- }
203
-
204
- return {
205
- transaction,
206
- paymentResult,
207
- status: paymentResult.status,
208
- provider: gatewayType,
209
- };
210
- }
211
-
212
- /**
213
- * Refund a payment
214
- *
215
- * @param {String} paymentId - Payment intent ID or transaction ID
216
- * @param {Number} amount - Amount to refund (optional, full refund if not provided)
217
- * @param {Object} options - Refund options
218
- * @param {String} options.reason - Refund reason
219
- * @returns {Promise<Object>} { transaction, refundResult }
220
- */
221
- async refund(paymentId, amount = null, options = {}) {
222
- const { reason = null } = options;
223
-
224
- const TransactionModel = this.models.Transaction;
225
-
226
- // Find transaction
227
- let transaction = await TransactionModel.findOne({
228
- 'gateway.paymentIntentId': paymentId,
229
- });
230
-
231
- if (!transaction) {
232
- transaction = await TransactionModel.findById(paymentId);
233
- }
234
-
235
- if (!transaction) {
236
- throw new TransactionNotFoundError(paymentId);
237
- }
238
-
239
- if (transaction.status !== 'verified' && transaction.status !== 'completed') {
240
- throw new RefundError(transaction._id, 'Only verified/completed transactions can be refunded');
241
- }
242
-
243
- // Get provider
244
- const gatewayType = transaction.gateway?.type || 'manual';
245
- const provider = this.providers[gatewayType];
246
-
247
- if (!provider) {
248
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
249
- }
250
-
251
- // Check if provider supports refunds
252
- const capabilities = provider.getCapabilities();
253
- if (!capabilities.supportsRefunds) {
254
- throw new RefundNotSupportedError(gatewayType);
255
- }
256
-
257
- // Calculate refundable amount
258
- const refundedSoFar = transaction.refundedAmount || 0;
259
- const refundableAmount = transaction.amount - refundedSoFar;
260
- const refundAmount = amount || refundableAmount;
261
-
262
- // Validate refund amount
263
- if (refundAmount <= 0) {
264
- throw new ValidationError(`Refund amount must be positive, got ${refundAmount}`);
265
- }
266
-
267
- if (refundAmount > refundableAmount) {
268
- throw new ValidationError(
269
- `Refund amount (${refundAmount}) exceeds refundable balance (${refundableAmount})`,
270
- { refundAmount, refundableAmount, alreadyRefunded: refundedSoFar }
271
- );
272
- }
273
-
274
- // Refund via provider
275
- let refundResult = null;
276
-
277
- try {
278
- refundResult = await provider.refund(paymentId, refundAmount, { reason });
279
- } catch (error) {
280
- this.logger.error('Refund failed:', error);
281
- throw new RefundError(paymentId, error.message);
282
- }
283
-
284
- // Create separate refund transaction (EXPENSE) for proper accounting
285
- const refundTransactionType = this.config.transactionTypeMapping?.refund || TRANSACTION_TYPE.EXPENSE;
286
-
287
- // Reverse commission proportionally for refund
288
- const refundCommission = transaction.commission
289
- ? reverseCommission(transaction.commission, transaction.amount, refundAmount)
290
- : null;
291
-
292
- const refundTransaction = await TransactionModel.create({
293
- organizationId: transaction.organizationId,
294
- customerId: transaction.customerId,
295
- amount: refundAmount,
296
- currency: transaction.currency,
297
- category: transaction.category,
298
- type: refundTransactionType, // EXPENSE - money going out
299
- method: transaction.method || 'manual',
300
- status: 'completed',
301
- gateway: {
302
- type: transaction.gateway?.type || 'manual',
303
- paymentIntentId: refundResult.id,
304
- provider: refundResult.provider,
305
- },
306
- paymentDetails: transaction.paymentDetails,
307
- ...(refundCommission && { commission: refundCommission }), // Reversed commission
308
- // Polymorphic reference (copy from original transaction)
309
- ...(transaction.referenceId && { referenceId: transaction.referenceId }),
310
- ...(transaction.referenceModel && { referenceModel: transaction.referenceModel }),
311
- metadata: {
312
- ...transaction.metadata,
313
- isRefund: true,
314
- originalTransactionId: transaction._id.toString(),
315
- refundReason: reason,
316
- refundResult: refundResult.metadata,
317
- },
318
- idempotencyKey: `refund_${transaction._id}_${Date.now()}`,
319
- });
320
-
321
- // Update original transaction status
322
- const isPartialRefund = refundAmount < transaction.amount;
323
- transaction.status = isPartialRefund ? 'partially_refunded' : 'refunded';
324
- transaction.refundedAmount = (transaction.refundedAmount || 0) + refundAmount;
325
- transaction.refundedAt = refundResult.refundedAt || new Date();
326
- transaction.metadata = {
327
- ...transaction.metadata,
328
- refundTransactionId: refundTransaction._id.toString(),
329
- refundReason: reason,
330
- };
331
-
332
- await transaction.save();
333
-
334
- // Trigger hook
335
- this._triggerHook('payment.refunded', {
336
- transaction,
337
- refundTransaction,
338
- refundResult,
339
- refundAmount,
340
- reason,
341
- isPartialRefund,
342
- });
343
-
344
- return {
345
- transaction,
346
- refundTransaction,
347
- refundResult,
348
- status: transaction.status,
349
- };
350
- }
351
-
352
- /**
353
- * Handle webhook from payment provider
354
- *
355
- * @param {String} provider - Provider name
356
- * @param {Object} payload - Webhook payload
357
- * @param {Object} headers - Request headers
358
- * @returns {Promise<Object>} { event, transaction }
359
- */
360
- async handleWebhook(providerName, payload, headers = {}) {
361
- const provider = this.providers[providerName];
362
-
363
- if (!provider) {
364
- throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
365
- }
366
-
367
- // Check if provider supports webhooks
368
- const capabilities = provider.getCapabilities();
369
- if (!capabilities.supportsWebhooks) {
370
- throw new ProviderCapabilityError(providerName, 'webhooks');
371
- }
372
-
373
- // Process webhook via provider
374
- let webhookEvent = null;
375
- try {
376
- webhookEvent = await provider.handleWebhook(payload, headers);
377
- } catch (error) {
378
- this.logger.error('Webhook processing failed:', error);
379
- throw new ProviderError(
380
- `Webhook processing failed for ${providerName}: ${error.message}`,
381
- 'WEBHOOK_PROCESSING_FAILED',
382
- { retryable: false }
383
- );
384
- }
385
-
386
- // Validate webhook event structure
387
- if (!webhookEvent?.data?.paymentIntentId) {
388
- throw new ValidationError(
389
- `Invalid webhook event structure from ${providerName}: missing paymentIntentId`,
390
- { provider: providerName, eventType: webhookEvent?.type }
391
- );
392
- }
393
-
394
- // Find transaction by payment intent ID from webhook
395
- const TransactionModel = this.models.Transaction;
396
- const transaction = await TransactionModel.findOne({
397
- 'gateway.paymentIntentId': webhookEvent.data.paymentIntentId,
398
- });
399
-
400
- if (!transaction) {
401
- this.logger.warn('Transaction not found for webhook event', {
402
- provider: providerName,
403
- eventId: webhookEvent.id,
404
- paymentIntentId: webhookEvent.data.paymentIntentId,
405
- });
406
- throw new TransactionNotFoundError(webhookEvent.data.paymentIntentId);
407
- }
408
-
409
- // Check for duplicate webhook processing (idempotency)
410
- if (transaction.webhook?.eventId === webhookEvent.id && transaction.webhook?.processedAt) {
411
- this.logger.warn('Webhook already processed', {
412
- transactionId: transaction._id,
413
- eventId: webhookEvent.id,
414
- });
415
- return {
416
- event: webhookEvent,
417
- transaction,
418
- status: 'already_processed',
419
- };
420
- }
421
-
422
- // Update transaction based on webhook event
423
- transaction.webhook = {
424
- eventId: webhookEvent.id,
425
- eventType: webhookEvent.type,
426
- receivedAt: new Date(),
427
- processedAt: new Date(),
428
- data: webhookEvent.data,
429
- };
430
-
431
- // Update status based on webhook type
432
- if (webhookEvent.type === 'payment.succeeded') {
433
- transaction.status = 'verified';
434
- transaction.verifiedAt = webhookEvent.createdAt;
435
- } else if (webhookEvent.type === 'payment.failed') {
436
- transaction.status = 'failed';
437
- } else if (webhookEvent.type === 'refund.succeeded') {
438
- transaction.status = 'refunded';
439
- transaction.refundedAt = webhookEvent.createdAt;
440
- }
441
-
442
- await transaction.save();
443
-
444
- // Trigger hook
445
- this._triggerHook(`payment.webhook.${webhookEvent.type}`, {
446
- event: webhookEvent,
447
- transaction,
448
- });
449
-
450
- return {
451
- event: webhookEvent,
452
- transaction,
453
- status: 'processed',
454
- };
455
- }
456
-
457
- /**
458
- * List payments/transactions with filters
459
- *
460
- * @param {Object} filters - Query filters
461
- * @param {Object} options - Query options (limit, skip, sort)
462
- * @returns {Promise<Array>} Transactions
463
- */
464
- async list(filters = {}, options = {}) {
465
- const TransactionModel = this.models.Transaction;
466
- const { limit = 50, skip = 0, sort = { createdAt: -1 } } = options;
467
-
468
- const transactions = await TransactionModel
469
- .find(filters)
470
- .limit(limit)
471
- .skip(skip)
472
- .sort(sort);
473
-
474
- return transactions;
475
- }
476
-
477
- /**
478
- * Get payment/transaction by ID
479
- *
480
- * @param {String} transactionId - Transaction ID
481
- * @returns {Promise<Object>} Transaction
482
- */
483
- async get(transactionId) {
484
- const TransactionModel = this.models.Transaction;
485
- const transaction = await TransactionModel.findById(transactionId);
486
-
487
- if (!transaction) {
488
- throw new TransactionNotFoundError(transactionId);
489
- }
490
-
491
- return transaction;
492
- }
493
-
494
- /**
495
- * Get provider instance
496
- *
497
- * @param {String} providerName - Provider name
498
- * @returns {Object} Provider instance
499
- */
500
- getProvider(providerName) {
501
- const provider = this.providers[providerName];
502
- if (!provider) {
503
- throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
504
- }
505
- return provider;
506
- }
507
-
508
- /**
509
- * Trigger event hook (fire-and-forget, non-blocking)
510
- * @private
511
- */
512
- _triggerHook(event, data) {
513
- triggerHook(this.hooks, event, data, this.logger);
514
- }
515
- }
516
-
517
- export default PaymentService;
@@ -1,142 +0,0 @@
1
- /**
2
- * Transaction Service
3
- * @classytic/revenue
4
- *
5
- * Thin, focused transaction service for core operations
6
- * Users handle their own analytics, exports, and complex queries
7
- *
8
- * Works with ANY model implementation:
9
- * - Plain Mongoose models
10
- * - @classytic/mongokit Repository instances
11
- * - Any other abstraction with compatible interface
12
- */
13
-
14
- import { TransactionNotFoundError } from '../core/errors.js';
15
- import { triggerHook } from '../utils/hooks.js';
16
-
17
- /**
18
- * Transaction Service
19
- * Focused on core transaction lifecycle operations
20
- */
21
- export class TransactionService {
22
- constructor(container) {
23
- this.container = container;
24
- this.models = container.get('models');
25
- this.hooks = container.get('hooks');
26
- this.logger = container.get('logger');
27
- }
28
-
29
- /**
30
- * Get transaction by ID
31
- *
32
- * @param {String} transactionId - Transaction ID
33
- * @returns {Promise<Object>} Transaction
34
- */
35
- async get(transactionId) {
36
- const TransactionModel = this.models.Transaction;
37
- const transaction = await TransactionModel.findById(transactionId);
38
-
39
- if (!transaction) {
40
- throw new TransactionNotFoundError(transactionId);
41
- }
42
-
43
- return transaction;
44
- }
45
-
46
- /**
47
- * List transactions with filters
48
- *
49
- * @param {Object} filters - Query filters
50
- * @param {Object} options - Query options (limit, skip, sort, populate)
51
- * @returns {Promise<Object>} { transactions, total, page, limit }
52
- */
53
- async list(filters = {}, options = {}) {
54
- const TransactionModel = this.models.Transaction;
55
- const {
56
- limit = 50,
57
- skip = 0,
58
- page = null,
59
- sort = { createdAt: -1 },
60
- populate = [],
61
- } = options;
62
-
63
- // Calculate pagination
64
- const actualSkip = page ? (page - 1) * limit : skip;
65
-
66
- // Build query
67
- let query = TransactionModel.find(filters)
68
- .limit(limit)
69
- .skip(actualSkip)
70
- .sort(sort);
71
-
72
- // Apply population if supported
73
- if (populate.length > 0 && typeof query.populate === 'function') {
74
- populate.forEach(field => {
75
- query = query.populate(field);
76
- });
77
- }
78
-
79
- const transactions = await query;
80
-
81
- // Count documents (works with both Mongoose and Repository)
82
- const total = await (TransactionModel.countDocuments
83
- ? TransactionModel.countDocuments(filters)
84
- : TransactionModel.count(filters));
85
-
86
- return {
87
- transactions,
88
- total,
89
- page: page || Math.floor(actualSkip / limit) + 1,
90
- limit,
91
- pages: Math.ceil(total / limit),
92
- };
93
- }
94
-
95
-
96
- /**
97
- * Update transaction
98
- *
99
- * @param {String} transactionId - Transaction ID
100
- * @param {Object} updates - Fields to update
101
- * @returns {Promise<Object>} Updated transaction
102
- */
103
- async update(transactionId, updates) {
104
- const TransactionModel = this.models.Transaction;
105
-
106
- // Support both Repository pattern and Mongoose
107
- let transaction;
108
- if (typeof TransactionModel.update === 'function') {
109
- // Repository pattern
110
- transaction = await TransactionModel.update(transactionId, updates);
111
- } else {
112
- // Plain Mongoose
113
- transaction = await TransactionModel.findByIdAndUpdate(
114
- transactionId,
115
- { $set: updates },
116
- { new: true }
117
- );
118
- }
119
-
120
- if (!transaction) {
121
- throw new TransactionNotFoundError(transactionId);
122
- }
123
-
124
- // Trigger hook (fire-and-forget, non-blocking)
125
- this._triggerHook('transaction.updated', {
126
- transaction,
127
- updates,
128
- });
129
-
130
- return transaction;
131
- }
132
-
133
- /**
134
- * Trigger event hook (fire-and-forget, non-blocking)
135
- * @private
136
- */
137
- _triggerHook(event, data) {
138
- triggerHook(this.hooks, event, data, this.logger);
139
- }
140
- }
141
-
142
- export default TransactionService;