@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.
package/README.md CHANGED
@@ -1,380 +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, 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
- - **Provider Pattern**: Pluggable payment providers (like LangChain/Vercel AI SDK)
13
- - **Framework Agnostic**: Works with Express, Fastify, Next.js, or standalone
14
- - **TypeScript Ready**: Full type definitions included
15
-
16
- ## Installation
17
-
18
- ```bash
19
- npm install @classytic/revenue
20
- npm install @classytic/revenue-manual # For manual payments
21
- ```
22
-
23
- ## Quick Start (30 seconds)
24
-
25
- ```javascript
26
- import { createRevenue } from '@classytic/revenue';
27
- import { ManualProvider } from '@classytic/revenue-manual';
28
- import Transaction from './models/Transaction.js';
29
-
30
- // 1. Configure
31
- const revenue = createRevenue({
32
- models: { Transaction },
33
- providers: { manual: new ManualProvider() },
34
- });
35
-
36
- // 2. Create subscription
37
- const { subscription, transaction } = await revenue.subscriptions.create({
38
- data: { organizationId, customerId },
39
- planKey: 'monthly',
40
- amount: 1500,
41
- gateway: 'manual',
42
- paymentData: { method: 'bkash', walletNumber: '01712345678' },
43
- });
44
-
45
- // 3. Verify payment
46
- await revenue.payments.verify(transaction.gateway.paymentIntentId);
47
-
48
- // 4. Refund if needed
49
- await revenue.payments.refund(transaction._id, 500, { reason: 'Partial refund' });
50
- ```
51
-
52
- **That's it!** Working revenue system in 3 steps.
53
-
54
- ## Transaction Model Setup
55
-
56
- The library requires a Transaction model with specific fields and provides reusable schemas:
57
-
58
- ```javascript
59
- import mongoose from 'mongoose';
60
- import {
61
- TRANSACTION_TYPE_VALUES,
62
- TRANSACTION_STATUS_VALUES,
63
- } from '@classytic/revenue/enums';
64
- import {
65
- gatewaySchema,
66
- paymentDetailsSchema,
67
- } from '@classytic/revenue/schemas';
68
-
69
- const transactionSchema = new mongoose.Schema({
70
- // ============ REQUIRED BY LIBRARY ============
71
- organizationId: { type: String, required: true, index: true },
72
- amount: { type: Number, required: true, min: 0 },
73
- type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // 'income' | 'expense'
74
- method: { type: String, required: true }, // 'manual' | 'bkash' | 'card' | etc.
75
- status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
76
- category: { type: String, required: true }, // Your custom categories
77
-
78
- // ============ LIBRARY SCHEMAS (nested) ============
79
- gateway: gatewaySchema, // Payment gateway details
80
- paymentDetails: paymentDetailsSchema, // Payment info (wallet, bank, etc.)
81
-
82
- // ============ YOUR CUSTOM FIELDS ============
83
- customerId: String,
84
- currency: { type: String, default: 'BDT' },
85
- verifiedAt: Date,
86
- verifiedBy: mongoose.Schema.Types.ObjectId,
87
- refundedAmount: Number,
88
- idempotencyKey: { type: String, unique: true, sparse: true },
89
- metadata: mongoose.Schema.Types.Mixed,
90
- }, { timestamps: true });
91
-
92
- export default mongoose.model('Transaction', transactionSchema);
93
- ```
94
-
95
- ## Available Schemas
96
-
97
- | Schema | Purpose | Key Fields |
98
- |--------|---------|------------|
99
- | `gatewaySchema` | Payment gateway integration | `type`, `paymentIntentId`, `sessionId` |
100
- | `paymentDetailsSchema` | Payment method info | `walletNumber`, `trxId`, `bankName` |
101
- | `commissionSchema` | Commission tracking | `rate`, `grossAmount`, `netAmount` |
102
- | `currentPaymentSchema` | Latest payment (for Order/Subscription models) | `transactionId`, `status`, `verifiedAt` |
103
- | `subscriptionInfoSchema` | Subscription details (for Order models) | `planKey`, `startDate`, `endDate` |
104
-
105
- **Usage:** Import and use as nested objects (NOT spread):
106
-
107
- ```javascript
108
- import { gatewaySchema } from '@classytic/revenue/schemas';
109
-
110
- const schema = new mongoose.Schema({
111
- gateway: gatewaySchema, // ✅ Correct - nested
112
- // ...gatewaySchema, // ❌ Wrong - don't spread
113
- });
114
- ```
115
-
116
- ## Core API
117
-
118
- ### Subscriptions
119
-
120
- ```javascript
121
- // Create subscription
122
- const { subscription, transaction, paymentIntent } =
123
- await revenue.subscriptions.create({
124
- data: { organizationId, customerId },
125
- planKey: 'monthly',
126
- amount: 1500,
127
- currency: 'BDT',
128
- gateway: 'manual',
129
- paymentData: { method: 'bkash', walletNumber: '01712345678' },
130
- });
131
-
132
- // Verify and activate
133
- await revenue.payments.verify(transaction.gateway.paymentIntentId);
134
- await revenue.subscriptions.activate(subscription._id);
135
-
136
- // Renew subscription
137
- await revenue.subscriptions.renew(subscription._id, {
138
- gateway: 'manual',
139
- paymentData: { method: 'nagad' },
140
- });
141
-
142
- // Pause/Resume
143
- await revenue.subscriptions.pause(subscription._id, { reason: 'Customer request' });
144
- await revenue.subscriptions.resume(subscription._id, { extendPeriod: true });
145
-
146
- // Cancel
147
- await revenue.subscriptions.cancel(subscription._id, { immediate: true });
148
- ```
149
-
150
- ### Payments
151
-
152
- ```javascript
153
- // Verify payment (admin approval for manual)
154
- const { transaction } = await revenue.payments.verify(paymentIntentId, {
155
- verifiedBy: adminUserId,
156
- });
157
-
158
- // Get payment status
159
- const { status } = await revenue.payments.getStatus(paymentIntentId);
160
-
161
- // Refund (creates separate EXPENSE transaction)
162
- const { transaction, refundTransaction } = await revenue.payments.refund(
163
- transactionId,
164
- 500, // Amount or null for full refund
165
- { reason: 'Customer requested' }
166
- );
167
-
168
- // Handle webhook (for automated providers like Stripe)
169
- const { event, transaction } = await revenue.payments.handleWebhook(
170
- 'stripe',
171
- webhookPayload,
172
- headers
173
- );
174
- ```
175
-
176
- ### Transactions
177
-
178
- ```javascript
179
- // Get transaction by ID
180
- const transaction = await revenue.transactions.get(transactionId);
181
-
182
- // List with filters
183
- const { transactions, total } = await revenue.transactions.list(
184
- { type: 'income', status: 'verified' },
185
- { limit: 50, sort: { createdAt: -1 } }
186
- );
187
-
188
- // Calculate net revenue
189
- const income = await revenue.transactions.list({ type: 'income' });
190
- const expense = await revenue.transactions.list({ type: 'expense' });
191
- const netRevenue = income.total - expense.total;
192
- ```
193
-
194
- ## Transaction Types (Income vs Expense)
195
-
196
- The library uses **double-entry accounting**:
197
-
198
- - **INCOME** (`'income'`): Money coming in - payments, subscriptions
199
- - **EXPENSE** (`'expense'`): Money going out - refunds, payouts
200
-
201
- ```javascript
202
- const revenue = createRevenue({
203
- models: { Transaction },
204
- config: {
205
- transactionTypeMapping: {
206
- subscription: 'income',
207
- purchase: 'income',
208
- refund: 'expense', // Refunds create separate expense transactions
209
- },
210
- },
211
- });
212
- ```
213
-
214
- **Refund Pattern:**
215
- - Refund creates NEW transaction with `type: 'expense'`
216
- - Original transaction status becomes `'refunded'` or `'partially_refunded'`
217
- - Both linked via metadata for audit trail
218
- - Calculate net: `SUM(income) - SUM(expense)`
219
-
220
- ## Custom Categories
221
-
222
- Map logical entities to transaction categories:
223
-
224
- ```javascript
225
- const revenue = createRevenue({
226
- models: { Transaction },
227
- config: {
228
- categoryMappings: {
229
- Order: 'order_subscription',
230
- PlatformSubscription: 'platform_subscription',
231
- Membership: 'gym_membership',
232
- Enrollment: 'course_enrollment',
233
- },
234
- },
235
- });
236
-
237
- // Usage
238
- await revenue.subscriptions.create({
239
- entity: 'Order', // Maps to 'order_subscription' category
240
- monetizationType: 'subscription',
241
- // ...
242
- });
243
- ```
244
-
245
- **Note:** `entity` is a logical identifier (not a database model name) for organizing your business logic.
246
-
247
- ## Hooks
248
-
249
- ```javascript
250
- const revenue = createRevenue({
251
- models: { Transaction },
252
- hooks: {
253
- 'subscription.created': async ({ subscription, transaction }) => {
254
- console.log('New subscription:', subscription._id);
255
- },
256
- 'payment.verified': async ({ transaction }) => {
257
- // Send confirmation email
258
- },
259
- 'payment.refunded': async ({ refundTransaction }) => {
260
- // Process refund notification
261
- },
262
- },
263
- });
264
- ```
265
-
266
- **Available hooks:**
267
- - `subscription.created`, `subscription.activated`, `subscription.renewed`
268
- - `subscription.paused`, `subscription.resumed`, `subscription.cancelled`
269
- - `payment.verified`, `payment.refunded`
270
- - `payment.webhook.{type}` (for webhook events)
271
-
272
- ## Building Payment Providers
273
-
274
- Create custom providers for Stripe, PayPal, etc.:
275
-
276
- ```javascript
277
- import { PaymentProvider, PaymentIntent, PaymentResult } from '@classytic/revenue';
278
-
279
- export class StripeProvider extends PaymentProvider {
280
- constructor(config) {
281
- super(config);
282
- this.name = 'stripe';
283
- this.stripe = new Stripe(config.apiKey);
284
- }
285
-
286
- async createIntent(params) {
287
- const intent = await this.stripe.paymentIntents.create({
288
- amount: params.amount,
289
- currency: params.currency,
290
- });
291
-
292
- return new PaymentIntent({
293
- id: intent.id,
294
- provider: 'stripe',
295
- status: intent.status,
296
- amount: intent.amount,
297
- currency: intent.currency,
298
- clientSecret: intent.client_secret,
299
- raw: intent,
300
- });
301
- }
302
-
303
- async verifyPayment(intentId) {
304
- const intent = await this.stripe.paymentIntents.retrieve(intentId);
305
- return new PaymentResult({
306
- id: intent.id,
307
- provider: 'stripe',
308
- status: intent.status === 'succeeded' ? 'succeeded' : 'failed',
309
- paidAt: new Date(),
310
- raw: intent,
311
- });
312
- }
313
-
314
- // Implement: getStatus(), refund(), handleWebhook()
315
- }
316
- ```
317
-
318
- **See:** [`docs/guides/PROVIDER_GUIDE.md`](../docs/guides/PROVIDER_GUIDE.md) for complete guide.
319
-
320
- ## TypeScript
321
-
322
- Full TypeScript support included:
323
-
324
- ```typescript
325
- import { createRevenue, Revenue, PaymentService } from '@classytic/revenue';
326
- import { TRANSACTION_TYPE, TRANSACTION_STATUS } from '@classytic/revenue/enums';
327
-
328
- const revenue: Revenue = createRevenue({
329
- models: { Transaction },
330
- });
331
-
332
- // All services are fully typed
333
- const payment = await revenue.payments.verify(id);
334
- const subscription = await revenue.subscriptions.create({ ... });
335
- ```
336
-
337
- ## Examples
338
-
339
- - [`examples/basic-usage.js`](examples/basic-usage.js) - Quick start guide
340
- - [`examples/transaction.model.js`](examples/transaction.model.js) - Complete model setup
341
- - [`examples/transaction-type-mapping.js`](examples/transaction-type-mapping.js) - Income/expense configuration
342
- - [`examples/complete-flow.js`](examples/complete-flow.js) - Full lifecycle with state management
343
- - [`examples/multivendor-platform.js`](examples/multivendor-platform.js) - Multi-tenant setup
344
-
345
- ## Error Handling
346
-
347
- ```javascript
348
- import {
349
- TransactionNotFoundError,
350
- ProviderNotFoundError,
351
- AlreadyVerifiedError,
352
- RefundError,
353
- } from '@classytic/revenue';
354
-
355
- try {
356
- await revenue.payments.verify(id);
357
- } catch (error) {
358
- if (error instanceof AlreadyVerifiedError) {
359
- console.log('Already verified');
360
- } else if (error instanceof TransactionNotFoundError) {
361
- console.log('Transaction not found');
362
- }
363
- }
364
- ```
365
-
366
- ## Documentation
367
-
368
- - **[Provider Guide](../docs/guides/PROVIDER_GUIDE.md)** - Build custom payment providers
369
- - **[Architecture](../docs/README.md#architecture)** - System design and patterns
370
- - **[API Reference](../docs/README.md)** - Complete API documentation
371
-
372
- ## Support
373
-
374
- - **GitHub**: [classytic/revenue](https://github.com/classytic/revenue)
375
- - **Issues**: [Report bugs](https://github.com/classytic/revenue/issues)
376
- - **NPM**: [@classytic/revenue](https://www.npmjs.com/package/@classytic/revenue)
377
-
378
- ## License
379
-
380
- MIT © [Classytic](https://github.com/classytic)
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)