@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 +492 -642
- package/enums/index.d.ts +9 -0
- package/enums/index.js +4 -0
- package/enums/transaction.enums.js +16 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/revenue.d.ts +350 -296
- package/services/payment.service.js +441 -400
- package/services/subscription.service.js +61 -6
- package/utils/commission.js +83 -0
- package/utils/index.d.ts +124 -99
- package/utils/index.js +9 -8
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,
|
|
10
|
-
- **Payment Processing**: Multi-gateway support (Stripe, SSLCommerz,
|
|
11
|
-
- **Transaction Management**:
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **TypeScript Ready**: Full type definitions included
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
npm install @classytic/revenue
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
##
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
```javascript
|
|
125
|
-
import {
|
|
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
|
-
const { transaction } = await revenue.
|
|
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
|
-
await revenue.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
await revenue.
|
|
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
|
-
const
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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)
|