@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 +39 -5
- package/index.js +10 -0
- package/package.json +1 -1
- package/utils/index.d.ts +40 -0
- package/utils/index.js +1 -0
- package/utils/subscription/actions.js +68 -0
- package/utils/subscription/index.js +20 -0
- package/utils/subscription/period.js +123 -0
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/
|
|
452
|
-
- [`examples/
|
|
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
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
|
@@ -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
|
+
|