@classytic/revenue 0.2.4 → 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 -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 +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 -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
@@ -1,535 +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, session 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
- let transaction = await this._findTransaction(TransactionModel, paymentIntentId);
66
-
67
- if (!transaction) {
68
- throw new TransactionNotFoundError(paymentIntentId);
69
- }
70
-
71
- if (transaction.status === 'verified' || transaction.status === 'completed') {
72
- throw new AlreadyVerifiedError(transaction._id);
73
- }
74
-
75
- // Get provider for verification
76
- const gatewayType = transaction.gateway?.type || 'manual';
77
- const provider = this.providers[gatewayType];
78
-
79
- if (!provider) {
80
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
81
- }
82
-
83
- // Verify payment with provider
84
- let paymentResult = null;
85
- try {
86
- paymentResult = await provider.verifyPayment(paymentIntentId);
87
- } catch (error) {
88
- this.logger.error('Payment verification failed:', error);
89
-
90
- // Update transaction as failed
91
- transaction.status = 'failed';
92
- transaction.failureReason = error.message;
93
- transaction.metadata = {
94
- ...transaction.metadata,
95
- verificationError: error.message,
96
- failedAt: new Date().toISOString(),
97
- };
98
- await transaction.save();
99
-
100
- // Trigger payment.failed hook
101
- this._triggerHook('payment.failed', {
102
- transaction,
103
- error: error.message,
104
- provider: gatewayType,
105
- paymentIntentId,
106
- });
107
-
108
- throw new PaymentVerificationError(paymentIntentId, error.message);
109
- }
110
-
111
- // Validate amount and currency match
112
- if (paymentResult.amount && paymentResult.amount !== transaction.amount) {
113
- throw new ValidationError(
114
- `Amount mismatch: expected ${transaction.amount}, got ${paymentResult.amount}`,
115
- { expected: transaction.amount, actual: paymentResult.amount }
116
- );
117
- }
118
-
119
- if (paymentResult.currency && paymentResult.currency.toUpperCase() !== transaction.currency.toUpperCase()) {
120
- throw new ValidationError(
121
- `Currency mismatch: expected ${transaction.currency}, got ${paymentResult.currency}`,
122
- { expected: transaction.currency, actual: paymentResult.currency }
123
- );
124
- }
125
-
126
- // Update transaction based on verification result
127
- transaction.status = paymentResult.status === 'succeeded' ? 'verified' : paymentResult.status;
128
- transaction.verifiedAt = paymentResult.paidAt || new Date();
129
- transaction.verifiedBy = verifiedBy;
130
- transaction.gateway = {
131
- ...transaction.gateway,
132
- verificationData: paymentResult.metadata,
133
- };
134
-
135
- await transaction.save();
136
-
137
- // Trigger hook
138
- this._triggerHook('payment.verified', {
139
- transaction,
140
- paymentResult,
141
- verifiedBy,
142
- });
143
-
144
- return {
145
- transaction,
146
- paymentResult,
147
- status: transaction.status,
148
- };
149
- }
150
-
151
- /**
152
- * Get payment status
153
- *
154
- * @param {String} paymentIntentId - Payment intent ID, session ID, or transaction ID
155
- * @returns {Promise<Object>} { transaction, status }
156
- */
157
- async getStatus(paymentIntentId) {
158
- const TransactionModel = this.models.Transaction;
159
- let transaction = await this._findTransaction(TransactionModel, paymentIntentId);
160
-
161
- if (!transaction) {
162
- throw new TransactionNotFoundError(paymentIntentId);
163
- }
164
-
165
- // Get provider
166
- const gatewayType = transaction.gateway?.type || 'manual';
167
- const provider = this.providers[gatewayType];
168
-
169
- if (!provider) {
170
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
171
- }
172
-
173
- // Get status from provider
174
- let paymentResult = null;
175
- try {
176
- paymentResult = await provider.getStatus(paymentIntentId);
177
- } catch (error) {
178
- this.logger.warn('Failed to get payment status from provider:', error);
179
- // Return transaction status as fallback
180
- return {
181
- transaction,
182
- status: transaction.status,
183
- provider: gatewayType,
184
- };
185
- }
186
-
187
- return {
188
- transaction,
189
- paymentResult,
190
- status: paymentResult.status,
191
- provider: gatewayType,
192
- };
193
- }
194
-
195
- /**
196
- * Refund a payment
197
- *
198
- * @param {String} paymentId - Payment intent ID, session ID, or transaction ID
199
- * @param {Number} amount - Amount to refund (optional, full refund if not provided)
200
- * @param {Object} options - Refund options
201
- * @param {String} options.reason - Refund reason
202
- * @returns {Promise<Object>} { transaction, refundResult }
203
- */
204
- async refund(paymentId, amount = null, options = {}) {
205
- const { reason = null } = options;
206
-
207
- const TransactionModel = this.models.Transaction;
208
- let transaction = await this._findTransaction(TransactionModel, paymentId);
209
-
210
- if (!transaction) {
211
- throw new TransactionNotFoundError(paymentId);
212
- }
213
-
214
- if (transaction.status !== 'verified' && transaction.status !== 'completed') {
215
- throw new RefundError(transaction._id, 'Only verified/completed transactions can be refunded');
216
- }
217
-
218
- // Get provider
219
- const gatewayType = transaction.gateway?.type || 'manual';
220
- const provider = this.providers[gatewayType];
221
-
222
- if (!provider) {
223
- throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
224
- }
225
-
226
- // Check if provider supports refunds
227
- const capabilities = provider.getCapabilities();
228
- if (!capabilities.supportsRefunds) {
229
- throw new RefundNotSupportedError(gatewayType);
230
- }
231
-
232
- // Calculate refundable amount
233
- const refundedSoFar = transaction.refundedAmount || 0;
234
- const refundableAmount = transaction.amount - refundedSoFar;
235
- const refundAmount = amount || refundableAmount;
236
-
237
- // Validate refund amount
238
- if (refundAmount <= 0) {
239
- throw new ValidationError(`Refund amount must be positive, got ${refundAmount}`);
240
- }
241
-
242
- if (refundAmount > refundableAmount) {
243
- throw new ValidationError(
244
- `Refund amount (${refundAmount}) exceeds refundable balance (${refundableAmount})`,
245
- { refundAmount, refundableAmount, alreadyRefunded: refundedSoFar }
246
- );
247
- }
248
-
249
- // Refund via provider
250
- let refundResult = null;
251
-
252
- try {
253
- refundResult = await provider.refund(paymentId, refundAmount, { reason });
254
- } catch (error) {
255
- this.logger.error('Refund failed:', error);
256
- throw new RefundError(paymentId, error.message);
257
- }
258
-
259
- // Create separate refund transaction (EXPENSE) for proper accounting
260
- const refundTransactionType = this.config.transactionTypeMapping?.refund || TRANSACTION_TYPE.EXPENSE;
261
-
262
- // Reverse commission proportionally for refund
263
- const refundCommission = transaction.commission
264
- ? reverseCommission(transaction.commission, transaction.amount, refundAmount)
265
- : null;
266
-
267
- const refundTransaction = await TransactionModel.create({
268
- organizationId: transaction.organizationId,
269
- customerId: transaction.customerId,
270
- amount: refundAmount,
271
- currency: transaction.currency,
272
- category: transaction.category,
273
- type: refundTransactionType, // EXPENSE - money going out
274
- method: transaction.method || 'manual',
275
- status: 'completed',
276
- gateway: {
277
- type: transaction.gateway?.type || 'manual',
278
- paymentIntentId: refundResult.id,
279
- provider: refundResult.provider,
280
- },
281
- paymentDetails: transaction.paymentDetails,
282
- ...(refundCommission && { commission: refundCommission }), // Reversed commission
283
- // Polymorphic reference (copy from original transaction)
284
- ...(transaction.referenceId && { referenceId: transaction.referenceId }),
285
- ...(transaction.referenceModel && { referenceModel: transaction.referenceModel }),
286
- metadata: {
287
- ...transaction.metadata,
288
- isRefund: true,
289
- originalTransactionId: transaction._id.toString(),
290
- refundReason: reason,
291
- refundResult: refundResult.metadata,
292
- },
293
- idempotencyKey: `refund_${transaction._id}_${Date.now()}`,
294
- });
295
-
296
- // Update original transaction status
297
- const isPartialRefund = refundAmount < transaction.amount;
298
- transaction.status = isPartialRefund ? 'partially_refunded' : 'refunded';
299
- transaction.refundedAmount = (transaction.refundedAmount || 0) + refundAmount;
300
- transaction.refundedAt = refundResult.refundedAt || new Date();
301
- transaction.metadata = {
302
- ...transaction.metadata,
303
- refundTransactionId: refundTransaction._id.toString(),
304
- refundReason: reason,
305
- };
306
-
307
- await transaction.save();
308
-
309
- // Trigger hook
310
- this._triggerHook('payment.refunded', {
311
- transaction,
312
- refundTransaction,
313
- refundResult,
314
- refundAmount,
315
- reason,
316
- isPartialRefund,
317
- });
318
-
319
- return {
320
- transaction,
321
- refundTransaction,
322
- refundResult,
323
- status: transaction.status,
324
- };
325
- }
326
-
327
- /**
328
- * Handle webhook from payment provider
329
- *
330
- * @param {String} provider - Provider name
331
- * @param {Object} payload - Webhook payload
332
- * @param {Object} headers - Request headers
333
- * @returns {Promise<Object>} { event, transaction }
334
- */
335
- async handleWebhook(providerName, payload, headers = {}) {
336
- const provider = this.providers[providerName];
337
-
338
- if (!provider) {
339
- throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
340
- }
341
-
342
- // Check if provider supports webhooks
343
- const capabilities = provider.getCapabilities();
344
- if (!capabilities.supportsWebhooks) {
345
- throw new ProviderCapabilityError(providerName, 'webhooks');
346
- }
347
-
348
- // Process webhook via provider
349
- let webhookEvent = null;
350
- try {
351
- webhookEvent = await provider.handleWebhook(payload, headers);
352
- } catch (error) {
353
- this.logger.error('Webhook processing failed:', error);
354
- throw new ProviderError(
355
- `Webhook processing failed for ${providerName}: ${error.message}`,
356
- 'WEBHOOK_PROCESSING_FAILED',
357
- { retryable: false }
358
- );
359
- }
360
-
361
- // Validate webhook event structure
362
- if (!webhookEvent?.data?.sessionId && !webhookEvent?.data?.paymentIntentId) {
363
- throw new ValidationError(
364
- `Invalid webhook event structure from ${providerName}: missing sessionId or paymentIntentId`,
365
- { provider: providerName, eventType: webhookEvent?.type }
366
- );
367
- }
368
-
369
- // Find transaction by sessionId first (for checkout flows), then paymentIntentId
370
- const TransactionModel = this.models.Transaction;
371
- let transaction = null;
372
-
373
- if (webhookEvent.data.sessionId) {
374
- transaction = await TransactionModel.findOne({
375
- 'gateway.sessionId': webhookEvent.data.sessionId,
376
- });
377
- }
378
-
379
- if (!transaction && webhookEvent.data.paymentIntentId) {
380
- transaction = await TransactionModel.findOne({
381
- 'gateway.paymentIntentId': webhookEvent.data.paymentIntentId,
382
- });
383
- }
384
-
385
- if (!transaction) {
386
- this.logger.warn('Transaction not found for webhook event', {
387
- provider: providerName,
388
- eventId: webhookEvent.id,
389
- sessionId: webhookEvent.data.sessionId,
390
- paymentIntentId: webhookEvent.data.paymentIntentId,
391
- });
392
- throw new TransactionNotFoundError(
393
- webhookEvent.data.sessionId || webhookEvent.data.paymentIntentId
394
- );
395
- }
396
-
397
- // Update gateway with complete information from webhook
398
- if (webhookEvent.data.sessionId && !transaction.gateway.sessionId) {
399
- transaction.gateway.sessionId = webhookEvent.data.sessionId;
400
- }
401
- if (webhookEvent.data.paymentIntentId && !transaction.gateway.paymentIntentId) {
402
- transaction.gateway.paymentIntentId = webhookEvent.data.paymentIntentId;
403
- }
404
-
405
- // Check for duplicate webhook processing (idempotency)
406
- if (transaction.webhook?.eventId === webhookEvent.id && transaction.webhook?.processedAt) {
407
- this.logger.warn('Webhook already processed', {
408
- transactionId: transaction._id,
409
- eventId: webhookEvent.id,
410
- });
411
- return {
412
- event: webhookEvent,
413
- transaction,
414
- status: 'already_processed',
415
- };
416
- }
417
-
418
- // Update transaction based on webhook event
419
- transaction.webhook = {
420
- eventId: webhookEvent.id,
421
- eventType: webhookEvent.type,
422
- receivedAt: new Date(),
423
- processedAt: new Date(),
424
- data: webhookEvent.data,
425
- };
426
-
427
- // Update status based on webhook type
428
- if (webhookEvent.type === 'payment.succeeded') {
429
- transaction.status = 'verified';
430
- transaction.verifiedAt = webhookEvent.createdAt;
431
- } else if (webhookEvent.type === 'payment.failed') {
432
- transaction.status = 'failed';
433
- } else if (webhookEvent.type === 'refund.succeeded') {
434
- transaction.status = 'refunded';
435
- transaction.refundedAt = webhookEvent.createdAt;
436
- }
437
-
438
- await transaction.save();
439
-
440
- // Trigger hook
441
- this._triggerHook(`payment.webhook.${webhookEvent.type}`, {
442
- event: webhookEvent,
443
- transaction,
444
- });
445
-
446
- return {
447
- event: webhookEvent,
448
- transaction,
449
- status: 'processed',
450
- };
451
- }
452
-
453
- /**
454
- * List payments/transactions with filters
455
- *
456
- * @param {Object} filters - Query filters
457
- * @param {Object} options - Query options (limit, skip, sort)
458
- * @returns {Promise<Array>} Transactions
459
- */
460
- async list(filters = {}, options = {}) {
461
- const TransactionModel = this.models.Transaction;
462
- const { limit = 50, skip = 0, sort = { createdAt: -1 } } = options;
463
-
464
- const transactions = await TransactionModel
465
- .find(filters)
466
- .limit(limit)
467
- .skip(skip)
468
- .sort(sort);
469
-
470
- return transactions;
471
- }
472
-
473
- /**
474
- * Get payment/transaction by ID
475
- *
476
- * @param {String} transactionId - Transaction ID
477
- * @returns {Promise<Object>} Transaction
478
- */
479
- async get(transactionId) {
480
- const TransactionModel = this.models.Transaction;
481
- const transaction = await TransactionModel.findById(transactionId);
482
-
483
- if (!transaction) {
484
- throw new TransactionNotFoundError(transactionId);
485
- }
486
-
487
- return transaction;
488
- }
489
-
490
- /**
491
- * Get provider instance
492
- *
493
- * @param {String} providerName - Provider name
494
- * @returns {Object} Provider instance
495
- */
496
- getProvider(providerName) {
497
- const provider = this.providers[providerName];
498
- if (!provider) {
499
- throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
500
- }
501
- return provider;
502
- }
503
-
504
- /**
505
- * Trigger event hook (fire-and-forget, non-blocking)
506
- * @private
507
- */
508
- _triggerHook(event, data) {
509
- triggerHook(this.hooks, event, data, this.logger);
510
- }
511
-
512
- /**
513
- * Find transaction by sessionId, paymentIntentId, or transaction ID
514
- * @private
515
- */
516
- async _findTransaction(TransactionModel, identifier) {
517
- let transaction = await TransactionModel.findOne({
518
- 'gateway.sessionId': identifier,
519
- });
520
-
521
- if (!transaction) {
522
- transaction = await TransactionModel.findOne({
523
- 'gateway.paymentIntentId': identifier,
524
- });
525
- }
526
-
527
- if (!transaction) {
528
- transaction = await TransactionModel.findById(identifier);
529
- }
530
-
531
- return transaction;
532
- }
533
- }
534
-
535
- 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;