@classytic/revenue 0.0.1

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/utils/index.js ADDED
@@ -0,0 +1,8 @@
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';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Logger Abstraction for Monetization Library
3
+ *
4
+ * Defaults to console for standalone usage
5
+ * Can be overridden with custom logger (pino, winston, etc)
6
+ *
7
+ * Usage:
8
+ * ```javascript
9
+ * import { setLogger } from '@fitverse/monetization';
10
+ *
11
+ * // Optional: Use your own logger
12
+ * setLogger(myPinoLogger);
13
+ * ```
14
+ */
15
+
16
+ let _logger = console;
17
+
18
+ /**
19
+ * Set custom logger implementation
20
+ * @param {Object} customLogger - Logger instance with info, warn, error, debug methods
21
+ */
22
+ export function setLogger(customLogger) {
23
+ _logger = customLogger;
24
+ }
25
+
26
+ /**
27
+ * Logger proxy - delegates to current logger implementation
28
+ */
29
+ export const logger = {
30
+ info: (...args) => _logger.info?.(...args) || _logger.log?.('INFO:', ...args),
31
+ warn: (...args) => _logger.warn?.(...args) || _logger.log?.('WARN:', ...args),
32
+ error: (...args) => _logger.error?.(...args) || _logger.log?.('ERROR:', ...args),
33
+ debug: (...args) => _logger.debug?.(...args) || _logger.log?.('DEBUG:', ...args),
34
+ };
35
+
36
+ export default logger;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Transaction Type Detection & Classification
3
+ *
4
+ * Distinguishes between:
5
+ * - Monetization-managed transactions (library-controlled, strict rules)
6
+ * - Manual admin transactions (flexible, admin-controlled)
7
+ *
8
+ * @module @classytic/revenue/utils/transaction-type
9
+ */
10
+
11
+ /**
12
+ * Transaction types with different protection rules
13
+ */
14
+ export const TRANSACTION_TYPE = {
15
+ MONETIZATION: 'monetization', // Library-managed (subscriptions, purchases)
16
+ MANUAL: 'manual', // Admin-managed (expenses, income, adjustments)
17
+ };
18
+
19
+ /**
20
+ * Default monetization categories
21
+ * Users can extend this via config.categoryMappings
22
+ */
23
+ const DEFAULT_MONETIZATION_CATEGORIES = [
24
+ 'subscription',
25
+ 'purchase',
26
+ ];
27
+
28
+ /**
29
+ * Check if category is monetization-related
30
+ * @param {string} category - Transaction category
31
+ * @param {Array<string>} additionalCategories - Additional categories from user config
32
+ * @returns {boolean}
33
+ */
34
+ function isMonetizationCategory(category, additionalCategories = []) {
35
+ const allCategories = [...DEFAULT_MONETIZATION_CATEGORIES, ...additionalCategories];
36
+ return allCategories.includes(category);
37
+ }
38
+
39
+ /**
40
+ * Check if transaction is monetization-managed
41
+ *
42
+ * Monetization-managed means:
43
+ * - Created through subscription/purchase flows via the library
44
+ * - Status controlled by payment webhooks/verification
45
+ * - Amount/commission calculated by library
46
+ * - Protected fields: status, amount, commission, gateway, verifiedAt, verifiedBy
47
+ *
48
+ * @param {Object} transaction - Transaction document or data
49
+ * @param {Object} options - Options
50
+ * @param {Array<string>} options.targetModels - Target models from config (default: ['Subscription', 'Membership'])
51
+ * @param {Array<string>} options.additionalCategories - Additional categories from user config
52
+ * @returns {boolean}
53
+ */
54
+ export function isMonetizationTransaction(transaction, options = {}) {
55
+ const {
56
+ targetModels = ['Subscription', 'Membership'],
57
+ additionalCategories = [],
58
+ } = options;
59
+
60
+ // Check 1: Has referenceModel from registered models
61
+ if (transaction.referenceModel && targetModels.includes(transaction.referenceModel)) {
62
+ return true;
63
+ }
64
+
65
+ // Check 2: Category is monetization-related
66
+ if (transaction.category) {
67
+ return isMonetizationCategory(transaction.category, additionalCategories);
68
+ }
69
+
70
+ return false;
71
+ }
72
+
73
+ /**
74
+ * Check if transaction is manual admin transaction
75
+ *
76
+ * Manual transactions:
77
+ * - Created directly by admins for operational expenses/income
78
+ * - Can be self-verified by admins
79
+ * - More flexible updates allowed
80
+ * - No commission/gateway complexity
81
+ *
82
+ * @param {Object} transaction - Transaction document or data
83
+ * @param {Object} options - Options (same as isMonetizationTransaction)
84
+ * @returns {boolean}
85
+ */
86
+ export function isManualTransaction(transaction, options = {}) {
87
+ return !isMonetizationTransaction(transaction, options);
88
+ }
89
+
90
+ /**
91
+ * Get transaction type
92
+ *
93
+ * @param {Object} transaction - Transaction document or data
94
+ * @param {Object} options - Options (same as isMonetizationTransaction)
95
+ * @returns {string} TRANSACTION_TYPE.MONETIZATION or TRANSACTION_TYPE.MANUAL
96
+ */
97
+ export function getTransactionType(transaction, options = {}) {
98
+ return isMonetizationTransaction(transaction, options)
99
+ ? TRANSACTION_TYPE.MONETIZATION
100
+ : TRANSACTION_TYPE.MANUAL;
101
+ }
102
+
103
+ /**
104
+ * Protected fields for monetization transactions
105
+ * These fields cannot be updated directly by admins
106
+ */
107
+ export const PROTECTED_MONETIZATION_FIELDS = [
108
+ 'status',
109
+ 'amount',
110
+ 'platformCommission',
111
+ 'netAmount',
112
+ 'verifiedAt',
113
+ 'verifiedBy',
114
+ 'gateway',
115
+ 'webhook',
116
+ 'metadata.commission',
117
+ 'metadata.gateway',
118
+ 'type',
119
+ 'category',
120
+ 'referenceModel',
121
+ 'referenceId',
122
+ ];
123
+
124
+ /**
125
+ * Editable fields for monetization transactions (before verification)
126
+ * These fields can be updated by frontend/customer before payment is verified
127
+ */
128
+ export const EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION = [
129
+ 'reference',
130
+ 'paymentDetails',
131
+ 'notes',
132
+ ];
133
+
134
+ /**
135
+ * Allowed fields for manual transaction creation
136
+ */
137
+ export const MANUAL_TRANSACTION_CREATE_FIELDS = [
138
+ 'organizationId',
139
+ 'type',
140
+ 'category',
141
+ 'amount',
142
+ 'method',
143
+ 'reference',
144
+ 'paymentDetails',
145
+ 'notes',
146
+ 'date', // Transaction date (can be backdated)
147
+ 'description',
148
+ ];
149
+
150
+ /**
151
+ * Allowed fields for manual transaction updates
152
+ */
153
+ export const MANUAL_TRANSACTION_UPDATE_FIELDS = [
154
+ 'amount',
155
+ 'method',
156
+ 'reference',
157
+ 'paymentDetails',
158
+ 'notes',
159
+ 'date',
160
+ 'description',
161
+ ];
162
+
163
+ /**
164
+ * Get allowed update fields based on transaction type and status
165
+ *
166
+ * @param {Object} transaction - Transaction document
167
+ * @param {Object} options - Options for transaction type detection
168
+ * @returns {Array<string>} Allowed field names
169
+ */
170
+ export function getAllowedUpdateFields(transaction, options = {}) {
171
+ const type = getTransactionType(transaction, options);
172
+
173
+ if (type === TRANSACTION_TYPE.MONETIZATION) {
174
+ // Monetization transactions: only allow pre-verification edits
175
+ if (transaction.status === 'pending') {
176
+ return EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION;
177
+ }
178
+ // After verification, no direct updates allowed
179
+ return [];
180
+ }
181
+
182
+ // Manual transactions: more flexible
183
+ if (transaction.status === 'verified' || transaction.status === 'completed') {
184
+ // Once verified/completed, only notes can be updated
185
+ return ['notes'];
186
+ }
187
+
188
+ // Pending manual transactions can be fully edited
189
+ return MANUAL_TRANSACTION_UPDATE_FIELDS;
190
+ }
191
+
192
+ /**
193
+ * Validate if field update is allowed
194
+ *
195
+ * @param {Object} transaction - Transaction document
196
+ * @param {string} fieldName - Field being updated
197
+ * @param {Object} options - Options for transaction type detection
198
+ * @returns {Object} { allowed: boolean, reason?: string }
199
+ */
200
+ export function validateFieldUpdate(transaction, fieldName, options = {}) {
201
+ const allowedFields = getAllowedUpdateFields(transaction, options);
202
+
203
+ if (allowedFields.includes(fieldName)) {
204
+ return { allowed: true };
205
+ }
206
+
207
+ const type = getTransactionType(transaction, options);
208
+
209
+ if (type === TRANSACTION_TYPE.MONETIZATION) {
210
+ if (PROTECTED_MONETIZATION_FIELDS.includes(fieldName)) {
211
+ return {
212
+ allowed: false,
213
+ reason: `Field "${fieldName}" is protected for monetization transactions. Updates must go through payment flow.`,
214
+ };
215
+ }
216
+ }
217
+
218
+ return {
219
+ allowed: false,
220
+ reason: `Field "${fieldName}" cannot be updated for ${transaction.status} transactions.`,
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Check if transaction can be self-verified by admin
226
+ *
227
+ * @param {Object} transaction - Transaction document
228
+ * @param {Object} options - Options for transaction type detection
229
+ * @returns {boolean}
230
+ */
231
+ export function canSelfVerify(transaction, options = {}) {
232
+ const type = getTransactionType(transaction, options);
233
+
234
+ // Only manual transactions can be self-verified
235
+ if (type === TRANSACTION_TYPE.MANUAL) {
236
+ return transaction.status === 'pending';
237
+ }
238
+
239
+ return false;
240
+ }
241
+
242
+ export default {
243
+ TRANSACTION_TYPE,
244
+ isMonetizationTransaction,
245
+ isManualTransaction,
246
+ getTransactionType,
247
+ PROTECTED_MONETIZATION_FIELDS,
248
+ EDITABLE_MONETIZATION_FIELDS_PRE_VERIFICATION,
249
+ MANUAL_TRANSACTION_CREATE_FIELDS,
250
+ MANUAL_TRANSACTION_UPDATE_FIELDS,
251
+ getAllowedUpdateFields,
252
+ validateFieldUpdate,
253
+ canSelfVerify,
254
+ };