@classytic/revenue 0.0.21 → 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.
package/README.md CHANGED
@@ -1,642 +1,492 @@
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
- ## Core Concepts
25
-
26
- ### Monetization Types (Strict)
27
- The library supports **3 monetization types** (strict):
28
- - **FREE**: No payment required
29
- - **SUBSCRIPTION**: Recurring payments
30
- - **PURCHASE**: One-time payments
31
-
32
- ### Transaction Categories (Flexible)
33
- You can use **custom category names** for your business logic while using the strict monetization types:
34
- - `'order_subscription'` for subscription orders
35
- - `'gym_membership'` for gym memberships
36
- - `'course_enrollment'` for course enrollments
37
- - Or any custom names you need
38
-
39
- ### How It Works
40
- ```javascript
41
- const revenue = createRevenue({
42
- models: { Transaction },
43
- config: {
44
- categoryMappings: {
45
- Order: 'order_subscription', // Customer orders
46
- PlatformSubscription: 'platform_subscription', // Tenant/org subscriptions
47
- Membership: 'gym_membership', // User memberships
48
- Enrollment: 'course_enrollment', // Course enrollments
49
- }
50
- }
51
- });
52
-
53
- // All these use SUBSCRIPTION monetization type but different categories
54
- await revenue.subscriptions.create({
55
- entity: 'Order', // Logical identifier maps to 'order_subscription'
56
- monetizationType: 'subscription',
57
- // ...
58
- });
59
-
60
- await revenue.subscriptions.create({
61
- entity: 'PlatformSubscription', // Logical identifier → maps to 'platform_subscription'
62
- monetizationType: 'subscription',
63
- // ...
64
- });
65
- ```
66
-
67
- **Note:** `entity` is NOT a database model name - it's just a logical identifier you choose to organize your business logic.
68
-
69
- ## Transaction Model Setup
70
-
71
- Spread library enums/schemas into your Transaction model:
72
-
73
- ```javascript
74
- import mongoose from 'mongoose';
75
- import {
76
- TRANSACTION_STATUS_VALUES,
77
- LIBRARY_CATEGORIES,
78
- } from '@classytic/revenue/enums';
79
- import {
80
- gatewaySchema,
81
- currentPaymentSchema,
82
- paymentDetailsSchema,
83
- } from '@classytic/revenue/schemas';
84
-
85
- // Merge library categories with your custom ones
86
- const MY_CATEGORIES = {
87
- ...LIBRARY_CATEGORIES, // subscription, purchase (library defaults)
88
- ORDER_SUBSCRIPTION: 'order_subscription',
89
- ORDER_PURCHASE: 'order_purchase',
90
- GYM_MEMBERSHIP: 'gym_membership',
91
- COURSE_ENROLLMENT: 'course_enrollment',
92
- SALARY: 'salary',
93
- RENT: 'rent',
94
- EQUIPMENT: 'equipment',
95
- // Add as many as you need
96
- };
97
-
98
- const transactionSchema = new mongoose.Schema({
99
- // Required by library
100
- organizationId: { type: String, required: true, index: true },
101
- amount: { type: Number, required: true, min: 0 },
102
- status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
103
- category: { type: String, enum: Object.values(MY_CATEGORIES), required: true },
104
-
105
- // Spread library schemas
106
- gateway: gatewaySchema,
107
- currentPayment: currentPaymentSchema,
108
- paymentDetails: paymentDetailsSchema,
109
-
110
- // Add your fields
111
- notes: String,
112
- invoiceNumber: String,
113
- }, { timestamps: true });
114
-
115
- export default mongoose.model('Transaction', transactionSchema);
116
- ```
117
-
118
- **See [`examples/transaction.model.js`](examples/transaction.model.js) for complete example with indexes.**
119
-
120
- ## Quick Start
121
-
122
- ### Minimal Setup (3 lines)
123
-
124
- ```javascript
125
- import { createRevenue } from '@classytic/revenue';
126
- import Transaction from './models/Transaction.js';
127
-
128
- // Works out-of-box with built-in manual provider
129
- const revenue = createRevenue({
130
- models: { Transaction },
131
- });
132
-
133
- // Create a subscription
134
- const { subscription, transaction } = await revenue.subscriptions.create({
135
- data: { organizationId, customerId },
136
- planKey: 'monthly',
137
- amount: 99.99,
138
- });
139
- ```
140
-
141
- That's it! The package works immediately with sensible defaults.
142
-
143
- ## Real-World Use Cases
144
-
145
- ### E-commerce Platform with Multiple Order Types
146
-
147
- ```javascript
148
- // Setup
149
- const revenue = createRevenue({
150
- models: { Transaction },
151
- config: {
152
- categoryMappings: {
153
- Order: 'order_subscription', // Recurring orders (meal kits, subscriptions)
154
- Purchase: 'order_purchase', // One-time orders
155
- }
156
- }
157
- });
158
-
159
- // Subscription order (meal kit subscription)
160
- const { subscription, transaction } = await revenue.subscriptions.create({
161
- data: { organizationId, customerId },
162
- entity: 'Order', // Logical identifier
163
- monetizationType: 'subscription', // STRICT: Must be subscription/purchase/free
164
- planKey: 'monthly',
165
- amount: 49.99,
166
- metadata: { productType: 'meal_kit' }
167
- });
168
- // Transaction created with category: 'order_subscription'
169
-
170
- // One-time purchase order
171
- const { transaction } = await revenue.subscriptions.create({
172
- data: { organizationId, customerId },
173
- entity: 'Purchase', // Logical identifier
174
- monetizationType: 'purchase',
175
- amount: 99.99,
176
- metadata: { productType: 'electronics' }
177
- });
178
- // Transaction created with category: 'order_purchase'
179
- ```
180
-
181
- ### Gym Management System
182
-
183
- ```javascript
184
- // Setup
185
- const revenue = createRevenue({
186
- models: { Transaction },
187
- config: {
188
- categoryMappings: {
189
- Membership: 'gym_membership',
190
- PersonalTraining: 'personal_training',
191
- DayPass: 'day_pass',
192
- }
193
- }
194
- });
195
-
196
- // Monthly gym membership
197
- await revenue.subscriptions.create({
198
- entity: 'Membership',
199
- monetizationType: 'subscription',
200
- planKey: 'monthly',
201
- amount: 59.99,
202
- });
203
- // Transaction: 'gym_membership'
204
-
205
- // Personal training package (one-time purchase)
206
- await revenue.subscriptions.create({
207
- entity: 'PersonalTraining',
208
- monetizationType: 'purchase',
209
- amount: 299.99,
210
- });
211
- // Transaction: 'personal_training'
212
-
213
- // Day pass (free trial)
214
- await revenue.subscriptions.create({
215
- entity: 'DayPass',
216
- monetizationType: 'free',
217
- amount: 0,
218
- });
219
- // No transaction created for free
220
- ```
221
-
222
- ### Online Learning Platform
223
-
224
- ```javascript
225
- // Setup
226
- const revenue = createRevenue({
227
- models: { Transaction },
228
- config: {
229
- categoryMappings: {
230
- CourseEnrollment: 'course_enrollment',
231
- MembershipPlan: 'membership_plan',
232
- }
233
- }
234
- });
235
-
236
- // One-time course purchase
237
- await revenue.subscriptions.create({
238
- entity: 'CourseEnrollment',
239
- monetizationType: 'purchase',
240
- amount: 99.00,
241
- metadata: { courseId: 'react-advanced' }
242
- });
243
- // Transaction: 'course_enrollment'
244
-
245
- // Monthly all-access membership
246
- await revenue.subscriptions.create({
247
- entity: 'MembershipPlan',
248
- monetizationType: 'subscription',
249
- planKey: 'monthly',
250
- amount: 29.99,
251
- });
252
- // Transaction: 'membership_plan'
253
- ```
254
-
255
- ### Without Category Mappings (Defaults)
256
-
257
- ```javascript
258
- // No mappings defined - uses library defaults
259
- const revenue = createRevenue({
260
- models: { Transaction },
261
- config: {
262
- categoryMappings: {} // Empty or omit this
263
- }
264
- });
265
-
266
- // All subscriptions use default 'subscription' category
267
- await revenue.subscriptions.create({
268
- monetizationType: 'subscription',
269
- planKey: 'monthly',
270
- amount: 49.99,
271
- });
272
- // Transaction created with category: 'subscription' (library default)
273
-
274
- // All purchases use default 'purchase' category
275
- await revenue.subscriptions.create({
276
- monetizationType: 'purchase',
277
- amount: 99.99,
278
- });
279
- // Transaction created with category: 'purchase' (library default)
280
- ```
281
-
282
- ## Usage Examples
283
-
284
- ### With Payment Provider
285
-
286
- ```javascript
287
- import { createRevenue } from '@classytic/revenue';
288
- // Future: import { stripe } from '@classytic/revenue-stripe';
289
-
290
- const revenue = createRevenue({
291
- models: { Transaction },
292
- providers: {
293
- // Built-in manual provider is auto-included
294
- // stripe: stripe({ apiKey: process.env.STRIPE_KEY }),
295
- },
296
- });
297
-
298
- // Create subscription with payment gateway
299
- await revenue.subscriptions.create({
300
- data: { organizationId, customerId },
301
- planKey: 'monthly',
302
- amount: 99.99,
303
- gateway: 'stripe', // or 'manual'
304
- });
305
- ```
306
-
307
- ### With Hooks
308
-
309
- ```javascript
310
- const revenue = createRevenue({
311
- models: { Transaction },
312
- hooks: {
313
- 'payment.verified': async ({ transaction }) => {
314
- console.log('Payment verified:', transaction._id);
315
- // Send email, update analytics, etc.
316
- },
317
- 'subscription.created': async ({ subscription, transaction }) => {
318
- console.log('New subscription:', subscription._id);
319
- },
320
- },
321
- });
322
- ```
323
-
324
- ### Custom Logger
325
-
326
- ```javascript
327
- import winston from 'winston';
328
-
329
- const revenue = createRevenue({
330
- models: { Transaction },
331
- logger: winston.createLogger({ /* config */ }),
332
- });
333
- ```
334
-
335
- ## Core API
336
-
337
- ### Services
338
-
339
- The `revenue` instance provides three focused services:
340
-
341
- #### Subscriptions
342
-
343
- ```javascript
344
- // Create subscription
345
- const { subscription, transaction, paymentIntent } = await revenue.subscriptions.create({
346
- data: { organizationId, customerId, ... },
347
- planKey: 'monthly',
348
- amount: 99.99,
349
- currency: 'USD',
350
- gateway: 'manual', // optional
351
- metadata: { /* ... */ }, // optional
352
- });
353
-
354
- // Renew subscription
355
- await revenue.subscriptions.renew(subscriptionId, { amount: 99.99 });
356
-
357
- // Activate subscription
358
- await revenue.subscriptions.activate(subscriptionId);
359
-
360
- // Cancel subscription
361
- await revenue.subscriptions.cancel(subscriptionId, { immediate: true });
362
-
363
- // Pause/Resume
364
- await revenue.subscriptions.pause(subscriptionId);
365
- await revenue.subscriptions.resume(subscriptionId);
366
-
367
- // Get/List
368
- await revenue.subscriptions.get(subscriptionId);
369
- await revenue.subscriptions.list(filters, options);
370
- ```
371
-
372
- #### Payments
373
-
374
- ```javascript
375
- // Verify payment
376
- const { transaction, paymentResult, status } = await revenue.payments.verify(
377
- paymentIntentId,
378
- { verifiedBy: userId }
379
- );
380
-
381
- // Get payment status
382
- const { transaction, status, provider } = await revenue.payments.getStatus(paymentIntentId);
383
-
384
- // Refund payment
385
- const { transaction, refundResult } = await revenue.payments.refund(
386
- paymentId,
387
- amount, // optional, defaults to full refund
388
- { reason: 'Customer request' }
389
- );
390
-
391
- // Handle webhook
392
- const { event, transaction, status } = await revenue.payments.handleWebhook(
393
- 'stripe',
394
- payload,
395
- headers
396
- );
397
- ```
398
-
399
- #### Transactions
400
-
401
- ```javascript
402
- // Get transaction
403
- const transaction = await revenue.transactions.get(transactionId);
404
-
405
- // List transactions
406
- const { transactions, total, page, limit, pages } = await revenue.transactions.list(
407
- { organizationId, status: 'verified' },
408
- { limit: 50, skip: 0, sort: { createdAt: -1 } }
409
- );
410
-
411
- // Update transaction
412
- await revenue.transactions.update(transactionId, { notes: 'Updated' });
413
- ```
414
-
415
- **Note**: For analytics, exports, or complex queries, use Mongoose aggregations directly on your Transaction model. This keeps the service thin and focused.
416
-
417
- ### Providers
418
-
419
- ```javascript
420
- // Get specific provider
421
- const stripeProvider = revenue.getProvider('stripe');
422
-
423
- // Check capabilities
424
- const capabilities = stripeProvider.getCapabilities();
425
- // {
426
- // supportsWebhooks: true,
427
- // supportsRefunds: true,
428
- // supportsPartialRefunds: true,
429
- // requiresManualVerification: false
430
- // }
431
- ```
432
-
433
- ## Error Handling
434
-
435
- All errors are typed with codes for easy handling:
436
-
437
- ```javascript
438
- import {
439
- TransactionNotFoundError,
440
- ProviderNotFoundError,
441
- RefundNotSupportedError
442
- } from '@classytic/revenue';
443
-
444
- try {
445
- await revenue.payments.verify(intentId);
446
- } catch (error) {
447
- if (error instanceof TransactionNotFoundError) {
448
- console.log('Transaction not found:', error.metadata.transactionId);
449
- }
450
-
451
- if (error.code === 'TRANSACTION_NOT_FOUND') {
452
- // Handle specific error
453
- }
454
-
455
- if (error.retryable) {
456
- // Retry the operation
457
- }
458
- }
459
- ```
460
-
461
- ### Error Classes
462
-
463
- - `RevenueError` - Base error class
464
- - `ConfigurationError` - Configuration issues
465
- - `ModelNotRegisteredError` - Model not provided
466
- - `ProviderError` - Provider-related errors
467
- - `ProviderNotFoundError` - Provider doesn't exist
468
- - `PaymentIntentCreationError` - Failed to create payment intent
469
- - `PaymentVerificationError` - Verification failed
470
- - `NotFoundError` - Resource not found
471
- - `TransactionNotFoundError` - Transaction not found
472
- - `SubscriptionNotFoundError` - Subscription not found
473
- - `ValidationError` - Validation failed
474
- - `InvalidAmountError` - Invalid amount
475
- - `MissingRequiredFieldError` - Required field missing
476
- - `StateError` - Invalid state
477
- - `AlreadyVerifiedError` - Already verified
478
- - `InvalidStateTransitionError` - Invalid state change
479
- - `RefundNotSupportedError` - Provider doesn't support refunds
480
- - `RefundError` - Refund failed
481
-
482
- ## Enums & Schemas
483
-
484
- ```javascript
485
- import {
486
- TRANSACTION_STATUS,
487
- PAYMENT_GATEWAY_TYPE,
488
- SUBSCRIPTION_STATUS,
489
- PLAN_KEYS,
490
- currentPaymentSchema,
491
- subscriptionInfoSchema,
492
- } from '@classytic/revenue';
493
-
494
- // Use in your models
495
- const organizationSchema = new Schema({
496
- currentPayment: currentPaymentSchema,
497
- subscription: subscriptionInfoSchema,
498
- });
499
- ```
500
-
501
- ## TypeScript
502
-
503
- Full TypeScript support included:
504
-
505
- ```typescript
506
- import { createRevenue, Revenue, RevenueOptions } from '@classytic/revenue';
507
-
508
- const options: RevenueOptions = {
509
- models: { Transaction: TransactionModel },
510
- };
511
-
512
- const revenue: Revenue = createRevenue(options);
513
- ```
514
-
515
- ## Advanced Usage
516
-
517
- ### Custom Providers
518
-
519
- ```javascript
520
- import { PaymentProvider } from '@classytic/revenue';
521
-
522
- class MyCustomProvider extends PaymentProvider {
523
- name = 'my-gateway';
524
-
525
- async createIntent(params) {
526
- // Implementation
527
- }
528
-
529
- async verifyPayment(intentId) {
530
- // Implementation
531
- }
532
-
533
- getCapabilities() {
534
- return {
535
- supportsWebhooks: true,
536
- supportsRefunds: true,
537
- supportsPartialRefunds: false,
538
- requiresManualVerification: false,
539
- };
540
- }
541
- }
542
-
543
- const revenue = createRevenue({
544
- models: { Transaction },
545
- providers: {
546
- 'my-gateway': new MyCustomProvider(),
547
- },
548
- });
549
- ```
550
-
551
- ### DI Container Access
552
-
553
- ```javascript
554
- const revenue = createRevenue({ models: { Transaction } });
555
-
556
- // Access container
557
- const models = revenue.container.get('models');
558
- const providers = revenue.container.get('providers');
559
- ```
560
-
561
- ## Hook Events
562
-
563
- Available hook events:
564
-
565
- - `payment.verified` - Payment verified
566
- - `payment.failed` - Payment failed
567
- - `subscription.created` - Subscription created
568
- - `subscription.renewed` - Subscription renewed
569
- - `subscription.activated` - Subscription activated
570
- - `subscription.cancelled` - Subscription cancelled
571
- - `subscription.paused` - Subscription paused
572
- - `subscription.resumed` - Subscription resumed
573
- - `transaction.created` - Transaction created
574
- - `transaction.updated` - Transaction updated
575
-
576
- Hooks are fire-and-forget - they never break the main flow. Errors are logged but don't throw.
577
-
578
- ## Architecture
579
-
580
- ```
581
- @classytic/revenue (core package)
582
- ├── Builder (createRevenue)
583
- ├── DI Container
584
- ├── Services (focused on lifecycle)
585
- │ ├── SubscriptionService
586
- │ ├── PaymentService
587
- │ └── TransactionService
588
- ├── Providers
589
- │ ├── base.js (interface)
590
- │ └── manual.js (built-in)
591
- ├── Error classes
592
- └── Schemas & Enums
593
-
594
- @classytic/revenue-stripe (future)
595
- @classytic/revenue-sslcommerz (future)
596
- @classytic/revenue-fastify (framework adapter, future)
597
- ```
598
-
599
- ## Design Principles
600
-
601
- - **KISS**: Keep It Simple, Stupid
602
- - **DRY**: Don't Repeat Yourself
603
- - **SOLID**: Single responsibility, focused services
604
- - **Immutable**: Revenue instance is deeply frozen
605
- - **Thin Core**: Core operations only, users extend as needed
606
- - **Smart Defaults**: Works out-of-box with minimal config
607
-
608
- ## Migration from Legacy API
609
-
610
- If you're using the old `initializeRevenue()` API:
611
-
612
- ```javascript
613
- // ❌ Old (legacy API - removed)
614
- import { initializeRevenue, monetization, payment } from '@classytic/revenue';
615
- initializeRevenue({ TransactionModel, transactionService });
616
- await monetization.createSubscription(params);
617
-
618
- // ✅ New (DI-based API)
619
- import { createRevenue } from '@classytic/revenue';
620
- const revenue = createRevenue({ models: { Transaction } });
621
- await revenue.subscriptions.create(params);
622
- ```
623
-
624
- ## Documentation
625
-
626
- - **[Building Payment Providers](../docs/guides/PROVIDER_GUIDE.md)** - Create custom payment integrations
627
- - **[Examples](../docs/examples/)** - Complete usage examples
628
- - **[Full Documentation](../docs/README.md)** - Comprehensive guides
629
-
630
- ## Support
631
-
632
- - **GitHub**: https://github.com/classytic/revenue
633
- - **Issues**: https://github.com/classytic/revenue/issues
634
- - **npm**: https://npmjs.com/package/@classytic/revenue
635
-
636
- ## License
637
-
638
- MIT © Classytic (Classytic)
639
-
640
- ---
641
-
642
- **Built with ❤️ following SOLID principles and industry best practices**
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, pause, cancel with lifecycle management
10
+ - **Payment Processing**: Multi-gateway support (Stripe, SSLCommerz, manual, etc.)
11
+ - **Transaction Management**: Income/expense tracking with verification and refunds
12
+ - **Commission Tracking**: Automatic platform commission calculation with gateway fee deduction
13
+ - **Provider Pattern**: Pluggable payment providers (like LangChain/Vercel AI SDK)
14
+ - **Framework Agnostic**: Works with Express, Fastify, Next.js, or standalone
15
+ - **TypeScript Ready**: Full type definitions included
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @classytic/revenue
21
+ npm install @classytic/revenue-manual # For manual payments
22
+ ```
23
+
24
+ ## Quick Start (30 seconds)
25
+
26
+ ```javascript
27
+ import { createRevenue } from '@classytic/revenue';
28
+ import { ManualProvider } from '@classytic/revenue-manual';
29
+ import Transaction from './models/Transaction.js';
30
+
31
+ // 1. Configure
32
+ const revenue = createRevenue({
33
+ models: { Transaction },
34
+ providers: { manual: new ManualProvider() },
35
+ });
36
+
37
+ // 2. Create subscription
38
+ const { subscription, transaction } = await revenue.subscriptions.create({
39
+ data: {
40
+ organizationId,
41
+ customerId,
42
+ referenceId: orderId, // Optional: Link to Order, Subscription, etc.
43
+ referenceModel: 'Order', // Optional: Model name for polymorphic ref
44
+ },
45
+ planKey: 'monthly',
46
+ amount: 1500,
47
+ gateway: 'manual',
48
+ paymentData: { method: 'bkash', walletNumber: '01712345678' },
49
+ });
50
+
51
+ // 3. Verify payment
52
+ await revenue.payments.verify(transaction.gateway.paymentIntentId);
53
+
54
+ // 4. Refund if needed
55
+ await revenue.payments.refund(transaction._id, 500, { reason: 'Partial refund' });
56
+ ```
57
+
58
+ **That's it!** Working revenue system in 3 steps.
59
+
60
+ ## Transaction Model Setup
61
+
62
+ The library requires a Transaction model with specific fields and provides reusable schemas:
63
+
64
+ ```javascript
65
+ import mongoose from 'mongoose';
66
+ import {
67
+ TRANSACTION_TYPE_VALUES,
68
+ TRANSACTION_STATUS_VALUES,
69
+ } from '@classytic/revenue/enums';
70
+ import {
71
+ gatewaySchema,
72
+ paymentDetailsSchema,
73
+ } from '@classytic/revenue/schemas';
74
+
75
+ const transactionSchema = new mongoose.Schema({
76
+ // ============ REQUIRED BY LIBRARY ============
77
+ organizationId: { type: String, required: true, index: true },
78
+ amount: { type: Number, required: true, min: 0 },
79
+ type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // 'income' | 'expense'
80
+ method: { type: String, required: true }, // 'manual' | 'bkash' | 'card' | etc.
81
+ status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
82
+ category: { type: String, required: true }, // Your custom categories
83
+
84
+ // ============ LIBRARY SCHEMAS (nested) ============
85
+ gateway: gatewaySchema, // Payment gateway details
86
+ paymentDetails: paymentDetailsSchema, // Payment info (wallet, bank, etc.)
87
+
88
+ // ============ POLYMORPHIC REFERENCE (recommended) ============
89
+ // Links transaction to any entity (Order, Subscription, Enrollment, etc.)
90
+ referenceId: {
91
+ type: mongoose.Schema.Types.ObjectId,
92
+ refPath: 'referenceModel',
93
+ },
94
+ referenceModel: {
95
+ type: String,
96
+ enum: ['Subscription', 'Order', 'Enrollment', 'Membership'], // Your models
97
+ },
98
+
99
+ // ============ YOUR CUSTOM FIELDS ============
100
+ customerId: String,
101
+ currency: { type: String, default: 'BDT' },
102
+ verifiedAt: Date,
103
+ verifiedBy: mongoose.Schema.Types.ObjectId,
104
+ refundedAmount: Number,
105
+ idempotencyKey: { type: String, unique: true, sparse: true },
106
+ metadata: mongoose.Schema.Types.Mixed,
107
+ }, { timestamps: true });
108
+
109
+ export default mongoose.model('Transaction', transactionSchema);
110
+ ```
111
+
112
+ ## Available Schemas
113
+
114
+ | Schema | Purpose | Key Fields |
115
+ |--------|---------|------------|
116
+ | `gatewaySchema` | Payment gateway integration | `type`, `paymentIntentId`, `sessionId` |
117
+ | `paymentDetailsSchema` | Payment method info | `walletNumber`, `trxId`, `bankName` |
118
+ | `commissionSchema` | Commission tracking (marketplace) | `rate`, `grossAmount`, `gatewayFeeAmount`, `netAmount` |
119
+ | `currentPaymentSchema` | Latest payment (for Order/Subscription models) | `transactionId`, `status`, `verifiedAt` |
120
+ | `subscriptionInfoSchema` | Subscription details (for Order models) | `planKey`, `startDate`, `endDate` |
121
+
122
+ **Usage:** Import and use as nested objects (NOT spread):
123
+
124
+ ```javascript
125
+ import { gatewaySchema } from '@classytic/revenue/schemas';
126
+
127
+ const schema = new mongoose.Schema({
128
+ gateway: gatewaySchema, // Correct - nested
129
+ // ...gatewaySchema, // Wrong - don't spread
130
+ });
131
+ ```
132
+
133
+ ## Core API
134
+
135
+ ### Subscriptions
136
+
137
+ ```javascript
138
+ // Create subscription
139
+ const { subscription, transaction, paymentIntent } =
140
+ await revenue.subscriptions.create({
141
+ data: { organizationId, customerId },
142
+ planKey: 'monthly',
143
+ amount: 1500,
144
+ currency: 'BDT',
145
+ gateway: 'manual',
146
+ paymentData: { method: 'bkash', walletNumber: '01712345678' },
147
+ });
148
+
149
+ // Verify and activate
150
+ await revenue.payments.verify(transaction.gateway.paymentIntentId);
151
+ await revenue.subscriptions.activate(subscription._id);
152
+
153
+ // Renew subscription
154
+ await revenue.subscriptions.renew(subscription._id, {
155
+ gateway: 'manual',
156
+ paymentData: { method: 'nagad' },
157
+ });
158
+
159
+ // Pause/Resume
160
+ await revenue.subscriptions.pause(subscription._id, { reason: 'Customer request' });
161
+ await revenue.subscriptions.resume(subscription._id, { extendPeriod: true });
162
+
163
+ // Cancel
164
+ await revenue.subscriptions.cancel(subscription._id, { immediate: true });
165
+ ```
166
+
167
+ ### Payments
168
+
169
+ ```javascript
170
+ // Verify payment (admin approval for manual)
171
+ const { transaction } = await revenue.payments.verify(paymentIntentId, {
172
+ verifiedBy: adminUserId,
173
+ });
174
+
175
+ // Get payment status
176
+ const { status } = await revenue.payments.getStatus(paymentIntentId);
177
+
178
+ // Refund (creates separate EXPENSE transaction)
179
+ const { transaction, refundTransaction } = await revenue.payments.refund(
180
+ transactionId,
181
+ 500, // Amount or null for full refund
182
+ { reason: 'Customer requested' }
183
+ );
184
+
185
+ // Handle webhook (for automated providers like Stripe)
186
+ const { event, transaction } = await revenue.payments.handleWebhook(
187
+ 'stripe',
188
+ webhookPayload,
189
+ headers
190
+ );
191
+ ```
192
+
193
+ ### Transactions
194
+
195
+ ```javascript
196
+ // Get transaction by ID
197
+ const transaction = await revenue.transactions.get(transactionId);
198
+
199
+ // List with filters
200
+ const { transactions, total } = await revenue.transactions.list(
201
+ { type: 'income', status: 'verified' },
202
+ { limit: 50, sort: { createdAt: -1 } }
203
+ );
204
+
205
+ // Calculate net revenue
206
+ const income = await revenue.transactions.list({ type: 'income' });
207
+ const expense = await revenue.transactions.list({ type: 'expense' });
208
+ const netRevenue = income.total - expense.total;
209
+ ```
210
+
211
+ ## Transaction Types (Income vs Expense)
212
+
213
+ The library uses **double-entry accounting**:
214
+
215
+ - **INCOME** (`'income'`): Money coming in - payments, subscriptions
216
+ - **EXPENSE** (`'expense'`): Money going out - refunds, payouts
217
+
218
+ ```javascript
219
+ const revenue = createRevenue({
220
+ models: { Transaction },
221
+ config: {
222
+ transactionTypeMapping: {
223
+ subscription: 'income',
224
+ purchase: 'income',
225
+ refund: 'expense', // Refunds create separate expense transactions
226
+ },
227
+ },
228
+ });
229
+ ```
230
+
231
+ **Refund Pattern:**
232
+ - Refund creates NEW transaction with `type: 'expense'`
233
+ - Original transaction status becomes `'refunded'` or `'partially_refunded'`
234
+ - Both linked via metadata for audit trail
235
+ - Calculate net: `SUM(income) - SUM(expense)`
236
+
237
+ ## Custom Categories
238
+
239
+ Map logical entities to transaction categories:
240
+
241
+ ```javascript
242
+ const revenue = createRevenue({
243
+ models: { Transaction },
244
+ config: {
245
+ categoryMappings: {
246
+ Order: 'order_subscription',
247
+ PlatformSubscription: 'platform_subscription',
248
+ Membership: 'gym_membership',
249
+ Enrollment: 'course_enrollment',
250
+ },
251
+ },
252
+ });
253
+
254
+ // Usage
255
+ await revenue.subscriptions.create({
256
+ entity: 'Order', // Maps to 'order_subscription' category
257
+ monetizationType: 'subscription',
258
+ // ...
259
+ });
260
+ ```
261
+
262
+ **Note:** `entity` is a logical identifier (not a database model name) for organizing your business logic.
263
+
264
+ ## Commission Tracking (Marketplace)
265
+
266
+ Automatically calculate platform commission with gateway fee deduction:
267
+
268
+ ```javascript
269
+ const revenue = createRevenue({
270
+ models: { Transaction },
271
+ config: {
272
+ // Commission rates by category
273
+ commissionRates: {
274
+ 'product_order': 0.10, // 10% platform commission
275
+ 'course_enrollment': 0.10, // 10% on courses
276
+ 'gym_membership': 0, // No commission
277
+ },
278
+
279
+ // Gateway fees (deducted from commission)
280
+ gatewayFeeRates: {
281
+ 'stripe': 0.029, // 2.9% Stripe fee
282
+ 'bkash': 0.018, // 1.8% bKash fee
283
+ 'manual': 0, // No fee
284
+ },
285
+ },
286
+ });
287
+
288
+ // Commission calculated automatically
289
+ const { transaction } = await revenue.subscriptions.create({
290
+ amount: 10000, // $100
291
+ entity: 'ProductOrder', // 10% commission
292
+ gateway: 'stripe', // → 2.9% fee
293
+ });
294
+
295
+ console.log(transaction.commission);
296
+ // {
297
+ // rate: 0.10,
298
+ // grossAmount: 1000, // $10 (10% of $100)
299
+ // gatewayFeeAmount: 290, // $2.90 (2.9% of $100)
300
+ // netAmount: 710, // $7.10 (platform keeps)
301
+ // status: 'pending'
302
+ // }
303
+
304
+ // Query pending commissions
305
+ const pending = await Transaction.find({ 'commission.status': 'pending' });
306
+ ```
307
+
308
+ **Refund handling:** Commission automatically reversed proportionally when refunds are processed.
309
+
310
+ **See:** [`examples/commission-tracking.js`](examples/commission-tracking.js) for complete guide.
311
+
312
+ ## Polymorphic References
313
+
314
+ Link transactions to any entity (Order, Subscription, Enrollment):
315
+
316
+ ```javascript
317
+ // Create transaction linked to Order
318
+ const { transaction } = await revenue.subscriptions.create({
319
+ data: {
320
+ organizationId,
321
+ customerId,
322
+ referenceId: order._id, // ⭐ Direct field (not metadata)
323
+ referenceModel: 'Order', // ⭐ Model name
324
+ },
325
+ amount: 1500,
326
+ // ...
327
+ });
328
+
329
+ // Query all transactions for an order
330
+ const orderTransactions = await Transaction.find({
331
+ referenceModel: 'Order',
332
+ referenceId: order._id,
333
+ });
334
+
335
+ // Use Mongoose populate
336
+ const transactions = await Transaction.find({ ... })
337
+ .populate('referenceId'); // Populates based on referenceModel
338
+ ```
339
+
340
+ **Why top-level?** Enables proper Mongoose queries and population. Storing in metadata prevents querying and indexing.
341
+
342
+ ## Hooks
343
+
344
+ ```javascript
345
+ const revenue = createRevenue({
346
+ models: { Transaction },
347
+ hooks: {
348
+ 'subscription.created': async ({ subscription, transaction }) => {
349
+ console.log('New subscription:', subscription._id);
350
+ },
351
+ 'payment.verified': async ({ transaction }) => {
352
+ // Send confirmation email
353
+ },
354
+ 'payment.refunded': async ({ refundTransaction }) => {
355
+ // Process refund notification
356
+ },
357
+ },
358
+ });
359
+ ```
360
+
361
+ **Available hooks:**
362
+ - `subscription.created`, `subscription.activated`, `subscription.renewed`
363
+ - `subscription.paused`, `subscription.resumed`, `subscription.cancelled`
364
+ - `payment.verified`, `payment.refunded`
365
+ - `payment.webhook.{type}` (for webhook events)
366
+
367
+ ## Provider Patterns
368
+
369
+ Ready-to-use patterns for popular payment gateways (copy to your project):
370
+
371
+ ### Available Patterns
372
+
373
+ | Pattern | Use Case | Location |
374
+ |---------|----------|----------|
375
+ | **stripe-checkout** | Single-tenant Stripe | [`provider-patterns/stripe-checkout/`](../provider-patterns/stripe-checkout/) |
376
+ | **stripe-connect-standard** | Multi-tenant marketplace | [`provider-patterns/stripe-connect-standard/`](../provider-patterns/stripe-connect-standard/) |
377
+ | **stripe-platform-manual** | Platform collects, manual payout | [`provider-patterns/stripe-platform-manual/`](../provider-patterns/stripe-platform-manual/) |
378
+ | **sslcommerz** | Bangladesh payment gateway | [`provider-patterns/sslcommerz/`](../provider-patterns/sslcommerz/) |
379
+
380
+ **See:** [`provider-patterns/INDEX.md`](../provider-patterns/INDEX.md) for complete guide.
381
+
382
+ ## Building Custom Providers
383
+
384
+ Create providers for any payment gateway:
385
+
386
+ ```javascript
387
+ import { PaymentProvider, PaymentIntent, PaymentResult } from '@classytic/revenue';
388
+
389
+ export class StripeProvider extends PaymentProvider {
390
+ constructor(config) {
391
+ super(config);
392
+ this.name = 'stripe';
393
+ this.stripe = new Stripe(config.apiKey);
394
+ }
395
+
396
+ async createIntent(params) {
397
+ const intent = await this.stripe.paymentIntents.create({
398
+ amount: params.amount,
399
+ currency: params.currency,
400
+ });
401
+
402
+ return new PaymentIntent({
403
+ id: intent.id,
404
+ provider: 'stripe',
405
+ status: intent.status,
406
+ amount: intent.amount,
407
+ currency: intent.currency,
408
+ clientSecret: intent.client_secret,
409
+ raw: intent,
410
+ });
411
+ }
412
+
413
+ async verifyPayment(intentId) {
414
+ const intent = await this.stripe.paymentIntents.retrieve(intentId);
415
+ return new PaymentResult({
416
+ id: intent.id,
417
+ provider: 'stripe',
418
+ status: intent.status === 'succeeded' ? 'succeeded' : 'failed',
419
+ paidAt: new Date(),
420
+ raw: intent,
421
+ });
422
+ }
423
+
424
+ // Implement: getStatus(), refund(), handleWebhook()
425
+ }
426
+ ```
427
+
428
+ **See:** [`docs/guides/PROVIDER_GUIDE.md`](../docs/guides/PROVIDER_GUIDE.md) for complete guide.
429
+
430
+ ## TypeScript
431
+
432
+ Full TypeScript support included:
433
+
434
+ ```typescript
435
+ import { createRevenue, Revenue, PaymentService } from '@classytic/revenue';
436
+ import { TRANSACTION_TYPE, TRANSACTION_STATUS } from '@classytic/revenue/enums';
437
+
438
+ const revenue: Revenue = createRevenue({
439
+ models: { Transaction },
440
+ });
441
+
442
+ // All services are fully typed
443
+ const payment = await revenue.payments.verify(id);
444
+ const subscription = await revenue.subscriptions.create({ ... });
445
+ ```
446
+
447
+ ## Examples
448
+
449
+ - [`examples/basic-usage.js`](examples/basic-usage.js) - Quick start guide
450
+ - [`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
456
+
457
+ ## Error Handling
458
+
459
+ ```javascript
460
+ import {
461
+ TransactionNotFoundError,
462
+ ProviderNotFoundError,
463
+ AlreadyVerifiedError,
464
+ RefundError,
465
+ } from '@classytic/revenue';
466
+
467
+ try {
468
+ await revenue.payments.verify(id);
469
+ } catch (error) {
470
+ if (error instanceof AlreadyVerifiedError) {
471
+ console.log('Already verified');
472
+ } else if (error instanceof TransactionNotFoundError) {
473
+ console.log('Transaction not found');
474
+ }
475
+ }
476
+ ```
477
+
478
+ ## Documentation
479
+
480
+ - **[Provider Guide](../docs/guides/PROVIDER_GUIDE.md)** - Build custom payment providers
481
+ - **[Architecture](../docs/README.md#architecture)** - System design and patterns
482
+ - **[API Reference](../docs/README.md)** - Complete API documentation
483
+
484
+ ## Support
485
+
486
+ - **GitHub**: [classytic/revenue](https://github.com/classytic/revenue)
487
+ - **Issues**: [Report bugs](https://github.com/classytic/revenue/issues)
488
+ - **NPM**: [@classytic/revenue](https://www.npmjs.com/package/@classytic/revenue)
489
+
490
+ ## License
491
+
492
+ MIT © [Classytic](https://github.com/classytic)