@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Classytic (Classytic)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,454 @@
1
+ # @classytic/revenue
2
+
3
+ > Enterprise revenue management with subscriptions and payment processing
4
+
5
+ Thin, focused, production-ready library with smart defaults. Built for SaaS, marketplaces, and subscription businesses.
6
+
7
+ ## Features
8
+
9
+ - **Subscriptions**: Create, renew, upgrade, downgrade with smart proration
10
+ - **Payment Processing**: Multi-gateway support (Stripe, SSLCommerz, bKash, manual)
11
+ - **Transaction Management**: Complete lifecycle with verification and refunds
12
+ - **Provider Pattern**: Pluggable payment providers (like AI SDK)
13
+ - **Framework Agnostic**: Works with Fastify, Express, Nest, or standalone
14
+ - **Model Flexible**: Plain Mongoose OR @classytic/mongokit Repository
15
+ - **TypeScript Ready**: Full type definitions included
16
+ - **Zero Dependencies**: Only requires `mongoose` as peer dependency
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @classytic/revenue
22
+ ```
23
+
24
+ ## Transaction Model Setup
25
+
26
+ Spread library enums/schemas into your Transaction model:
27
+
28
+ ```javascript
29
+ import mongoose from 'mongoose';
30
+ import {
31
+ TRANSACTION_STATUS_VALUES,
32
+ LIBRARY_CATEGORIES,
33
+ } from '@classytic/revenue/enums';
34
+ import {
35
+ gatewaySchema,
36
+ currentPaymentSchema,
37
+ paymentDetailsSchema,
38
+ } from '@classytic/revenue/schemas';
39
+
40
+ // Merge library categories with your own
41
+ const MY_CATEGORIES = {
42
+ ...LIBRARY_CATEGORIES, // subscription, purchase
43
+ SALARY: 'salary',
44
+ RENT: 'rent',
45
+ EQUIPMENT: 'equipment',
46
+ // Add as many as you need
47
+ };
48
+
49
+ const transactionSchema = new mongoose.Schema({
50
+ // Required by library
51
+ organizationId: { type: String, required: true, index: true },
52
+ amount: { type: Number, required: true, min: 0 },
53
+ status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
54
+ category: { type: String, enum: Object.values(MY_CATEGORIES), required: true },
55
+
56
+ // Spread library schemas
57
+ gateway: gatewaySchema,
58
+ currentPayment: currentPaymentSchema,
59
+ paymentDetails: paymentDetailsSchema,
60
+
61
+ // Add your fields
62
+ notes: String,
63
+ invoiceNumber: String,
64
+ }, { timestamps: true });
65
+
66
+ export default mongoose.model('Transaction', transactionSchema);
67
+ ```
68
+
69
+ **See [`examples/transaction.model.js`](examples/transaction.model.js) for complete example with indexes.**
70
+
71
+ ## Quick Start
72
+
73
+ ### Minimal Setup (3 lines)
74
+
75
+ ```javascript
76
+ import { createRevenue } from '@classytic/revenue';
77
+ import Transaction from './models/Transaction.js';
78
+
79
+ // Works out-of-box with built-in manual provider
80
+ const revenue = createRevenue({
81
+ models: { Transaction },
82
+ });
83
+
84
+ // Create a subscription
85
+ const { subscription, transaction } = await revenue.subscriptions.create({
86
+ data: { organizationId, customerId },
87
+ planKey: 'monthly',
88
+ amount: 99.99,
89
+ });
90
+ ```
91
+
92
+ That's it! The package works immediately with sensible defaults.
93
+
94
+ ## Usage Examples
95
+
96
+ ### With Payment Provider
97
+
98
+ ```javascript
99
+ import { createRevenue } from '@classytic/revenue';
100
+ // Future: import { stripe } from '@classytic/revenue-stripe';
101
+
102
+ const revenue = createRevenue({
103
+ models: { Transaction },
104
+ providers: {
105
+ // Built-in manual provider is auto-included
106
+ // stripe: stripe({ apiKey: process.env.STRIPE_KEY }),
107
+ },
108
+ });
109
+
110
+ // Create subscription with payment gateway
111
+ await revenue.subscriptions.create({
112
+ data: { organizationId, customerId },
113
+ planKey: 'monthly',
114
+ amount: 99.99,
115
+ gateway: 'stripe', // or 'manual'
116
+ });
117
+ ```
118
+
119
+ ### With Hooks
120
+
121
+ ```javascript
122
+ const revenue = createRevenue({
123
+ models: { Transaction },
124
+ hooks: {
125
+ 'payment.verified': async ({ transaction }) => {
126
+ console.log('Payment verified:', transaction._id);
127
+ // Send email, update analytics, etc.
128
+ },
129
+ 'subscription.created': async ({ subscription, transaction }) => {
130
+ console.log('New subscription:', subscription._id);
131
+ },
132
+ },
133
+ });
134
+ ```
135
+
136
+ ### Custom Logger
137
+
138
+ ```javascript
139
+ import winston from 'winston';
140
+
141
+ const revenue = createRevenue({
142
+ models: { Transaction },
143
+ logger: winston.createLogger({ /* config */ }),
144
+ });
145
+ ```
146
+
147
+ ## Core API
148
+
149
+ ### Services
150
+
151
+ The `revenue` instance provides three focused services:
152
+
153
+ #### Subscriptions
154
+
155
+ ```javascript
156
+ // Create subscription
157
+ const { subscription, transaction, paymentIntent } = await revenue.subscriptions.create({
158
+ data: { organizationId, customerId, ... },
159
+ planKey: 'monthly',
160
+ amount: 99.99,
161
+ currency: 'USD',
162
+ gateway: 'manual', // optional
163
+ metadata: { /* ... */ }, // optional
164
+ });
165
+
166
+ // Renew subscription
167
+ await revenue.subscriptions.renew(subscriptionId, { amount: 99.99 });
168
+
169
+ // Activate subscription
170
+ await revenue.subscriptions.activate(subscriptionId);
171
+
172
+ // Cancel subscription
173
+ await revenue.subscriptions.cancel(subscriptionId, { immediate: true });
174
+
175
+ // Pause/Resume
176
+ await revenue.subscriptions.pause(subscriptionId);
177
+ await revenue.subscriptions.resume(subscriptionId);
178
+
179
+ // Get/List
180
+ await revenue.subscriptions.get(subscriptionId);
181
+ await revenue.subscriptions.list(filters, options);
182
+ ```
183
+
184
+ #### Payments
185
+
186
+ ```javascript
187
+ // Verify payment
188
+ const { transaction, paymentResult, status } = await revenue.payments.verify(
189
+ paymentIntentId,
190
+ { verifiedBy: userId }
191
+ );
192
+
193
+ // Get payment status
194
+ const { transaction, status, provider } = await revenue.payments.getStatus(paymentIntentId);
195
+
196
+ // Refund payment
197
+ const { transaction, refundResult } = await revenue.payments.refund(
198
+ paymentId,
199
+ amount, // optional, defaults to full refund
200
+ { reason: 'Customer request' }
201
+ );
202
+
203
+ // Handle webhook
204
+ const { event, transaction, status } = await revenue.payments.handleWebhook(
205
+ 'stripe',
206
+ payload,
207
+ headers
208
+ );
209
+ ```
210
+
211
+ #### Transactions
212
+
213
+ ```javascript
214
+ // Get transaction
215
+ const transaction = await revenue.transactions.get(transactionId);
216
+
217
+ // List transactions
218
+ const { transactions, total, page, limit, pages } = await revenue.transactions.list(
219
+ { organizationId, status: 'verified' },
220
+ { limit: 50, skip: 0, sort: { createdAt: -1 } }
221
+ );
222
+
223
+ // Update transaction
224
+ await revenue.transactions.update(transactionId, { notes: 'Updated' });
225
+ ```
226
+
227
+ **Note**: For analytics, exports, or complex queries, use Mongoose aggregations directly on your Transaction model. This keeps the service thin and focused.
228
+
229
+ ### Providers
230
+
231
+ ```javascript
232
+ // Get specific provider
233
+ const stripeProvider = revenue.getProvider('stripe');
234
+
235
+ // Check capabilities
236
+ const capabilities = stripeProvider.getCapabilities();
237
+ // {
238
+ // supportsWebhooks: true,
239
+ // supportsRefunds: true,
240
+ // supportsPartialRefunds: true,
241
+ // requiresManualVerification: false
242
+ // }
243
+ ```
244
+
245
+ ## Error Handling
246
+
247
+ All errors are typed with codes for easy handling:
248
+
249
+ ```javascript
250
+ import {
251
+ TransactionNotFoundError,
252
+ ProviderNotFoundError,
253
+ RefundNotSupportedError
254
+ } from '@classytic/revenue';
255
+
256
+ try {
257
+ await revenue.payments.verify(intentId);
258
+ } catch (error) {
259
+ if (error instanceof TransactionNotFoundError) {
260
+ console.log('Transaction not found:', error.metadata.transactionId);
261
+ }
262
+
263
+ if (error.code === 'TRANSACTION_NOT_FOUND') {
264
+ // Handle specific error
265
+ }
266
+
267
+ if (error.retryable) {
268
+ // Retry the operation
269
+ }
270
+ }
271
+ ```
272
+
273
+ ### Error Classes
274
+
275
+ - `RevenueError` - Base error class
276
+ - `ConfigurationError` - Configuration issues
277
+ - `ModelNotRegisteredError` - Model not provided
278
+ - `ProviderError` - Provider-related errors
279
+ - `ProviderNotFoundError` - Provider doesn't exist
280
+ - `PaymentIntentCreationError` - Failed to create payment intent
281
+ - `PaymentVerificationError` - Verification failed
282
+ - `NotFoundError` - Resource not found
283
+ - `TransactionNotFoundError` - Transaction not found
284
+ - `SubscriptionNotFoundError` - Subscription not found
285
+ - `ValidationError` - Validation failed
286
+ - `InvalidAmountError` - Invalid amount
287
+ - `MissingRequiredFieldError` - Required field missing
288
+ - `StateError` - Invalid state
289
+ - `AlreadyVerifiedError` - Already verified
290
+ - `InvalidStateTransitionError` - Invalid state change
291
+ - `RefundNotSupportedError` - Provider doesn't support refunds
292
+ - `RefundError` - Refund failed
293
+
294
+ ## Enums & Schemas
295
+
296
+ ```javascript
297
+ import {
298
+ TRANSACTION_STATUS,
299
+ PAYMENT_GATEWAY_TYPE,
300
+ SUBSCRIPTION_STATUS,
301
+ PLAN_KEYS,
302
+ currentPaymentSchema,
303
+ subscriptionInfoSchema,
304
+ } from '@classytic/revenue';
305
+
306
+ // Use in your models
307
+ const organizationSchema = new Schema({
308
+ currentPayment: currentPaymentSchema,
309
+ subscription: subscriptionInfoSchema,
310
+ });
311
+ ```
312
+
313
+ ## TypeScript
314
+
315
+ Full TypeScript support included:
316
+
317
+ ```typescript
318
+ import { createRevenue, Revenue, RevenueOptions } from '@classytic/revenue';
319
+
320
+ const options: RevenueOptions = {
321
+ models: { Transaction: TransactionModel },
322
+ };
323
+
324
+ const revenue: Revenue = createRevenue(options);
325
+ ```
326
+
327
+ ## Advanced Usage
328
+
329
+ ### Custom Providers
330
+
331
+ ```javascript
332
+ import { PaymentProvider } from '@classytic/revenue';
333
+
334
+ class MyCustomProvider extends PaymentProvider {
335
+ name = 'my-gateway';
336
+
337
+ async createIntent(params) {
338
+ // Implementation
339
+ }
340
+
341
+ async verifyPayment(intentId) {
342
+ // Implementation
343
+ }
344
+
345
+ getCapabilities() {
346
+ return {
347
+ supportsWebhooks: true,
348
+ supportsRefunds: true,
349
+ supportsPartialRefunds: false,
350
+ requiresManualVerification: false,
351
+ };
352
+ }
353
+ }
354
+
355
+ const revenue = createRevenue({
356
+ models: { Transaction },
357
+ providers: {
358
+ 'my-gateway': new MyCustomProvider(),
359
+ },
360
+ });
361
+ ```
362
+
363
+ ### DI Container Access
364
+
365
+ ```javascript
366
+ const revenue = createRevenue({ models: { Transaction } });
367
+
368
+ // Access container
369
+ const models = revenue.container.get('models');
370
+ const providers = revenue.container.get('providers');
371
+ ```
372
+
373
+ ## Hook Events
374
+
375
+ Available hook events:
376
+
377
+ - `payment.verified` - Payment verified
378
+ - `payment.failed` - Payment failed
379
+ - `subscription.created` - Subscription created
380
+ - `subscription.renewed` - Subscription renewed
381
+ - `subscription.activated` - Subscription activated
382
+ - `subscription.cancelled` - Subscription cancelled
383
+ - `subscription.paused` - Subscription paused
384
+ - `subscription.resumed` - Subscription resumed
385
+ - `transaction.created` - Transaction created
386
+ - `transaction.updated` - Transaction updated
387
+
388
+ Hooks are fire-and-forget - they never break the main flow. Errors are logged but don't throw.
389
+
390
+ ## Architecture
391
+
392
+ ```
393
+ @classytic/revenue (core package)
394
+ ├── Builder (createRevenue)
395
+ ├── DI Container
396
+ ├── Services (focused on lifecycle)
397
+ │ ├── SubscriptionService
398
+ │ ├── PaymentService
399
+ │ └── TransactionService
400
+ ├── Providers
401
+ │ ├── base.js (interface)
402
+ │ └── manual.js (built-in)
403
+ ├── Error classes
404
+ └── Schemas & Enums
405
+
406
+ @classytic/revenue-stripe (future)
407
+ @classytic/revenue-sslcommerz (future)
408
+ @classytic/revenue-fastify (framework adapter, future)
409
+ ```
410
+
411
+ ## Design Principles
412
+
413
+ - **KISS**: Keep It Simple, Stupid
414
+ - **DRY**: Don't Repeat Yourself
415
+ - **SOLID**: Single responsibility, focused services
416
+ - **Immutable**: Revenue instance is deeply frozen
417
+ - **Thin Core**: Core operations only, users extend as needed
418
+ - **Smart Defaults**: Works out-of-box with minimal config
419
+
420
+ ## Migration from Legacy API
421
+
422
+ If you're using the old `initializeRevenue()` API:
423
+
424
+ ```javascript
425
+ // ❌ Old (legacy API - removed)
426
+ import { initializeRevenue, monetization, payment } from '@classytic/revenue';
427
+ initializeRevenue({ TransactionModel, transactionService });
428
+ await monetization.createSubscription(params);
429
+
430
+ // ✅ New (DI-based API)
431
+ import { createRevenue } from '@classytic/revenue';
432
+ const revenue = createRevenue({ models: { Transaction } });
433
+ await revenue.subscriptions.create(params);
434
+ ```
435
+
436
+ ## Documentation
437
+
438
+ - **[Building Payment Providers](../docs/guides/PROVIDER_GUIDE.md)** - Create custom payment integrations
439
+ - **[Examples](../docs/examples/)** - Complete usage examples
440
+ - **[Full Documentation](../docs/README.md)** - Comprehensive guides
441
+
442
+ ## Support
443
+
444
+ - **GitHub**: https://github.com/classytic/revenue
445
+ - **Issues**: https://github.com/classytic/revenue/issues
446
+ - **npm**: https://npmjs.com/package/@classytic/revenue
447
+
448
+ ## License
449
+
450
+ MIT © Classytic (Classytic)
451
+
452
+ ---
453
+
454
+ **Built with ❤️ following SOLID principles and industry best practices**
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Revenue Builder - Main Entry Point
3
+ * @classytic/revenue
4
+ *
5
+ * Factory function to create revenue instance
6
+ * Inspired by: AI SDK, LangChain, Prisma Client
7
+ */
8
+
9
+ import { Container } from './container.js';
10
+ import { SubscriptionService } from '../services/subscription.service.js';
11
+ import { PaymentService } from '../services/payment.service.js';
12
+ import { TransactionService } from '../services/transaction.service.js';
13
+
14
+ /**
15
+ * Create revenue instance with dependency injection
16
+ *
17
+ * @param {Object} options - Configuration options
18
+ * @param {Object} options.models - Mongoose models { Transaction, Subscription, etc. }
19
+ * @param {Object} options.providers - Payment providers { manual, stripe, etc. }
20
+ * @param {Object} options.hooks - Event hooks
21
+ * @param {Object} options.config - Additional configuration
22
+ * @param {Object} options.logger - Logger instance
23
+ * @returns {Revenue} Revenue instance
24
+ *
25
+ * @example
26
+ * ```javascript
27
+ * import { createRevenue, ManualProvider } from '@classytic/revenue';
28
+ *
29
+ * const revenue = createRevenue({
30
+ * models: {
31
+ * Transaction: TransactionModel,
32
+ * Subscription: SubscriptionModel,
33
+ * },
34
+ * providers: {
35
+ * manual: new ManualProvider(),
36
+ * },
37
+ * config: {
38
+ * targetModels: ['Subscription', 'Membership'],
39
+ * categoryMappings: {
40
+ * Subscription: 'platform_subscription',
41
+ * Membership: 'gym_membership',
42
+ * },
43
+ * },
44
+ * });
45
+ *
46
+ * // Use anywhere
47
+ * const subscription = await revenue.subscriptions.create({ ... });
48
+ * await revenue.payments.verify(txnId);
49
+ * ```
50
+ */
51
+ export function createRevenue(options = {}) {
52
+ // Validate required options
53
+ if (!options.models || !options.models.Transaction) {
54
+ throw new Error('createRevenue(): options.models.Transaction is required');
55
+ }
56
+
57
+ // Create DI container
58
+ const container = new Container();
59
+
60
+ // Register models
61
+ container.singleton('models', options.models);
62
+
63
+ // Register providers
64
+ const providers = options.providers || {};
65
+ container.singleton('providers', providers);
66
+
67
+ // Register hooks
68
+ container.singleton('hooks', options.hooks || {});
69
+
70
+ // Register config
71
+ const config = {
72
+ targetModels: ['Subscription', 'Membership'],
73
+ categoryMappings: {},
74
+ ...options.config,
75
+ };
76
+ container.singleton('config', config);
77
+
78
+ // Register logger
79
+ container.singleton('logger', options.logger || console);
80
+
81
+ // Create service instances (lazy-loaded)
82
+ const services = {
83
+ subscriptions: null,
84
+ payments: null,
85
+ transactions: null,
86
+ };
87
+
88
+ // Create revenue instance
89
+ const revenue = {
90
+ /**
91
+ * Get container (for advanced usage)
92
+ */
93
+ container,
94
+
95
+ /**
96
+ * Registered payment providers
97
+ */
98
+ providers,
99
+
100
+ /**
101
+ * Configuration
102
+ */
103
+ config,
104
+
105
+ /**
106
+ * Subscription service
107
+ * Lazy-loaded on first access
108
+ */
109
+ get subscriptions() {
110
+ if (!services.subscriptions) {
111
+ services.subscriptions = new SubscriptionService(container);
112
+ }
113
+ return services.subscriptions;
114
+ },
115
+
116
+ /**
117
+ * Payment service
118
+ * Lazy-loaded on first access
119
+ */
120
+ get payments() {
121
+ if (!services.payments) {
122
+ services.payments = new PaymentService(container);
123
+ }
124
+ return services.payments;
125
+ },
126
+
127
+ /**
128
+ * Transaction service
129
+ * Lazy-loaded on first access
130
+ */
131
+ get transactions() {
132
+ if (!services.transactions) {
133
+ services.transactions = new TransactionService(container);
134
+ }
135
+ return services.transactions;
136
+ },
137
+
138
+ /**
139
+ * Get a specific provider
140
+ */
141
+ getProvider(name) {
142
+ const provider = providers[name];
143
+ if (!provider) {
144
+ throw new Error(`Provider "${name}" not found. Available: ${Object.keys(providers).join(', ')}`);
145
+ }
146
+ return provider;
147
+ },
148
+ };
149
+
150
+ // Deeply freeze the revenue object (truly immutable)
151
+ Object.freeze(revenue);
152
+ Object.freeze(providers);
153
+ Object.freeze(config);
154
+
155
+ return revenue;
156
+ }
157
+
158
+ /**
159
+ * Revenue instance type (for documentation)
160
+ * @typedef {Object} Revenue
161
+ * @property {Container} container - DI container (readonly)
162
+ * @property {Object} providers - Payment providers (readonly, frozen)
163
+ * @property {Object} config - Configuration (readonly, frozen)
164
+ * @property {SubscriptionService} subscriptions - Subscription service
165
+ * @property {PaymentService} payments - Payment service
166
+ * @property {TransactionService} transactions - Transaction service
167
+ * @property {Function} getProvider - Get payment provider
168
+ */
169
+
170
+ export default createRevenue;