@classytic/revenue 0.0.23 → 0.0.24

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/README.md CHANGED
@@ -309,6 +309,43 @@ const pending = await Transaction.find({ 'commission.status': 'pending' });
309
309
 
310
310
  **See:** [`examples/commission-tracking.js`](examples/commission-tracking.js) for complete guide.
311
311
 
312
+ ## Subscription Utilities
313
+
314
+ Universal helpers for period calculation, proration, and action eligibility:
315
+
316
+ ```javascript
317
+ import {
318
+ // Period calculation
319
+ calculatePeriodRange,
320
+ calculateProratedAmount,
321
+ addDuration,
322
+
323
+ // Action eligibility
324
+ canRenewSubscription,
325
+ canPauseSubscription,
326
+ isSubscriptionActive,
327
+ } from '@classytic/revenue/utils';
328
+
329
+ // Calculate period
330
+ const { startDate, endDate } = calculatePeriodRange({
331
+ duration: 30,
332
+ unit: 'days',
333
+ });
334
+
335
+ // Calculate prorated refund
336
+ const refund = calculateProratedAmount({
337
+ amountPaid: 1500,
338
+ startDate: sub.startDate,
339
+ endDate: sub.endDate,
340
+ asOfDate: new Date(),
341
+ });
342
+
343
+ // Check eligibility
344
+ if (canRenewSubscription(membership)) {
345
+ await revenue.subscriptions.renew(membership.subscriptionId);
346
+ }
347
+ ```
348
+
312
349
  ## Polymorphic References
313
350
 
314
351
  Link transactions to any entity (Order, Subscription, Enrollment):
@@ -448,11 +485,8 @@ const subscription = await revenue.subscriptions.create({ ... });
448
485
 
449
486
  - [`examples/basic-usage.js`](examples/basic-usage.js) - Quick start guide
450
487
  - [`examples/transaction.model.js`](examples/transaction.model.js) - Complete model setup
451
- - [`examples/transaction-type-mapping.js`](examples/transaction-type-mapping.js) - Income/expense configuration
452
- - [`examples/complete-flow.js`](examples/complete-flow.js) - Full lifecycle with state management
453
- - [`examples/commission-tracking.js`](examples/commission-tracking.js) - Platform commission calculation
454
- - [`examples/polymorphic-references.js`](examples/polymorphic-references.js) - Link transactions to entities
455
- - [`examples/multivendor-platform.js`](examples/multivendor-platform.js) - Multi-tenant setup
488
+ - [`examples/complete-flow.js`](examples/complete-flow.js) - Full lifecycle (types, refs, state)
489
+ - [`examples/commission-tracking.js`](examples/commission-tracking.js) - Commission calculation
456
490
 
457
491
  ## Error Handling
458
492
 
package/index.js CHANGED
@@ -49,6 +49,16 @@ export {
49
49
  setLogger,
50
50
  calculateCommission,
51
51
  reverseCommission,
52
+ // Subscription utilities
53
+ addDuration,
54
+ calculatePeriodRange,
55
+ calculateProratedAmount,
56
+ resolveIntervalToDuration,
57
+ isSubscriptionActive,
58
+ canRenewSubscription,
59
+ canCancelSubscription,
60
+ canPauseSubscription,
61
+ canResumeSubscription,
52
62
  } from './utils/index.js';
53
63
 
54
64
  // ============ DEFAULT EXPORT ============
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/revenue",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
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/utils/index.d.ts CHANGED
@@ -100,6 +100,37 @@ export function reverseCommission(
100
100
  refundAmount: number
101
101
  ): CommissionObject | null;
102
102
 
103
+ // ============ SUBSCRIPTION UTILITIES ============
104
+
105
+ export function addDuration(startDate: Date, duration: number, unit?: string): Date;
106
+
107
+ export function calculatePeriodRange(params: {
108
+ currentEndDate?: Date | null;
109
+ startDate?: Date | null;
110
+ duration: number;
111
+ unit?: string;
112
+ now?: Date;
113
+ }): { startDate: Date; endDate: Date };
114
+
115
+ export function calculateProratedAmount(params: {
116
+ amountPaid: number;
117
+ startDate: Date;
118
+ endDate: Date;
119
+ asOfDate?: Date;
120
+ precision?: number;
121
+ }): number;
122
+
123
+ export function resolveIntervalToDuration(
124
+ interval?: string,
125
+ intervalCount?: number
126
+ ): { duration: number; unit: string };
127
+
128
+ export function isSubscriptionActive(subscription: any): boolean;
129
+ export function canRenewSubscription(entity: any): boolean;
130
+ export function canCancelSubscription(entity: any): boolean;
131
+ export function canPauseSubscription(entity: any): boolean;
132
+ export function canResumeSubscription(entity: any): boolean;
133
+
103
134
  // ============ DEFAULT EXPORT ============
104
135
 
105
136
  declare const _default: {
@@ -119,6 +150,15 @@ declare const _default: {
119
150
  triggerHook: typeof triggerHook;
120
151
  calculateCommission: typeof calculateCommission;
121
152
  reverseCommission: typeof reverseCommission;
153
+ addDuration: typeof addDuration;
154
+ calculatePeriodRange: typeof calculatePeriodRange;
155
+ calculateProratedAmount: typeof calculateProratedAmount;
156
+ resolveIntervalToDuration: typeof resolveIntervalToDuration;
157
+ isSubscriptionActive: typeof isSubscriptionActive;
158
+ canRenewSubscription: typeof canRenewSubscription;
159
+ canCancelSubscription: typeof canCancelSubscription;
160
+ canPauseSubscription: typeof canPauseSubscription;
161
+ canResumeSubscription: typeof canResumeSubscription;
122
162
  };
123
163
 
124
164
  export default _default;
package/utils/index.js CHANGED
@@ -7,3 +7,4 @@ export * from './transaction-type.js';
7
7
  export { default as logger, setLogger } from './logger.js';
8
8
  export { triggerHook } from './hooks.js';
9
9
  export { calculateCommission, reverseCommission } from './commission.js';
10
+ export * from './subscription/index.js';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Subscription Action Utilities
3
+ * @classytic/revenue/utils/subscription
4
+ *
5
+ * Eligibility checks for subscription actions
6
+ */
7
+
8
+ import { SUBSCRIPTION_STATUS } from '../../enums/subscription.enums.js';
9
+
10
+ /**
11
+ * Check if subscription is active
12
+ */
13
+ export function isSubscriptionActive(subscription) {
14
+ if (!subscription) return false;
15
+ if (!subscription.isActive) return false;
16
+
17
+ if (subscription.endDate) {
18
+ const now = new Date();
19
+ const endDate = new Date(subscription.endDate);
20
+ if (endDate < now) return false;
21
+ }
22
+
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * Check if can renew
28
+ */
29
+ export function canRenewSubscription(entity) {
30
+ if (!entity || !entity.subscription) return false;
31
+ return isSubscriptionActive(entity.subscription);
32
+ }
33
+
34
+ /**
35
+ * Check if can cancel
36
+ */
37
+ export function canCancelSubscription(entity) {
38
+ if (!entity || !entity.subscription) return false;
39
+ if (!isSubscriptionActive(entity.subscription)) return false;
40
+ return !entity.subscription.canceledAt;
41
+ }
42
+
43
+ /**
44
+ * Check if can pause
45
+ */
46
+ export function canPauseSubscription(entity) {
47
+ if (!entity || !entity.subscription) return false;
48
+ if (entity.status === SUBSCRIPTION_STATUS.PAUSED) return false;
49
+ if (entity.status === SUBSCRIPTION_STATUS.CANCELLED) return false;
50
+ return isSubscriptionActive(entity.subscription);
51
+ }
52
+
53
+ /**
54
+ * Check if can resume
55
+ */
56
+ export function canResumeSubscription(entity) {
57
+ if (!entity || !entity.subscription) return false;
58
+ return entity.status === SUBSCRIPTION_STATUS.PAUSED;
59
+ }
60
+
61
+ export default {
62
+ isSubscriptionActive,
63
+ canRenewSubscription,
64
+ canCancelSubscription,
65
+ canPauseSubscription,
66
+ canResumeSubscription,
67
+ };
68
+
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Subscription Utilities Index
3
+ * @classytic/revenue/utils/subscription
4
+ */
5
+
6
+ export {
7
+ addDuration,
8
+ calculatePeriodRange,
9
+ calculateProratedAmount,
10
+ resolveIntervalToDuration,
11
+ } from './period.js';
12
+
13
+ export {
14
+ isSubscriptionActive,
15
+ canRenewSubscription,
16
+ canCancelSubscription,
17
+ canPauseSubscription,
18
+ canResumeSubscription,
19
+ } from './actions.js';
20
+
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Subscription Period Utilities
3
+ * @classytic/revenue/utils/subscription
4
+ *
5
+ * Universal period calculation, proration, and date utilities
6
+ */
7
+
8
+ /**
9
+ * Add duration to date
10
+ */
11
+ export function addDuration(startDate, duration, unit = 'days') {
12
+ const date = new Date(startDate);
13
+
14
+ switch (unit) {
15
+ case 'months':
16
+ case 'month':
17
+ date.setMonth(date.getMonth() + duration);
18
+ return date;
19
+ case 'years':
20
+ case 'year':
21
+ date.setFullYear(date.getFullYear() + duration);
22
+ return date;
23
+ case 'weeks':
24
+ case 'week':
25
+ date.setDate(date.getDate() + (duration * 7));
26
+ return date;
27
+ case 'days':
28
+ case 'day':
29
+ default:
30
+ date.setDate(date.getDate() + duration);
31
+ return date;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Calculate subscription period start/end dates
37
+ */
38
+ export function calculatePeriodRange({
39
+ currentEndDate = null,
40
+ startDate = null,
41
+ duration,
42
+ unit = 'days',
43
+ now = new Date(),
44
+ }) {
45
+ let periodStart;
46
+
47
+ if (startDate) {
48
+ periodStart = new Date(startDate);
49
+ } else if (currentEndDate) {
50
+ const end = new Date(currentEndDate);
51
+ periodStart = end > now ? end : now;
52
+ } else {
53
+ periodStart = now;
54
+ }
55
+
56
+ const periodEnd = addDuration(periodStart, duration, unit);
57
+
58
+ return { startDate: periodStart, endDate: periodEnd };
59
+ }
60
+
61
+ /**
62
+ * Calculate prorated refund amount for unused period
63
+ */
64
+ export function calculateProratedAmount({
65
+ amountPaid,
66
+ startDate,
67
+ endDate,
68
+ asOfDate = new Date(),
69
+ precision = 2,
70
+ }) {
71
+ if (!amountPaid || amountPaid <= 0) return 0;
72
+
73
+ const start = new Date(startDate);
74
+ const end = new Date(endDate);
75
+ const asOf = new Date(asOfDate);
76
+
77
+ const totalMs = end - start;
78
+ if (totalMs <= 0) return 0;
79
+
80
+ const remainingMs = Math.max(0, end - asOf);
81
+ if (remainingMs <= 0) return 0;
82
+
83
+ const ratio = remainingMs / totalMs;
84
+ const amount = amountPaid * ratio;
85
+
86
+ const factor = 10 ** precision;
87
+ return Math.round(amount * factor) / factor;
88
+ }
89
+
90
+ /**
91
+ * Convert interval + count to duration/unit
92
+ */
93
+ export function resolveIntervalToDuration(interval = 'month', intervalCount = 1) {
94
+ const normalized = (interval || 'month').toLowerCase();
95
+ const count = Number(intervalCount) > 0 ? Number(intervalCount) : 1;
96
+
97
+ switch (normalized) {
98
+ case 'year':
99
+ case 'years':
100
+ return { duration: count, unit: 'years' };
101
+ case 'week':
102
+ case 'weeks':
103
+ return { duration: count, unit: 'weeks' };
104
+ case 'quarter':
105
+ case 'quarters':
106
+ return { duration: count * 3, unit: 'months' };
107
+ case 'day':
108
+ case 'days':
109
+ return { duration: count, unit: 'days' };
110
+ case 'month':
111
+ case 'months':
112
+ default:
113
+ return { duration: count, unit: 'months' };
114
+ }
115
+ }
116
+
117
+ export default {
118
+ addDuration,
119
+ calculatePeriodRange,
120
+ calculateProratedAmount,
121
+ resolveIntervalToDuration,
122
+ };
123
+