@classytic/revenue 0.0.2 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/enums/index.d.ts CHANGED
@@ -5,6 +5,13 @@
5
5
 
6
6
  // ============ TRANSACTION ENUMS ============
7
7
 
8
+ export const TRANSACTION_TYPE: {
9
+ readonly INCOME: 'income';
10
+ readonly EXPENSE: 'expense';
11
+ };
12
+
13
+ export const TRANSACTION_TYPE_VALUES: string[];
14
+
8
15
  export const TRANSACTION_STATUS: {
9
16
  readonly PENDING: 'pending';
10
17
  readonly PAYMENT_INITIATED: 'payment_initiated';
@@ -86,6 +93,8 @@ export const MONETIZATION_TYPE_VALUES: string[];
86
93
  // ============ DEFAULT EXPORT ============
87
94
 
88
95
  declare const _default: {
96
+ TRANSACTION_TYPE: typeof TRANSACTION_TYPE;
97
+ TRANSACTION_TYPE_VALUES: typeof TRANSACTION_TYPE_VALUES;
89
98
  TRANSACTION_STATUS: typeof TRANSACTION_STATUS;
90
99
  TRANSACTION_STATUS_VALUES: typeof TRANSACTION_STATUS_VALUES;
91
100
  LIBRARY_CATEGORIES: typeof LIBRARY_CATEGORIES;
package/enums/index.js CHANGED
@@ -16,6 +16,8 @@ export * from './monetization.enums.js';
16
16
 
17
17
  // Default export for convenience
18
18
  import {
19
+ TRANSACTION_TYPE,
20
+ TRANSACTION_TYPE_VALUES,
19
21
  TRANSACTION_STATUS,
20
22
  TRANSACTION_STATUS_VALUES,
21
23
  LIBRARY_CATEGORIES,
@@ -45,6 +47,8 @@ import {
45
47
 
46
48
  export default {
47
49
  // Transaction enums
50
+ TRANSACTION_TYPE,
51
+ TRANSACTION_TYPE_VALUES,
48
52
  TRANSACTION_STATUS,
49
53
  TRANSACTION_STATUS_VALUES,
50
54
  LIBRARY_CATEGORIES,
@@ -6,6 +6,22 @@
6
6
  * Users should define their own categories and merge with these.
7
7
  */
8
8
 
9
+ // ============ TRANSACTION TYPE ============
10
+ /**
11
+ * Transaction Type - Income vs Expense
12
+ *
13
+ * INCOME: Money coming in (payments, subscriptions, purchases)
14
+ * EXPENSE: Money going out (refunds, payouts)
15
+ *
16
+ * Users can map these in their config via transactionTypeMapping
17
+ */
18
+ export const TRANSACTION_TYPE = {
19
+ INCOME: 'income',
20
+ EXPENSE: 'expense',
21
+ };
22
+
23
+ export const TRANSACTION_TYPE_VALUES = Object.values(TRANSACTION_TYPE);
24
+
9
25
  // ============ TRANSACTION STATUS ============
10
26
  /**
11
27
  * Transaction Status - Library-managed states
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/revenue",
3
- "version": "0.0.2",
3
+ "version": "0.0.22",
4
4
  "description": "Enterprise revenue management system with subscriptions, purchases, proration, and payment processing",
5
5
  "main": "index.js",
6
6
  "types": "revenue.d.ts",
package/revenue.d.ts CHANGED
@@ -105,13 +105,21 @@ export class SubscriptionService {
105
105
  amount: number;
106
106
  currency?: string;
107
107
  gateway?: string;
108
+ entity?: string;
109
+ monetizationType?: 'free' | 'subscription' | 'purchase';
108
110
  paymentData?: any;
109
111
  metadata?: Record<string, any>;
110
112
  idempotencyKey?: string;
111
113
  }): Promise<{ subscription: any; transaction: any; paymentIntent: PaymentIntent | null }>;
112
114
 
113
115
  activate(subscriptionId: string, options?: { timestamp?: Date }): Promise<any>;
114
- renew(subscriptionId: string, params?: any): Promise<{ subscription: any; transaction: any; paymentIntent: PaymentIntent }>;
116
+ renew(subscriptionId: string, params?: {
117
+ gateway?: string;
118
+ entity?: string;
119
+ paymentData?: any;
120
+ metadata?: Record<string, any>;
121
+ idempotencyKey?: string;
122
+ }): Promise<{ subscription: any; transaction: any; paymentIntent: PaymentIntent }>;
115
123
  cancel(subscriptionId: string, options?: { immediate?: boolean; reason?: string }): Promise<any>;
116
124
  pause(subscriptionId: string, options?: { reason?: string }): Promise<any>;
117
125
  resume(subscriptionId: string, options?: { extendPeriod?: boolean }): Promise<any>;
@@ -124,7 +132,7 @@ export class PaymentService {
124
132
 
125
133
  verify(paymentIntentId: string, options?: { verifiedBy?: string }): Promise<{ transaction: any; paymentResult: PaymentResult; status: string }>;
126
134
  getStatus(paymentIntentId: string): Promise<{ transaction: any; paymentResult: PaymentResult; status: string; provider: string }>;
127
- refund(paymentId: string, amount?: number, options?: { reason?: string }): Promise<{ transaction: any; refundResult: RefundResult; status: string }>;
135
+ refund(paymentId: string, amount?: number, options?: { reason?: string }): Promise<{ transaction: any; refundTransaction: any; refundResult: RefundResult; status: string }>;
128
136
  handleWebhook(providerName: string, payload: any, headers?: any): Promise<{ event: WebhookEvent; transaction: any; status: string }>;
129
137
  list(filters?: any, options?: any): Promise<any[]>;
130
138
  get(transactionId: string): Promise<any>;
@@ -193,8 +201,43 @@ export interface RevenueOptions {
193
201
  providers?: Record<string, PaymentProvider>;
194
202
  hooks?: Record<string, Function[]>;
195
203
  config?: {
196
- targetModels?: string[];
204
+ /**
205
+ * Maps logical entity identifiers to custom transaction category names
206
+ *
207
+ * Entity identifiers are NOT database model names - they are logical identifiers
208
+ * you choose to organize your business logic.
209
+ *
210
+ * @example
211
+ * categoryMappings: {
212
+ * Order: 'order_subscription', // Customer orders
213
+ * PlatformSubscription: 'platform_subscription', // Tenant/org subscriptions
214
+ * TenantUpgrade: 'tenant_upgrade', // Tenant upgrades
215
+ * Membership: 'gym_membership', // User memberships
216
+ * Enrollment: 'course_enrollment', // Course enrollments
217
+ * }
218
+ *
219
+ * If not specified, falls back to library defaults: 'subscription' or 'purchase'
220
+ */
197
221
  categoryMappings?: Record<string, string>;
222
+
223
+ /**
224
+ * Maps transaction types to income/expense for your accounting system
225
+ *
226
+ * Allows you to control how different transaction types are recorded:
227
+ * - 'income': Money coming in (payments, subscriptions)
228
+ * - 'expense': Money going out (refunds)
229
+ *
230
+ * @example
231
+ * transactionTypeMapping: {
232
+ * subscription: 'income',
233
+ * subscription_renewal: 'income',
234
+ * purchase: 'income',
235
+ * refund: 'expense',
236
+ * }
237
+ *
238
+ * If not specified, library defaults to 'income' for all payment transactions
239
+ */
240
+ transactionTypeMapping?: Record<string, 'income' | 'expense'>;
198
241
  [key: string]: any;
199
242
  };
200
243
  logger?: Console | any;
@@ -204,6 +247,11 @@ export function createRevenue(options: RevenueOptions): Revenue;
204
247
 
205
248
  // ============ ENUMS ============
206
249
 
250
+ export const TRANSACTION_TYPE: {
251
+ INCOME: 'income';
252
+ EXPENSE: 'expense';
253
+ };
254
+
207
255
  export const TRANSACTION_STATUS: {
208
256
  PENDING: 'pending';
209
257
  PAYMENT_INITIATED: 'payment_initiated';
@@ -218,19 +266,10 @@ export const TRANSACTION_STATUS: {
218
266
  PARTIALLY_REFUNDED: 'partially_refunded';
219
267
  };
220
268
 
221
- export const TRANSACTION_TYPES: {
222
- INCOME: 'income';
223
- EXPENSE: 'expense';
224
- };
225
-
226
- // Note: PAYMENT_METHOD removed - users define their own payment methods
227
-
228
269
  export const PAYMENT_GATEWAY_TYPE: {
229
270
  MANUAL: 'manual';
230
271
  STRIPE: 'stripe';
231
272
  SSLCOMMERZ: 'sslcommerz';
232
- BKASH_GATEWAY: 'bkash_gateway';
233
- NAGAD_GATEWAY: 'nagad_gateway';
234
273
  };
235
274
 
236
275
  export const SUBSCRIPTION_STATUS: {
@@ -263,15 +302,6 @@ export const subscriptionPlanSchema: Schema;
263
302
  export const gatewaySchema: Schema;
264
303
  export const commissionSchema: Schema;
265
304
  export const paymentDetailsSchema: Schema;
266
- export const tenantSnapshotSchema: Schema;
267
- export const timelineEventSchema: Schema;
268
- export const customerInfoSchema: Schema;
269
- export const customDiscountSchema: Schema;
270
- export const stripeAccountSchema: Schema;
271
- export const sslcommerzAccountSchema: Schema;
272
- export const bkashMerchantSchema: Schema;
273
- export const bankAccountSchema: Schema;
274
- export const walletSchema: Schema;
275
305
 
276
306
  // ============ UTILITIES ============
277
307
 
@@ -12,23 +12,11 @@ export const paymentSummarySchema: Schema;
12
12
  export const paymentDetailsSchema: Schema;
13
13
  export const gatewaySchema: Schema;
14
14
  export const commissionSchema: Schema;
15
- export const tenantSnapshotSchema: Schema;
16
- export const timelineEventSchema: Schema;
17
- export const customerInfoSchema: Schema;
18
15
 
19
16
  // ============ SUBSCRIPTION SCHEMAS ============
20
17
 
21
18
  export const subscriptionInfoSchema: Schema;
22
19
  export const subscriptionPlanSchema: Schema;
23
- export const customDiscountSchema: Schema;
24
-
25
- // ============ GATEWAY ACCOUNT SCHEMAS ============
26
-
27
- export const stripeAccountSchema: Schema;
28
- export const sslcommerzAccountSchema: Schema;
29
- export const bkashMerchantSchema: Schema;
30
- export const bankAccountSchema: Schema;
31
- export const walletSchema: Schema;
32
20
 
33
21
  // ============ DEFAULT EXPORT ============
34
22
 
@@ -38,17 +26,8 @@ declare const _default: {
38
26
  paymentDetailsSchema: Schema;
39
27
  gatewaySchema: Schema;
40
28
  commissionSchema: Schema;
41
- tenantSnapshotSchema: Schema;
42
- timelineEventSchema: Schema;
43
- customerInfoSchema: Schema;
44
29
  subscriptionInfoSchema: Schema;
45
30
  subscriptionPlanSchema: Schema;
46
- customDiscountSchema: Schema;
47
- stripeAccountSchema: Schema;
48
- sslcommerzAccountSchema: Schema;
49
- bkashMerchantSchema: Schema;
50
- bankAccountSchema: Schema;
51
- walletSchema: Schema;
52
31
  };
53
32
 
54
33
  export default _default;
@@ -16,6 +16,7 @@ import {
16
16
  ProviderCapabilityError,
17
17
  } from '../core/errors.js';
18
18
  import { triggerHook } from '../utils/hooks.js';
19
+ import { TRANSACTION_TYPE } from '../enums/transaction.enums.js';
19
20
 
20
21
  /**
21
22
  * Payment Service
@@ -132,7 +133,7 @@ export class PaymentService {
132
133
  }
133
134
 
134
135
  if (!transaction) {
135
- throw new Error('Transaction not found');
136
+ throw new TransactionNotFoundError(paymentIntentId);
136
137
  }
137
138
 
138
139
  // Get provider
@@ -140,7 +141,7 @@ export class PaymentService {
140
141
  const provider = this.providers[gatewayType];
141
142
 
142
143
  if (!provider) {
143
- throw new Error(`Payment provider "${gatewayType}" not found`);
144
+ throw new ProviderNotFoundError(gatewayType, Object.keys(this.providers));
144
145
  }
145
146
 
146
147
  // Get status from provider
@@ -218,18 +219,46 @@ export class PaymentService {
218
219
  refundResult = await provider.refund(paymentId, refundAmount, { reason });
219
220
  } catch (error) {
220
221
  this.logger.error('Refund failed:', error);
221
- throw new Error(`Refund failed: ${error.message}`);
222
+ throw new RefundError(paymentId, error.message);
222
223
  }
223
224
 
224
- // Update transaction
225
+ // Create separate refund transaction (EXPENSE) for proper accounting
226
+ const refundTransactionType = this.config.transactionTypeMapping?.refund || TRANSACTION_TYPE.EXPENSE;
227
+
228
+ const refundTransaction = await TransactionModel.create({
229
+ organizationId: transaction.organizationId,
230
+ customerId: transaction.customerId,
231
+ amount: refundAmount,
232
+ currency: transaction.currency,
233
+ category: transaction.category,
234
+ type: refundTransactionType, // EXPENSE - money going out
235
+ method: transaction.method || 'manual',
236
+ status: 'completed',
237
+ gateway: {
238
+ type: transaction.gateway?.type || 'manual',
239
+ paymentIntentId: refundResult.id,
240
+ provider: refundResult.provider,
241
+ },
242
+ paymentDetails: transaction.paymentDetails,
243
+ metadata: {
244
+ ...transaction.metadata,
245
+ isRefund: true,
246
+ originalTransactionId: transaction._id.toString(),
247
+ refundReason: reason,
248
+ refundResult: refundResult.metadata,
249
+ },
250
+ idempotencyKey: `refund_${transaction._id}_${Date.now()}`,
251
+ });
252
+
253
+ // Update original transaction status
225
254
  const isPartialRefund = refundAmount < transaction.amount;
226
255
  transaction.status = isPartialRefund ? 'partially_refunded' : 'refunded';
227
256
  transaction.refundedAmount = (transaction.refundedAmount || 0) + refundAmount;
228
257
  transaction.refundedAt = refundResult.refundedAt || new Date();
229
258
  transaction.metadata = {
230
259
  ...transaction.metadata,
260
+ refundTransactionId: refundTransaction._id.toString(),
231
261
  refundReason: reason,
232
- refundResult: refundResult.metadata,
233
262
  };
234
263
 
235
264
  await transaction.save();
@@ -237,6 +266,7 @@ export class PaymentService {
237
266
  // Trigger hook
238
267
  this._triggerHook('payment.refunded', {
239
268
  transaction,
269
+ refundTransaction,
240
270
  refundResult,
241
271
  refundAmount,
242
272
  reason,
@@ -245,6 +275,7 @@ export class PaymentService {
245
275
 
246
276
  return {
247
277
  transaction,
278
+ refundTransaction,
248
279
  refundResult,
249
280
  status: transaction.status,
250
281
  };
@@ -262,7 +293,7 @@ export class PaymentService {
262
293
  const provider = this.providers[providerName];
263
294
 
264
295
  if (!provider) {
265
- throw new Error(`Payment provider "${providerName}" not found`);
296
+ throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
266
297
  }
267
298
 
268
299
  // Process webhook via provider
@@ -271,7 +302,7 @@ export class PaymentService {
271
302
  webhookEvent = await provider.handleWebhook(payload, headers);
272
303
  } catch (error) {
273
304
  this.logger.error('Webhook processing failed:', error);
274
- throw new Error(`Webhook processing failed: ${error.message}`);
305
+ throw new ProviderError(providerName, `Webhook processing failed: ${error.message}`);
275
306
  }
276
307
 
277
308
  // Find transaction by payment intent ID from webhook
@@ -286,7 +317,7 @@ export class PaymentService {
286
317
  eventId: webhookEvent.id,
287
318
  paymentIntentId: webhookEvent.data.paymentIntentId,
288
319
  });
289
- throw new Error('Transaction not found for payment intent');
320
+ throw new TransactionNotFoundError(webhookEvent.data.paymentIntentId);
290
321
  }
291
322
 
292
323
  // Check for duplicate webhook processing (idempotency)
@@ -368,7 +399,7 @@ export class PaymentService {
368
399
  const transaction = await TransactionModel.findById(transactionId);
369
400
 
370
401
  if (!transaction) {
371
- throw new Error('Transaction not found');
402
+ throw new TransactionNotFoundError(transactionId);
372
403
  }
373
404
 
374
405
  return transaction;
@@ -383,7 +414,7 @@ export class PaymentService {
383
414
  getProvider(providerName) {
384
415
  const provider = this.providers[providerName];
385
416
  if (!provider) {
386
- throw new Error(`Payment provider "${providerName}" not found. Available: ${Object.keys(this.providers).join(', ')}`);
417
+ throw new ProviderNotFoundError(providerName, Object.keys(this.providers));
387
418
  }
388
419
  return provider;
389
420
  }
@@ -15,8 +15,12 @@ import {
15
15
  ModelNotRegisteredError,
16
16
  SubscriptionNotActiveError,
17
17
  PaymentIntentCreationError,
18
+ InvalidStateTransitionError,
18
19
  } from '../core/errors.js';
19
20
  import { triggerHook } from '../utils/hooks.js';
21
+ import { resolveCategory } from '../utils/category-resolver.js';
22
+ import { MONETIZATION_TYPES } from '../enums/monetization.enums.js';
23
+ import { TRANSACTION_TYPE } from '../enums/transaction.enums.js';
20
24
 
21
25
  /**
22
26
  * Subscription Service
@@ -41,6 +45,9 @@ export class SubscriptionService {
41
45
  * @param {Number} params.amount - Subscription amount
42
46
  * @param {String} params.currency - Currency code (default: 'BDT')
43
47
  * @param {String} params.gateway - Payment gateway to use (default: 'manual')
48
+ * @param {String} params.entity - Logical entity identifier (e.g., 'Order', 'PlatformSubscription', 'Membership')
49
+ * NOTE: This is NOT a database model name - it's just a logical identifier for categoryMappings
50
+ * @param {String} params.monetizationType - Monetization type ('free', 'subscription', 'purchase')
44
51
  * @param {Object} params.paymentData - Payment method details
45
52
  * @param {Object} params.metadata - Additional metadata
46
53
  * @param {String} params.idempotencyKey - Idempotency key for duplicate prevention
@@ -53,6 +60,8 @@ export class SubscriptionService {
53
60
  amount,
54
61
  currency = 'BDT',
55
62
  gateway = 'manual',
63
+ entity = null,
64
+ monetizationType = MONETIZATION_TYPES.SUBSCRIPTION,
56
65
  paymentData,
57
66
  metadata = {},
58
67
  idempotencyKey = null,
@@ -99,6 +108,14 @@ export class SubscriptionService {
99
108
  throw new PaymentIntentCreationError(gateway, error);
100
109
  }
101
110
 
111
+ // Resolve category based on entity and monetizationType
112
+ const category = resolveCategory(entity, monetizationType, this.config.categoryMappings);
113
+
114
+ // Resolve transaction type using config mapping or default to 'income'
115
+ const transactionType = this.config.transactionTypeMapping?.subscription
116
+ || this.config.transactionTypeMapping?.[monetizationType]
117
+ || TRANSACTION_TYPE.INCOME;
118
+
102
119
  // Create transaction record
103
120
  const TransactionModel = this.models.Transaction;
104
121
  transaction = await TransactionModel.create({
@@ -106,8 +123,9 @@ export class SubscriptionService {
106
123
  customerId: data.customerId || null,
107
124
  amount,
108
125
  currency,
109
- category: 'platform_subscription',
110
- type: 'credit',
126
+ category,
127
+ type: transactionType,
128
+ method: paymentData?.method || 'manual',
111
129
  status: paymentIntent.status === 'succeeded' ? 'verified' : 'pending',
112
130
  gateway: {
113
131
  type: gateway,
@@ -121,6 +139,8 @@ export class SubscriptionService {
121
139
  metadata: {
122
140
  ...metadata,
123
141
  planKey,
142
+ entity,
143
+ monetizationType,
124
144
  paymentIntentId: paymentIntent.id,
125
145
  },
126
146
  idempotencyKey: idempotencyKey || `sub_${nanoid(16)}`,
@@ -146,6 +166,8 @@ export class SubscriptionService {
146
166
  metadata: {
147
167
  ...metadata,
148
168
  isFree,
169
+ entity,
170
+ monetizationType,
149
171
  },
150
172
  ...data,
151
173
  });
@@ -218,35 +240,41 @@ export class SubscriptionService {
218
240
  *
219
241
  * @param {String} subscriptionId - Subscription ID
220
242
  * @param {Object} params - Renewal parameters
243
+ * @param {String} params.gateway - Payment gateway to use (default: 'manual')
244
+ * @param {String} params.entity - Logical entity identifier (optional, inherits from subscription)
245
+ * @param {Object} params.paymentData - Payment method details
246
+ * @param {Object} params.metadata - Additional metadata
247
+ * @param {String} params.idempotencyKey - Idempotency key for duplicate prevention
221
248
  * @returns {Promise<Object>} { subscription, transaction, paymentIntent }
222
249
  */
223
250
  async renew(subscriptionId, params = {}) {
224
251
  const {
225
252
  gateway = 'manual',
253
+ entity = null,
226
254
  paymentData,
227
255
  metadata = {},
228
256
  idempotencyKey = null,
229
257
  } = params;
230
258
 
231
259
  if (!this.models.Subscription) {
232
- throw new Error('Subscription model not registered');
260
+ throw new ModelNotRegisteredError('Subscription');
233
261
  }
234
262
 
235
263
  const SubscriptionModel = this.models.Subscription;
236
264
  const subscription = await SubscriptionModel.findById(subscriptionId);
237
265
 
238
266
  if (!subscription) {
239
- throw new Error('Subscription not found');
267
+ throw new SubscriptionNotFoundError(subscriptionId);
240
268
  }
241
269
 
242
270
  if (subscription.amount === 0) {
243
- throw new Error('Free subscriptions do not require renewal');
271
+ throw new InvalidAmountError(0, 'Free subscriptions do not require renewal');
244
272
  }
245
273
 
246
274
  // Get provider
247
275
  const provider = this.providers[gateway];
248
276
  if (!provider) {
249
- throw new Error(`Payment provider "${gateway}" not found`);
277
+ throw new ProviderNotFoundError(gateway, Object.keys(this.providers));
250
278
  }
251
279
 
252
280
  // Create payment intent
@@ -260,6 +288,17 @@ export class SubscriptionService {
260
288
  },
261
289
  });
262
290
 
291
+ // Resolve category - use provided entity or inherit from subscription metadata
292
+ const effectiveEntity = entity || subscription.metadata?.entity;
293
+ const effectiveMonetizationType = subscription.metadata?.monetizationType || MONETIZATION_TYPES.SUBSCRIPTION;
294
+ const category = resolveCategory(effectiveEntity, effectiveMonetizationType, this.config.categoryMappings);
295
+
296
+ // Resolve transaction type using config mapping or default to 'income'
297
+ const transactionType = this.config.transactionTypeMapping?.subscription_renewal
298
+ || this.config.transactionTypeMapping?.subscription
299
+ || this.config.transactionTypeMapping?.[effectiveMonetizationType]
300
+ || TRANSACTION_TYPE.INCOME;
301
+
263
302
  // Create transaction
264
303
  const TransactionModel = this.models.Transaction;
265
304
  const transaction = await TransactionModel.create({
@@ -267,8 +306,9 @@ export class SubscriptionService {
267
306
  customerId: subscription.customerId,
268
307
  amount: subscription.amount,
269
308
  currency: subscription.currency || 'BDT',
270
- category: 'platform_subscription',
271
- type: 'credit',
309
+ category,
310
+ type: transactionType,
311
+ method: paymentData?.method || 'manual',
272
312
  status: paymentIntent.status === 'succeeded' ? 'verified' : 'pending',
273
313
  gateway: {
274
314
  type: gateway,
@@ -282,6 +322,8 @@ export class SubscriptionService {
282
322
  metadata: {
283
323
  ...metadata,
284
324
  subscriptionId: subscription._id.toString(),
325
+ entity: effectiveEntity,
326
+ monetizationType: effectiveMonetizationType,
285
327
  isRenewal: true,
286
328
  paymentIntentId: paymentIntent.id,
287
329
  },
@@ -322,14 +364,14 @@ export class SubscriptionService {
322
364
  const { immediate = false, reason = null } = options;
323
365
 
324
366
  if (!this.models.Subscription) {
325
- throw new Error('Subscription model not registered');
367
+ throw new ModelNotRegisteredError('Subscription');
326
368
  }
327
369
 
328
370
  const SubscriptionModel = this.models.Subscription;
329
371
  const subscription = await SubscriptionModel.findById(subscriptionId);
330
372
 
331
373
  if (!subscription) {
332
- throw new Error('Subscription not found');
374
+ throw new SubscriptionNotFoundError(subscriptionId);
333
375
  }
334
376
 
335
377
  const now = new Date();
@@ -369,18 +411,18 @@ export class SubscriptionService {
369
411
  const { reason = null } = options;
370
412
 
371
413
  if (!this.models.Subscription) {
372
- throw new Error('Subscription model not registered');
414
+ throw new ModelNotRegisteredError('Subscription');
373
415
  }
374
416
 
375
417
  const SubscriptionModel = this.models.Subscription;
376
418
  const subscription = await SubscriptionModel.findById(subscriptionId);
377
419
 
378
420
  if (!subscription) {
379
- throw new Error('Subscription not found');
421
+ throw new SubscriptionNotFoundError(subscriptionId);
380
422
  }
381
423
 
382
424
  if (!subscription.isActive) {
383
- throw new Error('Only active subscriptions can be paused');
425
+ throw new SubscriptionNotActiveError(subscriptionId, 'Only active subscriptions can be paused');
384
426
  }
385
427
 
386
428
  const pausedAt = new Date();
@@ -412,18 +454,23 @@ export class SubscriptionService {
412
454
  const { extendPeriod = false } = options;
413
455
 
414
456
  if (!this.models.Subscription) {
415
- throw new Error('Subscription model not registered');
457
+ throw new ModelNotRegisteredError('Subscription');
416
458
  }
417
459
 
418
460
  const SubscriptionModel = this.models.Subscription;
419
461
  const subscription = await SubscriptionModel.findById(subscriptionId);
420
462
 
421
463
  if (!subscription) {
422
- throw new Error('Subscription not found');
464
+ throw new SubscriptionNotFoundError(subscriptionId);
423
465
  }
424
466
 
425
467
  if (!subscription.pausedAt) {
426
- throw new Error('Only paused subscriptions can be resumed');
468
+ throw new InvalidStateTransitionError(
469
+ 'resume',
470
+ 'paused',
471
+ subscription.status,
472
+ 'Only paused subscriptions can be resumed'
473
+ );
427
474
  }
428
475
 
429
476
  const now = new Date();
@@ -463,7 +510,7 @@ export class SubscriptionService {
463
510
  */
464
511
  async list(filters = {}, options = {}) {
465
512
  if (!this.models.Subscription) {
466
- throw new Error('Subscription model not registered');
513
+ throw new ModelNotRegisteredError('Subscription');
467
514
  }
468
515
 
469
516
  const SubscriptionModel = this.models.Subscription;
@@ -486,14 +533,14 @@ export class SubscriptionService {
486
533
  */
487
534
  async get(subscriptionId) {
488
535
  if (!this.models.Subscription) {
489
- throw new Error('Subscription model not registered');
536
+ throw new ModelNotRegisteredError('Subscription');
490
537
  }
491
538
 
492
539
  const SubscriptionModel = this.models.Subscription;
493
540
  const subscription = await SubscriptionModel.findById(subscriptionId);
494
541
 
495
542
  if (!subscription) {
496
- throw new Error('Subscription not found');
543
+ throw new SubscriptionNotFoundError(subscriptionId);
497
544
  }
498
545
 
499
546
  return subscription;