@classytic/revenue 0.0.22 → 0.0.23

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.
@@ -19,6 +19,7 @@ import {
19
19
  } from '../core/errors.js';
20
20
  import { triggerHook } from '../utils/hooks.js';
21
21
  import { resolveCategory } from '../utils/category-resolver.js';
22
+ import { calculateCommission } from '../utils/commission.js';
22
23
  import { MONETIZATION_TYPES } from '../enums/monetization.enums.js';
23
24
  import { TRANSACTION_TYPE } from '../enums/transaction.enums.js';
24
25
 
@@ -40,7 +41,7 @@ export class SubscriptionService {
40
41
  * Create a new subscription
41
42
  *
42
43
  * @param {Object} params - Subscription parameters
43
- * @param {Object} params.data - Subscription data (organizationId, customerId, etc.)
44
+ * @param {Object} params.data - Subscription data (organizationId, customerId, referenceId, referenceModel, etc.)
44
45
  * @param {String} params.planKey - Plan key ('monthly', 'quarterly', 'yearly')
45
46
  * @param {Number} params.amount - Subscription amount
46
47
  * @param {String} params.currency - Currency code (default: 'BDT')
@@ -51,6 +52,20 @@ export class SubscriptionService {
51
52
  * @param {Object} params.paymentData - Payment method details
52
53
  * @param {Object} params.metadata - Additional metadata
53
54
  * @param {String} params.idempotencyKey - Idempotency key for duplicate prevention
55
+ *
56
+ * @example
57
+ * // With polymorphic reference (recommended)
58
+ * await revenue.subscriptions.create({
59
+ * data: {
60
+ * organizationId: '...',
61
+ * customerId: '...',
62
+ * referenceId: subscription._id, // Links to entity
63
+ * referenceModel: 'Subscription', // Model name
64
+ * },
65
+ * amount: 1500,
66
+ * // ...
67
+ * });
68
+ *
54
69
  * @returns {Promise<Object>} { subscription, transaction, paymentIntent }
55
70
  */
56
71
  async create(params) {
@@ -116,6 +131,11 @@ export class SubscriptionService {
116
131
  || this.config.transactionTypeMapping?.[monetizationType]
117
132
  || TRANSACTION_TYPE.INCOME;
118
133
 
134
+ // Calculate commission if configured
135
+ const commissionRate = this.config.commissionRates?.[category] || 0;
136
+ const gatewayFeeRate = this.config.gatewayFeeRates?.[gateway] || 0;
137
+ const commission = calculateCommission(amount, commissionRate, gatewayFeeRate);
138
+
119
139
  // Create transaction record
120
140
  const TransactionModel = this.models.Transaction;
121
141
  transaction = await TransactionModel.create({
@@ -136,6 +156,10 @@ export class SubscriptionService {
136
156
  provider: gateway,
137
157
  ...paymentData,
138
158
  },
159
+ ...(commission && { commission }), // Only include if commission exists
160
+ // Polymorphic reference (top-level, not metadata)
161
+ ...(data.referenceId && { referenceId: data.referenceId }),
162
+ ...(data.referenceModel && { referenceModel: data.referenceModel }),
139
163
  metadata: {
140
164
  ...metadata,
141
165
  planKey,
@@ -152,7 +176,8 @@ export class SubscriptionService {
152
176
  if (this.models.Subscription) {
153
177
  const SubscriptionModel = this.models.Subscription;
154
178
 
155
- subscription = await SubscriptionModel.create({
179
+ // Create subscription with proper reference tracking
180
+ const subscriptionData = {
156
181
  organizationId: data.organizationId,
157
182
  customerId: data.customerId || null,
158
183
  planKey,
@@ -170,7 +195,13 @@ export class SubscriptionService {
170
195
  monetizationType,
171
196
  },
172
197
  ...data,
173
- });
198
+ };
199
+
200
+ // Remove referenceId/referenceModel from subscription (they're for transactions)
201
+ delete subscriptionData.referenceId;
202
+ delete subscriptionData.referenceModel;
203
+
204
+ subscription = await SubscriptionModel.create(subscriptionData);
174
205
  }
175
206
 
176
207
  // Trigger hook
@@ -299,6 +330,11 @@ export class SubscriptionService {
299
330
  || this.config.transactionTypeMapping?.[effectiveMonetizationType]
300
331
  || TRANSACTION_TYPE.INCOME;
301
332
 
333
+ // Calculate commission if configured
334
+ const commissionRate = this.config.commissionRates?.[category] || 0;
335
+ const gatewayFeeRate = this.config.gatewayFeeRates?.[gateway] || 0;
336
+ const commission = calculateCommission(subscription.amount, commissionRate, gatewayFeeRate);
337
+
302
338
  // Create transaction
303
339
  const TransactionModel = this.models.Transaction;
304
340
  const transaction = await TransactionModel.create({
@@ -319,9 +355,13 @@ export class SubscriptionService {
319
355
  provider: gateway,
320
356
  ...paymentData,
321
357
  },
358
+ ...(commission && { commission }), // Only include if commission exists
359
+ // Polymorphic reference to subscription
360
+ referenceId: subscription._id,
361
+ referenceModel: 'Subscription',
322
362
  metadata: {
323
363
  ...metadata,
324
- subscriptionId: subscription._id.toString(),
364
+ subscriptionId: subscription._id.toString(), // Keep for backward compat
325
365
  entity: effectiveEntity,
326
366
  monetizationType: effectiveMonetizationType,
327
367
  isRenewal: true,
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Commission Calculation Utility
3
+ * @classytic/revenue
4
+ *
5
+ * Handles platform commission calculation with gateway fee deduction
6
+ */
7
+
8
+ /**
9
+ * Build commission object for transaction
10
+ *
11
+ * @param {Number} amount - Transaction amount
12
+ * @param {Number} commissionRate - Commission rate (0 to 1, e.g., 0.10 for 10%)
13
+ * @param {Number} gatewayFeeRate - Gateway fee rate (0 to 1, e.g., 0.018 for 1.8%)
14
+ * @returns {Object} Commission object or null
15
+ */
16
+ export function calculateCommission(amount, commissionRate, gatewayFeeRate = 0) {
17
+ // No commission if rate is 0 or negative
18
+ if (!commissionRate || commissionRate <= 0) {
19
+ return null;
20
+ }
21
+
22
+ // Validate inputs
23
+ if (amount < 0) {
24
+ throw new Error('Transaction amount cannot be negative');
25
+ }
26
+
27
+ if (commissionRate < 0 || commissionRate > 1) {
28
+ throw new Error('Commission rate must be between 0 and 1');
29
+ }
30
+
31
+ if (gatewayFeeRate < 0 || gatewayFeeRate > 1) {
32
+ throw new Error('Gateway fee rate must be between 0 and 1');
33
+ }
34
+
35
+ // Calculate commission
36
+ const grossAmount = Math.round(amount * commissionRate * 100) / 100; // Round to 2 decimals
37
+ const gatewayFeeAmount = Math.round(amount * gatewayFeeRate * 100) / 100;
38
+ const netAmount = Math.max(0, Math.round((grossAmount - gatewayFeeAmount) * 100) / 100);
39
+
40
+ return {
41
+ rate: commissionRate,
42
+ grossAmount,
43
+ gatewayFeeRate,
44
+ gatewayFeeAmount,
45
+ netAmount,
46
+ status: 'pending',
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Reverse commission on refund (proportional)
52
+ *
53
+ * @param {Object} originalCommission - Original commission object
54
+ * @param {Number} originalAmount - Original transaction amount
55
+ * @param {Number} refundAmount - Amount being refunded
56
+ * @returns {Object} Reversed commission or null
57
+ */
58
+ export function reverseCommission(originalCommission, originalAmount, refundAmount) {
59
+ if (!originalCommission || !originalCommission.netAmount) {
60
+ return null;
61
+ }
62
+
63
+ // Calculate proportional refund
64
+ const refundRatio = refundAmount / originalAmount;
65
+ const reversedNetAmount = Math.round(originalCommission.netAmount * refundRatio * 100) / 100;
66
+ const reversedGrossAmount = Math.round(originalCommission.grossAmount * refundRatio * 100) / 100;
67
+ const reversedGatewayFee = Math.round(originalCommission.gatewayFeeAmount * refundRatio * 100) / 100;
68
+
69
+ return {
70
+ rate: originalCommission.rate,
71
+ grossAmount: reversedGrossAmount,
72
+ gatewayFeeRate: originalCommission.gatewayFeeRate,
73
+ gatewayFeeAmount: reversedGatewayFee,
74
+ netAmount: reversedNetAmount,
75
+ status: 'waived', // Commission waived due to refund
76
+ };
77
+ }
78
+
79
+ export default {
80
+ calculateCommission,
81
+ reverseCommission,
82
+ };
83
+
package/utils/index.d.ts CHANGED
@@ -1,99 +1,124 @@
1
- /**
2
- * TypeScript definitions for @classytic/revenue/utils
3
- * Core utilities
4
- */
5
-
6
- // ============ TRANSACTION TYPE UTILITIES ============
7
-
8
- export const TRANSACTION_TYPE: {
9
- readonly MONETIZATION: 'monetization';
10
- readonly MANUAL: 'manual';
11
- };
12
-
13
- export const PROTECTED_MONETIZATION_FIELDS: readonly string[];
14
- export const EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION: readonly string[];
15
- export const MANUAL_TRANSACTION_CREATE_FIELDS: readonly string[];
16
- export const MANUAL_TRANSACTION_UPDATE_FIELDS: readonly string[];
17
-
18
- export interface TransactionTypeOptions {
19
- targetModels?: string[];
20
- additionalCategories?: string[];
21
- }
22
-
23
- export function isMonetizationTransaction(
24
- transaction: any,
25
- options?: TransactionTypeOptions
26
- ): boolean;
27
-
28
- export function isManualTransaction(
29
- transaction: any,
30
- options?: TransactionTypeOptions
31
- ): boolean;
32
-
33
- export function getTransactionType(
34
- transaction: any,
35
- options?: TransactionTypeOptions
36
- ): 'monetization' | 'manual';
37
-
38
- export function getAllowedUpdateFields(
39
- transaction: any,
40
- options?: TransactionTypeOptions
41
- ): string[];
42
-
43
- export interface FieldValidationResult {
44
- allowed: boolean;
45
- reason?: string;
46
- }
47
-
48
- export function validateFieldUpdate(
49
- transaction: any,
50
- fieldName: string,
51
- options?: TransactionTypeOptions
52
- ): FieldValidationResult;
53
-
54
- export function canSelfVerify(
55
- transaction: any,
56
- options?: TransactionTypeOptions
57
- ): boolean;
58
-
59
- // ============ LOGGER UTILITIES ============
60
-
61
- export interface Logger {
62
- info(...args: any[]): void;
63
- warn(...args: any[]): void;
64
- error(...args: any[]): void;
65
- debug(...args: any[]): void;
66
- }
67
-
68
- export const logger: Logger;
69
- export function setLogger(logger: Logger | Console): void;
70
-
71
- // ============ HOOK UTILITIES ============
72
-
73
- export function triggerHook(
74
- hooks: Record<string, Function[]>,
75
- event: string,
76
- data: any,
77
- logger: Logger
78
- ): void;
79
-
80
- // ============ DEFAULT EXPORT ============
81
-
82
- declare const _default: {
83
- TRANSACTION_TYPE: typeof TRANSACTION_TYPE;
84
- isMonetizationTransaction: typeof isMonetizationTransaction;
85
- isManualTransaction: typeof isManualTransaction;
86
- getTransactionType: typeof getTransactionType;
87
- PROTECTED_MONETIZATION_FIELDS: typeof PROTECTED_MONETIZATION_FIELDS;
88
- EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION: typeof EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION;
89
- MANUAL_TRANSACTION_CREATE_FIELDS: typeof MANUAL_TRANSACTION_CREATE_FIELDS;
90
- MANUAL_TRANSACTION_UPDATE_FIELDS: typeof MANUAL_TRANSACTION_UPDATE_FIELDS;
91
- getAllowedUpdateFields: typeof getAllowedUpdateFields;
92
- validateFieldUpdate: typeof validateFieldUpdate;
93
- canSelfVerify: typeof canSelfVerify;
94
- logger: typeof logger;
95
- setLogger: typeof setLogger;
96
- triggerHook: typeof triggerHook;
97
- };
98
-
99
- export default _default;
1
+ /**
2
+ * TypeScript definitions for @classytic/revenue/utils
3
+ * Core utilities
4
+ */
5
+
6
+ // ============ TRANSACTION TYPE UTILITIES ============
7
+
8
+ export const TRANSACTION_TYPE: {
9
+ readonly MONETIZATION: 'monetization';
10
+ readonly MANUAL: 'manual';
11
+ };
12
+
13
+ export const PROTECTED_MONETIZATION_FIELDS: readonly string[];
14
+ export const EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION: readonly string[];
15
+ export const MANUAL_TRANSACTION_CREATE_FIELDS: readonly string[];
16
+ export const MANUAL_TRANSACTION_UPDATE_FIELDS: readonly string[];
17
+
18
+ export interface TransactionTypeOptions {
19
+ targetModels?: string[];
20
+ additionalCategories?: string[];
21
+ }
22
+
23
+ export function isMonetizationTransaction(
24
+ transaction: any,
25
+ options?: TransactionTypeOptions
26
+ ): boolean;
27
+
28
+ export function isManualTransaction(
29
+ transaction: any,
30
+ options?: TransactionTypeOptions
31
+ ): boolean;
32
+
33
+ export function getTransactionType(
34
+ transaction: any,
35
+ options?: TransactionTypeOptions
36
+ ): 'monetization' | 'manual';
37
+
38
+ export function getAllowedUpdateFields(
39
+ transaction: any,
40
+ options?: TransactionTypeOptions
41
+ ): string[];
42
+
43
+ export interface FieldValidationResult {
44
+ allowed: boolean;
45
+ reason?: string;
46
+ }
47
+
48
+ export function validateFieldUpdate(
49
+ transaction: any,
50
+ fieldName: string,
51
+ options?: TransactionTypeOptions
52
+ ): FieldValidationResult;
53
+
54
+ export function canSelfVerify(
55
+ transaction: any,
56
+ options?: TransactionTypeOptions
57
+ ): boolean;
58
+
59
+ // ============ LOGGER UTILITIES ============
60
+
61
+ export interface Logger {
62
+ info(...args: any[]): void;
63
+ warn(...args: any[]): void;
64
+ error(...args: any[]): void;
65
+ debug(...args: any[]): void;
66
+ }
67
+
68
+ export const logger: Logger;
69
+ export function setLogger(logger: Logger | Console): void;
70
+
71
+ // ============ HOOK UTILITIES ============
72
+
73
+ export function triggerHook(
74
+ hooks: Record<string, Function[]>,
75
+ event: string,
76
+ data: any,
77
+ logger: Logger
78
+ ): void;
79
+
80
+ // ============ COMMISSION UTILITIES ============
81
+
82
+ export interface CommissionObject {
83
+ rate: number;
84
+ grossAmount: number;
85
+ gatewayFeeRate: number;
86
+ gatewayFeeAmount: number;
87
+ netAmount: number;
88
+ status: 'pending' | 'due' | 'paid' | 'waived';
89
+ }
90
+
91
+ export function calculateCommission(
92
+ amount: number,
93
+ commissionRate: number,
94
+ gatewayFeeRate?: number
95
+ ): CommissionObject | null;
96
+
97
+ export function reverseCommission(
98
+ originalCommission: CommissionObject,
99
+ originalAmount: number,
100
+ refundAmount: number
101
+ ): CommissionObject | null;
102
+
103
+ // ============ DEFAULT EXPORT ============
104
+
105
+ declare const _default: {
106
+ TRANSACTION_TYPE: typeof TRANSACTION_TYPE;
107
+ isMonetizationTransaction: typeof isMonetizationTransaction;
108
+ isManualTransaction: typeof isManualTransaction;
109
+ getTransactionType: typeof getTransactionType;
110
+ PROTECTED_MONETIZATION_FIELDS: typeof PROTECTED_MONETIZATION_FIELDS;
111
+ EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION: typeof EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION;
112
+ MANUAL_TRANSACTION_CREATE_FIELDS: typeof MANUAL_TRANSACTION_CREATE_FIELDS;
113
+ MANUAL_TRANSACTION_UPDATE_FIELDS: typeof MANUAL_TRANSACTION_UPDATE_FIELDS;
114
+ getAllowedUpdateFields: typeof getAllowedUpdateFields;
115
+ validateFieldUpdate: typeof validateFieldUpdate;
116
+ canSelfVerify: typeof canSelfVerify;
117
+ logger: typeof logger;
118
+ setLogger: typeof setLogger;
119
+ triggerHook: typeof triggerHook;
120
+ calculateCommission: typeof calculateCommission;
121
+ reverseCommission: typeof reverseCommission;
122
+ };
123
+
124
+ export default _default;
package/utils/index.js CHANGED
@@ -1,8 +1,9 @@
1
- /**
2
- * Core Utilities
3
- * @classytic/revenue
4
- */
5
-
6
- export * from './transaction-type.js';
7
- export { default as logger, setLogger } from './logger.js';
8
- export { triggerHook } from './hooks.js';
1
+ /**
2
+ * Core Utilities
3
+ * @classytic/revenue
4
+ */
5
+
6
+ export * from './transaction-type.js';
7
+ export { default as logger, setLogger } from './logger.js';
8
+ export { triggerHook } from './hooks.js';
9
+ export { calculateCommission, reverseCommission } from './commission.js';