@classytic/revenue 0.0.24 → 0.2.0
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 +184 -24
- package/core/builder.js +51 -4
- package/dist/types/core/builder.d.ts +95 -0
- package/dist/types/core/container.d.ts +57 -0
- package/dist/types/core/errors.d.ts +122 -0
- package/dist/types/enums/escrow.enums.d.ts +24 -0
- package/dist/types/enums/index.d.ts +69 -0
- package/dist/types/enums/monetization.enums.d.ts +6 -0
- package/dist/types/enums/payment.enums.d.ts +16 -0
- package/dist/types/enums/split.enums.d.ts +25 -0
- package/dist/types/enums/subscription.enums.d.ts +15 -0
- package/dist/types/enums/transaction.enums.d.ts +24 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/providers/base.d.ts +126 -0
- package/dist/types/schemas/escrow/hold.schema.d.ts +54 -0
- package/dist/types/schemas/escrow/index.d.ts +6 -0
- package/dist/types/schemas/index.d.ts +506 -0
- package/dist/types/schemas/split/index.d.ts +8 -0
- package/dist/types/schemas/split/split.schema.d.ts +142 -0
- package/dist/types/schemas/subscription/index.d.ts +152 -0
- package/dist/types/schemas/subscription/info.schema.d.ts +128 -0
- package/dist/types/schemas/subscription/plan.schema.d.ts +39 -0
- package/dist/types/schemas/transaction/common.schema.d.ts +12 -0
- package/dist/types/schemas/transaction/gateway.schema.d.ts +86 -0
- package/dist/types/schemas/transaction/index.d.ts +202 -0
- package/dist/types/schemas/transaction/payment.schema.d.ts +145 -0
- package/dist/types/services/escrow.service.d.ts +51 -0
- package/dist/types/services/payment.service.d.ts +80 -0
- package/dist/types/services/subscription.service.d.ts +139 -0
- package/dist/types/services/transaction.service.d.ts +40 -0
- package/dist/types/utils/category-resolver.d.ts +46 -0
- package/dist/types/utils/commission-split.d.ts +56 -0
- package/dist/types/utils/commission.d.ts +29 -0
- package/dist/types/utils/hooks.d.ts +17 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/logger.d.ts +12 -0
- package/dist/types/utils/subscription/actions.d.ts +28 -0
- package/dist/types/utils/subscription/index.d.ts +2 -0
- package/dist/types/utils/subscription/period.d.ts +47 -0
- package/dist/types/utils/transaction-type.d.ts +102 -0
- package/enums/escrow.enums.js +36 -0
- package/enums/index.js +36 -0
- package/enums/payment.enums.js +26 -5
- package/enums/split.enums.js +37 -0
- package/index.js +8 -2
- package/package.json +91 -74
- package/schemas/escrow/hold.schema.js +62 -0
- package/schemas/escrow/index.js +15 -0
- package/schemas/index.js +6 -0
- package/schemas/split/index.js +16 -0
- package/schemas/split/split.schema.js +86 -0
- package/services/escrow.service.js +353 -0
- package/services/payment.service.js +64 -3
- package/services/subscription.service.js +36 -16
- package/utils/commission-split.js +180 -0
- package/utils/index.js +6 -0
- package/revenue.d.ts +0 -350
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@ Thin, focused, production-ready library with smart defaults. Built for SaaS, mar
|
|
|
9
9
|
- **Subscriptions**: Create, renew, pause, cancel with lifecycle management
|
|
10
10
|
- **Payment Processing**: Multi-gateway support (Stripe, SSLCommerz, manual, etc.)
|
|
11
11
|
- **Transaction Management**: Income/expense tracking with verification and refunds
|
|
12
|
+
- **Escrow & Hold/Release**: Platform-as-intermediary payment flow (NEW in v0.1.0)
|
|
13
|
+
- **Multi-Party Splits**: Distribute revenue to platform, affiliates, partners (NEW)
|
|
14
|
+
- **Affiliate Commissions**: Built-in support for referral/affiliate programs (NEW)
|
|
12
15
|
- **Commission Tracking**: Automatic platform commission calculation with gateway fee deduction
|
|
13
16
|
- **Provider Pattern**: Pluggable payment providers (like LangChain/Vercel AI SDK)
|
|
14
17
|
- **Framework Agnostic**: Works with Express, Fastify, Next.js, or standalone
|
|
@@ -21,41 +24,51 @@ npm install @classytic/revenue
|
|
|
21
24
|
npm install @classytic/revenue-manual # For manual payments
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
## Quick Start
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Single-Tenant (Simple SaaS)
|
|
25
30
|
|
|
26
31
|
```javascript
|
|
27
32
|
import { createRevenue } from '@classytic/revenue';
|
|
28
33
|
import { ManualProvider } from '@classytic/revenue-manual';
|
|
29
|
-
import Transaction from './models/Transaction.js';
|
|
30
34
|
|
|
31
|
-
// 1. Configure
|
|
32
35
|
const revenue = createRevenue({
|
|
33
36
|
models: { Transaction },
|
|
34
37
|
providers: { manual: new ManualProvider() },
|
|
35
38
|
});
|
|
36
39
|
|
|
37
|
-
//
|
|
38
|
-
const {
|
|
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
|
-
},
|
|
40
|
+
// Create subscription (no organizationId needed)
|
|
41
|
+
const { transaction } = await revenue.subscriptions.create({
|
|
42
|
+
data: { customerId: user._id },
|
|
45
43
|
planKey: 'monthly',
|
|
46
|
-
amount:
|
|
44
|
+
amount: 2999, // $29.99
|
|
47
45
|
gateway: 'manual',
|
|
48
|
-
paymentData: { method: '
|
|
46
|
+
paymentData: { method: 'card' },
|
|
49
47
|
});
|
|
50
48
|
|
|
51
|
-
//
|
|
49
|
+
// Verify → Refund
|
|
52
50
|
await revenue.payments.verify(transaction.gateway.paymentIntentId);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Multi-Tenant (Marketplace/Platform)
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
```javascript
|
|
56
|
+
// Same API, just pass organizationId
|
|
57
|
+
const { transaction } = await revenue.subscriptions.create({
|
|
58
|
+
data: {
|
|
59
|
+
organizationId: vendor._id, // ← Multi-tenant
|
|
60
|
+
customerId: customer._id,
|
|
61
|
+
referenceId: order._id,
|
|
62
|
+
referenceModel: 'Order',
|
|
63
|
+
},
|
|
64
|
+
planKey: 'monthly',
|
|
65
|
+
amount: 1500,
|
|
66
|
+
gateway: 'manual',
|
|
67
|
+
paymentData: { method: 'bkash' },
|
|
68
|
+
});
|
|
56
69
|
```
|
|
57
70
|
|
|
58
|
-
**
|
|
71
|
+
**Works for both!** Same API, different use cases.
|
|
59
72
|
|
|
60
73
|
## Transaction Model Setup
|
|
61
74
|
|
|
@@ -74,12 +87,14 @@ import {
|
|
|
74
87
|
|
|
75
88
|
const transactionSchema = new mongoose.Schema({
|
|
76
89
|
// ============ REQUIRED BY LIBRARY ============
|
|
77
|
-
organizationId: { type: String, required: true, index: true },
|
|
78
90
|
amount: { type: Number, required: true, min: 0 },
|
|
79
91
|
type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // 'income' | 'expense'
|
|
80
92
|
method: { type: String, required: true }, // 'manual' | 'bkash' | 'card' | etc.
|
|
81
93
|
status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
|
|
82
94
|
category: { type: String, required: true }, // Your custom categories
|
|
95
|
+
|
|
96
|
+
// ============ MULTI-TENANT (optional) ============
|
|
97
|
+
organizationId: { type: String, index: true }, // For multi-tenant platforms
|
|
83
98
|
|
|
84
99
|
// ============ LIBRARY SCHEMAS (nested) ============
|
|
85
100
|
gateway: gatewaySchema, // Payment gateway details
|
|
@@ -346,6 +361,103 @@ if (canRenewSubscription(membership)) {
|
|
|
346
361
|
}
|
|
347
362
|
```
|
|
348
363
|
|
|
364
|
+
## Escrow & Multi-Party Splits (v0.1.0+)
|
|
365
|
+
|
|
366
|
+
**NEW:** Platform-as-intermediary payment flow for marketplaces, group buy, and affiliate systems.
|
|
367
|
+
|
|
368
|
+
### Basic Escrow Flow
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// 1. Customer makes purchase
|
|
372
|
+
const { transaction } = await revenue.subscriptions.create({
|
|
373
|
+
amount: 1000,
|
|
374
|
+
gateway: 'stripe',
|
|
375
|
+
// ...
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// 2. Verify payment
|
|
379
|
+
await revenue.payments.verify(transaction._id);
|
|
380
|
+
|
|
381
|
+
// 3. Hold in escrow
|
|
382
|
+
await revenue.escrow.hold(transaction._id);
|
|
383
|
+
|
|
384
|
+
// 4. Split to multiple recipients
|
|
385
|
+
await revenue.escrow.split(transaction._id, [
|
|
386
|
+
{ type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
|
|
387
|
+
{ type: 'affiliate_commission', recipientId: 'affiliate-123', rate: 0.05 },
|
|
388
|
+
]);
|
|
389
|
+
// Auto-releases remainder to organization (85%)
|
|
390
|
+
|
|
391
|
+
// Or manually release
|
|
392
|
+
await revenue.escrow.release(transaction._id, {
|
|
393
|
+
recipientId: 'org-123',
|
|
394
|
+
recipientType: 'organization',
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Affiliate Commission (Simplified API)
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
import { calculateCommissionWithSplits } from '@classytic/revenue';
|
|
402
|
+
|
|
403
|
+
const commission = calculateCommissionWithSplits(
|
|
404
|
+
5000, // amount
|
|
405
|
+
0.10, // platform rate
|
|
406
|
+
0.029, // gateway fee
|
|
407
|
+
{
|
|
408
|
+
affiliateRate: 0.05,
|
|
409
|
+
affiliateId: 'affiliate-123',
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Returns:
|
|
414
|
+
// {
|
|
415
|
+
// grossAmount: 500, // Platform: 10%
|
|
416
|
+
// netAmount: 355, // After gateway fee (2.9%)
|
|
417
|
+
// affiliate: {
|
|
418
|
+
// grossAmount: 250, // Affiliate: 5%
|
|
419
|
+
// netAmount: 250,
|
|
420
|
+
// },
|
|
421
|
+
// splits: [...]
|
|
422
|
+
// }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Multi-Party Splits
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
import { calculateSplits } from '@classytic/revenue';
|
|
429
|
+
|
|
430
|
+
const splits = calculateSplits(10000, [
|
|
431
|
+
{ type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
|
|
432
|
+
{ type: 'affiliate_commission', recipientId: 'level1', rate: 0.05 },
|
|
433
|
+
{ type: 'affiliate_commission', recipientId: 'level2', rate: 0.02 },
|
|
434
|
+
{ type: 'partner_commission', recipientId: 'partner', rate: 0.03 },
|
|
435
|
+
], 0.029); // Gateway fee
|
|
436
|
+
|
|
437
|
+
// Returns splits array with calculated amounts
|
|
438
|
+
// Organization receives: 8000 (80%)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Schemas for Escrow
|
|
442
|
+
|
|
443
|
+
Add to your transaction model when using escrow:
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
import { holdSchema, splitsSchema } from '@classytic/revenue';
|
|
447
|
+
|
|
448
|
+
TransactionSchema.add(holdSchema); // Adds hold/release tracking
|
|
449
|
+
TransactionSchema.add(splitsSchema); // Adds multi-party splits
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Use Cases:**
|
|
453
|
+
- E-commerce marketplaces (hold until delivery confirmed)
|
|
454
|
+
- Course platforms with affiliates
|
|
455
|
+
- Group buy / crowdfunding
|
|
456
|
+
- Multi-level marketing
|
|
457
|
+
- SaaS reseller programs
|
|
458
|
+
|
|
459
|
+
**See:** [`ESCROW_FEATURES.md`](../../ESCROW_FEATURES.md) and [`examples/escrow-flow.js`](examples/escrow-flow.js)
|
|
460
|
+
|
|
349
461
|
## Polymorphic References
|
|
350
462
|
|
|
351
463
|
Link transactions to any entity (Order, Subscription, Enrollment):
|
|
@@ -382,24 +494,71 @@ const transactions = await Transaction.find({ ... })
|
|
|
382
494
|
const revenue = createRevenue({
|
|
383
495
|
models: { Transaction },
|
|
384
496
|
hooks: {
|
|
385
|
-
|
|
386
|
-
|
|
497
|
+
// Monetization lifecycle (specific)
|
|
498
|
+
'purchase.created': async ({ transaction, isFree }) => {
|
|
499
|
+
console.log('One-time purchase:', transaction._id);
|
|
500
|
+
},
|
|
501
|
+
'subscription.created': async ({ transaction, isFree }) => {
|
|
502
|
+
console.log('Recurring subscription:', transaction._id);
|
|
387
503
|
},
|
|
504
|
+
'free.created': async ({ transaction }) => {
|
|
505
|
+
console.log('Free access granted:', transaction._id);
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
// Generic event (fires for all types)
|
|
509
|
+
'monetization.created': async ({ transaction, monetizationType }) => {
|
|
510
|
+
console.log(`${monetizationType} created:`, transaction._id);
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
// Payment lifecycle
|
|
388
514
|
'payment.verified': async ({ transaction }) => {
|
|
389
515
|
// Send confirmation email
|
|
390
516
|
},
|
|
517
|
+
'payment.failed': async ({ transaction, error, provider }) => {
|
|
518
|
+
// Alert admin or send customer notification
|
|
519
|
+
console.error('Payment failed:', error);
|
|
520
|
+
},
|
|
391
521
|
'payment.refunded': async ({ refundTransaction }) => {
|
|
392
522
|
// Process refund notification
|
|
393
523
|
},
|
|
524
|
+
|
|
525
|
+
// Subscription management (requires Subscription model)
|
|
526
|
+
'subscription.activated': async ({ subscription }) => {
|
|
527
|
+
// Subscription activated after payment
|
|
528
|
+
},
|
|
529
|
+
'subscription.renewed': async ({ subscription, renewalCount }) => {
|
|
530
|
+
// Subscription renewed
|
|
531
|
+
},
|
|
532
|
+
'subscription.paused': async ({ subscription }) => {
|
|
533
|
+
// Subscription paused
|
|
534
|
+
},
|
|
535
|
+
'subscription.resumed': async ({ subscription }) => {
|
|
536
|
+
// Subscription resumed
|
|
537
|
+
},
|
|
538
|
+
'subscription.cancelled': async ({ subscription }) => {
|
|
539
|
+
// Subscription cancelled
|
|
540
|
+
},
|
|
394
541
|
},
|
|
395
542
|
});
|
|
396
543
|
```
|
|
397
544
|
|
|
398
545
|
**Available hooks:**
|
|
399
|
-
|
|
546
|
+
|
|
547
|
+
**Monetization Events (specific):**
|
|
548
|
+
- `purchase.created` - One-time purchase
|
|
549
|
+
- `subscription.created` - Recurring subscription
|
|
550
|
+
- `free.created` - Free access granted
|
|
551
|
+
- `monetization.created` - Generic event (fires for all types)
|
|
552
|
+
|
|
553
|
+
**Payment Events:**
|
|
554
|
+
- `payment.verified` - Payment confirmed
|
|
555
|
+
- `payment.failed` - Payment verification failed
|
|
556
|
+
- `payment.refunded` - Refund processed
|
|
557
|
+
- `payment.webhook.{type}` - Webhook events from providers
|
|
558
|
+
|
|
559
|
+
**Subscription Management Events (requires Subscription model):**
|
|
560
|
+
- `subscription.activated`, `subscription.renewed`
|
|
400
561
|
- `subscription.paused`, `subscription.resumed`, `subscription.cancelled`
|
|
401
|
-
- `payment.verified`, `payment.refunded`
|
|
402
|
-
- `payment.webhook.{type}` (for webhook events)
|
|
403
562
|
|
|
404
563
|
## Provider Patterns
|
|
405
564
|
|
|
@@ -483,10 +642,11 @@ const subscription = await revenue.subscriptions.create({ ... });
|
|
|
483
642
|
|
|
484
643
|
## Examples
|
|
485
644
|
|
|
486
|
-
- [`examples/
|
|
645
|
+
- [`examples/single-tenant.js`](examples/single-tenant.js) - Simple SaaS (no organizations)
|
|
487
646
|
- [`examples/transaction.model.js`](examples/transaction.model.js) - Complete model setup
|
|
488
647
|
- [`examples/complete-flow.js`](examples/complete-flow.js) - Full lifecycle (types, refs, state)
|
|
489
648
|
- [`examples/commission-tracking.js`](examples/commission-tracking.js) - Commission calculation
|
|
649
|
+
- [`examples/hooks-v0.2.0.js`](examples/hooks-v0.2.0.js) - v0.2.0 semantic hooks (NEW)
|
|
490
650
|
|
|
491
651
|
## Error Handling
|
|
492
652
|
|
package/core/builder.js
CHANGED
|
@@ -10,13 +10,15 @@ import { Container } from './container.js';
|
|
|
10
10
|
import { SubscriptionService } from '../services/subscription.service.js';
|
|
11
11
|
import { PaymentService } from '../services/payment.service.js';
|
|
12
12
|
import { TransactionService } from '../services/transaction.service.js';
|
|
13
|
+
import { EscrowService } from '../services/escrow.service.js';
|
|
14
|
+
import { ConfigurationError } from './errors.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Create revenue instance with dependency injection
|
|
16
18
|
*
|
|
17
19
|
* @param {Object} options - Configuration options
|
|
18
20
|
* @param {Object} options.models - Mongoose models { Transaction, Subscription, etc. }
|
|
19
|
-
* @param {
|
|
21
|
+
* @param {Record<string, import('../providers/base.js').PaymentProvider>} options.providers - Payment providers - Register ANY custom gateway by name
|
|
20
22
|
* @param {Object} options.hooks - Event hooks
|
|
21
23
|
* @param {Object} options.config - Additional configuration
|
|
22
24
|
* @param {Object} options.logger - Logger instance
|
|
@@ -24,7 +26,8 @@ import { TransactionService } from '../services/transaction.service.js';
|
|
|
24
26
|
*
|
|
25
27
|
* @example
|
|
26
28
|
* ```javascript
|
|
27
|
-
* import { createRevenue
|
|
29
|
+
* import { createRevenue } from '@classytic/revenue';
|
|
30
|
+
* import { ManualProvider } from '@classytic/revenue-manual';
|
|
28
31
|
*
|
|
29
32
|
* const revenue = createRevenue({
|
|
30
33
|
* models: {
|
|
@@ -33,6 +36,10 @@ import { TransactionService } from '../services/transaction.service.js';
|
|
|
33
36
|
* },
|
|
34
37
|
* providers: {
|
|
35
38
|
* manual: new ManualProvider(),
|
|
39
|
+
* bkash: new BkashProvider(), // Custom gateway
|
|
40
|
+
* nagad: new NagadProvider(), // Custom gateway
|
|
41
|
+
* stripe: new StripeProvider(), // Custom gateway
|
|
42
|
+
* // ... register any gateway you want
|
|
36
43
|
* },
|
|
37
44
|
* config: {
|
|
38
45
|
* targetModels: ['Subscription', 'Membership'],
|
|
@@ -43,8 +50,11 @@ import { TransactionService } from '../services/transaction.service.js';
|
|
|
43
50
|
* },
|
|
44
51
|
* });
|
|
45
52
|
*
|
|
46
|
-
* // Use
|
|
47
|
-
* const subscription = await revenue.subscriptions.create({
|
|
53
|
+
* // Use any registered gateway by name
|
|
54
|
+
* const subscription = await revenue.subscriptions.create({
|
|
55
|
+
* gateway: 'bkash', // Use your custom gateway
|
|
56
|
+
* // ...
|
|
57
|
+
* });
|
|
48
58
|
* await revenue.payments.verify(txnId);
|
|
49
59
|
* ```
|
|
50
60
|
*/
|
|
@@ -62,6 +72,14 @@ export function createRevenue(options = {}) {
|
|
|
62
72
|
|
|
63
73
|
// Register providers
|
|
64
74
|
const providers = options.providers || {};
|
|
75
|
+
|
|
76
|
+
// Validate provider interface in non-production
|
|
77
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
78
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
79
|
+
validateProvider(name, provider);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
container.singleton('providers', providers);
|
|
66
84
|
|
|
67
85
|
// Register hooks
|
|
@@ -83,6 +101,7 @@ export function createRevenue(options = {}) {
|
|
|
83
101
|
subscriptions: null,
|
|
84
102
|
payments: null,
|
|
85
103
|
transactions: null,
|
|
104
|
+
escrow: null,
|
|
86
105
|
};
|
|
87
106
|
|
|
88
107
|
// Create revenue instance
|
|
@@ -135,6 +154,17 @@ export function createRevenue(options = {}) {
|
|
|
135
154
|
return services.transactions;
|
|
136
155
|
},
|
|
137
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Escrow service
|
|
159
|
+
* Lazy-loaded on first access
|
|
160
|
+
*/
|
|
161
|
+
get escrow() {
|
|
162
|
+
if (!services.escrow) {
|
|
163
|
+
services.escrow = new EscrowService(container);
|
|
164
|
+
}
|
|
165
|
+
return services.escrow;
|
|
166
|
+
},
|
|
167
|
+
|
|
138
168
|
/**
|
|
139
169
|
* Get a specific provider
|
|
140
170
|
*/
|
|
@@ -155,6 +185,22 @@ export function createRevenue(options = {}) {
|
|
|
155
185
|
return revenue;
|
|
156
186
|
}
|
|
157
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Validate provider implements required interface
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
function validateProvider(name, provider) {
|
|
193
|
+
const required = ['createIntent', 'verifyPayment', 'getStatus', 'getCapabilities'];
|
|
194
|
+
const missing = required.filter(method => typeof provider[method] !== 'function');
|
|
195
|
+
|
|
196
|
+
if (missing.length > 0) {
|
|
197
|
+
throw new ConfigurationError(
|
|
198
|
+
`Provider "${name}" is missing required methods: ${missing.join(', ')}`,
|
|
199
|
+
{ provider: name, missing }
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
158
204
|
/**
|
|
159
205
|
* Revenue instance type (for documentation)
|
|
160
206
|
* @typedef {Object} Revenue
|
|
@@ -164,6 +210,7 @@ export function createRevenue(options = {}) {
|
|
|
164
210
|
* @property {SubscriptionService} subscriptions - Subscription service
|
|
165
211
|
* @property {PaymentService} payments - Payment service
|
|
166
212
|
* @property {TransactionService} transactions - Transaction service
|
|
213
|
+
* @property {EscrowService} escrow - Escrow service
|
|
167
214
|
* @property {Function} getProvider - Get payment provider
|
|
168
215
|
*/
|
|
169
216
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create revenue instance with dependency injection
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} options - Configuration options
|
|
5
|
+
* @param {Object} options.models - Mongoose models { Transaction, Subscription, etc. }
|
|
6
|
+
* @param {Record<string, import('../providers/base.js').PaymentProvider>} options.providers - Payment providers - Register ANY custom gateway by name
|
|
7
|
+
* @param {Object} options.hooks - Event hooks
|
|
8
|
+
* @param {Object} options.config - Additional configuration
|
|
9
|
+
* @param {Object} options.logger - Logger instance
|
|
10
|
+
* @returns {Revenue} Revenue instance
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```javascript
|
|
14
|
+
* import { createRevenue } from '@classytic/revenue';
|
|
15
|
+
* import { ManualProvider } from '@classytic/revenue-manual';
|
|
16
|
+
*
|
|
17
|
+
* const revenue = createRevenue({
|
|
18
|
+
* models: {
|
|
19
|
+
* Transaction: TransactionModel,
|
|
20
|
+
* Subscription: SubscriptionModel,
|
|
21
|
+
* },
|
|
22
|
+
* providers: {
|
|
23
|
+
* manual: new ManualProvider(),
|
|
24
|
+
* bkash: new BkashProvider(), // Custom gateway
|
|
25
|
+
* nagad: new NagadProvider(), // Custom gateway
|
|
26
|
+
* stripe: new StripeProvider(), // Custom gateway
|
|
27
|
+
* // ... register any gateway you want
|
|
28
|
+
* },
|
|
29
|
+
* config: {
|
|
30
|
+
* targetModels: ['Subscription', 'Membership'],
|
|
31
|
+
* categoryMappings: {
|
|
32
|
+
* Subscription: 'platform_subscription',
|
|
33
|
+
* Membership: 'gym_membership',
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Use any registered gateway by name
|
|
39
|
+
* const subscription = await revenue.subscriptions.create({
|
|
40
|
+
* gateway: 'bkash', // Use your custom gateway
|
|
41
|
+
* // ...
|
|
42
|
+
* });
|
|
43
|
+
* await revenue.payments.verify(txnId);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function createRevenue(options?: {
|
|
47
|
+
models: any;
|
|
48
|
+
providers: Record<string, import("../providers/base.js").PaymentProvider>;
|
|
49
|
+
hooks: any;
|
|
50
|
+
config: any;
|
|
51
|
+
logger: any;
|
|
52
|
+
}): Revenue;
|
|
53
|
+
export default createRevenue;
|
|
54
|
+
/**
|
|
55
|
+
* Revenue instance type (for documentation)
|
|
56
|
+
*/
|
|
57
|
+
export type Revenue = {
|
|
58
|
+
/**
|
|
59
|
+
* - DI container (readonly)
|
|
60
|
+
*/
|
|
61
|
+
container: Container;
|
|
62
|
+
/**
|
|
63
|
+
* - Payment providers (readonly, frozen)
|
|
64
|
+
*/
|
|
65
|
+
providers: any;
|
|
66
|
+
/**
|
|
67
|
+
* - Configuration (readonly, frozen)
|
|
68
|
+
*/
|
|
69
|
+
config: any;
|
|
70
|
+
/**
|
|
71
|
+
* - Subscription service
|
|
72
|
+
*/
|
|
73
|
+
subscriptions: SubscriptionService;
|
|
74
|
+
/**
|
|
75
|
+
* - Payment service
|
|
76
|
+
*/
|
|
77
|
+
payments: PaymentService;
|
|
78
|
+
/**
|
|
79
|
+
* - Transaction service
|
|
80
|
+
*/
|
|
81
|
+
transactions: TransactionService;
|
|
82
|
+
/**
|
|
83
|
+
* - Escrow service
|
|
84
|
+
*/
|
|
85
|
+
escrow: EscrowService;
|
|
86
|
+
/**
|
|
87
|
+
* - Get payment provider
|
|
88
|
+
*/
|
|
89
|
+
getProvider: Function;
|
|
90
|
+
};
|
|
91
|
+
import { Container } from './container.js';
|
|
92
|
+
import { SubscriptionService } from '../services/subscription.service.js';
|
|
93
|
+
import { PaymentService } from '../services/payment.service.js';
|
|
94
|
+
import { TransactionService } from '../services/transaction.service.js';
|
|
95
|
+
import { EscrowService } from '../services/escrow.service.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Injection Container
|
|
3
|
+
* @classytic/revenue
|
|
4
|
+
*
|
|
5
|
+
* Lightweight DI container for managing dependencies
|
|
6
|
+
* Inspired by: Awilix, InversifyJS but much simpler
|
|
7
|
+
*/
|
|
8
|
+
export class Container {
|
|
9
|
+
_services: Map<any, any>;
|
|
10
|
+
_singletons: Map<any, any>;
|
|
11
|
+
/**
|
|
12
|
+
* Register a service
|
|
13
|
+
* @param {string} name - Service name
|
|
14
|
+
* @param {any} implementation - Service implementation or factory
|
|
15
|
+
* @param {Object} options - Registration options
|
|
16
|
+
*/
|
|
17
|
+
register(name: string, implementation: any, options?: any): this;
|
|
18
|
+
/**
|
|
19
|
+
* Register a singleton service
|
|
20
|
+
* @param {string} name - Service name
|
|
21
|
+
* @param {any} implementation - Service implementation
|
|
22
|
+
*/
|
|
23
|
+
singleton(name: string, implementation: any): this;
|
|
24
|
+
/**
|
|
25
|
+
* Register a transient service (new instance each time)
|
|
26
|
+
* @param {string} name - Service name
|
|
27
|
+
* @param {Function} factory - Factory function
|
|
28
|
+
*/
|
|
29
|
+
transient(name: string, factory: Function): this;
|
|
30
|
+
/**
|
|
31
|
+
* Get a service from the container
|
|
32
|
+
* @param {string} name - Service name
|
|
33
|
+
* @returns {any} Service instance
|
|
34
|
+
*/
|
|
35
|
+
get(name: string): any;
|
|
36
|
+
/**
|
|
37
|
+
* Check if service is registered
|
|
38
|
+
* @param {string} name - Service name
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
has(name: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get all registered service names
|
|
44
|
+
* @returns {string[]}
|
|
45
|
+
*/
|
|
46
|
+
keys(): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Clear all services (useful for testing)
|
|
49
|
+
*/
|
|
50
|
+
clear(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Create a child container (for scoped dependencies)
|
|
53
|
+
* @returns {Container}
|
|
54
|
+
*/
|
|
55
|
+
createScope(): Container;
|
|
56
|
+
}
|
|
57
|
+
export default Container;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if error is retryable
|
|
3
|
+
*/
|
|
4
|
+
export function isRetryable(error: any): any;
|
|
5
|
+
/**
|
|
6
|
+
* Check if error is from revenue package
|
|
7
|
+
*/
|
|
8
|
+
export function isRevenueError(error: any): error is RevenueError;
|
|
9
|
+
/**
|
|
10
|
+
* Revenue Error Classes
|
|
11
|
+
* @classytic/revenue
|
|
12
|
+
*
|
|
13
|
+
* Typed errors with codes for better error handling
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Base Revenue Error
|
|
17
|
+
*/
|
|
18
|
+
export class RevenueError extends Error {
|
|
19
|
+
constructor(message: any, code: any, options?: {});
|
|
20
|
+
code: any;
|
|
21
|
+
retryable: any;
|
|
22
|
+
metadata: any;
|
|
23
|
+
toJSON(): {
|
|
24
|
+
name: string;
|
|
25
|
+
message: string;
|
|
26
|
+
code: any;
|
|
27
|
+
retryable: any;
|
|
28
|
+
metadata: any;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration Errors
|
|
33
|
+
*/
|
|
34
|
+
export class ConfigurationError extends RevenueError {
|
|
35
|
+
constructor(message: any, metadata?: {});
|
|
36
|
+
}
|
|
37
|
+
export class ModelNotRegisteredError extends ConfigurationError {
|
|
38
|
+
constructor(modelName: any);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Provider Errors
|
|
42
|
+
*/
|
|
43
|
+
export class ProviderError extends RevenueError {
|
|
44
|
+
}
|
|
45
|
+
export class ProviderNotFoundError extends ProviderError {
|
|
46
|
+
constructor(providerName: any, availableProviders?: any[]);
|
|
47
|
+
}
|
|
48
|
+
export class ProviderCapabilityError extends ProviderError {
|
|
49
|
+
constructor(providerName: any, capability: any);
|
|
50
|
+
}
|
|
51
|
+
export class PaymentIntentCreationError extends ProviderError {
|
|
52
|
+
constructor(providerName: any, originalError: any);
|
|
53
|
+
}
|
|
54
|
+
export class PaymentVerificationError extends ProviderError {
|
|
55
|
+
constructor(paymentIntentId: any, reason: any);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resource Not Found Errors
|
|
59
|
+
*/
|
|
60
|
+
export class NotFoundError extends RevenueError {
|
|
61
|
+
}
|
|
62
|
+
export class SubscriptionNotFoundError extends NotFoundError {
|
|
63
|
+
constructor(subscriptionId: any);
|
|
64
|
+
}
|
|
65
|
+
export class TransactionNotFoundError extends NotFoundError {
|
|
66
|
+
constructor(transactionId: any);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validation Errors
|
|
70
|
+
*/
|
|
71
|
+
export class ValidationError extends RevenueError {
|
|
72
|
+
constructor(message: any, metadata?: {});
|
|
73
|
+
}
|
|
74
|
+
export class InvalidAmountError extends ValidationError {
|
|
75
|
+
constructor(amount: any);
|
|
76
|
+
}
|
|
77
|
+
export class MissingRequiredFieldError extends ValidationError {
|
|
78
|
+
constructor(fieldName: any);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* State Errors
|
|
82
|
+
*/
|
|
83
|
+
export class StateError extends RevenueError {
|
|
84
|
+
}
|
|
85
|
+
export class AlreadyVerifiedError extends StateError {
|
|
86
|
+
constructor(transactionId: any);
|
|
87
|
+
}
|
|
88
|
+
export class InvalidStateTransitionError extends StateError {
|
|
89
|
+
constructor(resourceType: any, resourceId: any, fromState: any, toState: any);
|
|
90
|
+
}
|
|
91
|
+
export class SubscriptionNotActiveError extends StateError {
|
|
92
|
+
constructor(subscriptionId: any);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Operation Errors
|
|
96
|
+
*/
|
|
97
|
+
export class OperationError extends RevenueError {
|
|
98
|
+
}
|
|
99
|
+
export class RefundNotSupportedError extends OperationError {
|
|
100
|
+
constructor(providerName: any);
|
|
101
|
+
}
|
|
102
|
+
export class RefundError extends OperationError {
|
|
103
|
+
constructor(transactionId: any, reason: any);
|
|
104
|
+
}
|
|
105
|
+
export namespace ERROR_CODES {
|
|
106
|
+
let CONFIGURATION_ERROR: string;
|
|
107
|
+
let MODEL_NOT_REGISTERED: string;
|
|
108
|
+
let PROVIDER_NOT_FOUND: string;
|
|
109
|
+
let PROVIDER_CAPABILITY_NOT_SUPPORTED: string;
|
|
110
|
+
let PAYMENT_INTENT_CREATION_FAILED: string;
|
|
111
|
+
let PAYMENT_VERIFICATION_FAILED: string;
|
|
112
|
+
let SUBSCRIPTION_NOT_FOUND: string;
|
|
113
|
+
let TRANSACTION_NOT_FOUND: string;
|
|
114
|
+
let VALIDATION_ERROR: string;
|
|
115
|
+
let INVALID_AMOUNT: string;
|
|
116
|
+
let MISSING_REQUIRED_FIELD: string;
|
|
117
|
+
let ALREADY_VERIFIED: string;
|
|
118
|
+
let INVALID_STATE_TRANSITION: string;
|
|
119
|
+
let SUBSCRIPTION_NOT_ACTIVE: string;
|
|
120
|
+
let REFUND_NOT_SUPPORTED: string;
|
|
121
|
+
let REFUND_FAILED: string;
|
|
122
|
+
}
|