@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 +492 -380
- package/index.js +2 -0
- package/package.json +1 -1
- package/revenue.d.ts +350 -320
- package/services/payment.service.js +441 -431
- package/services/subscription.service.js +44 -4
- package/utils/commission.js +83 -0
- package/utils/index.d.ts +124 -99
- package/utils/index.js +9 -8
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
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
npm install @classytic/revenue
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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)
|