@classytic/revenue 0.0.1
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/LICENSE +21 -0
- package/README.md +454 -0
- package/core/builder.js +170 -0
- package/core/container.js +119 -0
- package/core/errors.js +262 -0
- package/enums/index.js +70 -0
- package/enums/monetization.enums.js +15 -0
- package/enums/payment.enums.js +43 -0
- package/enums/subscription.enums.js +33 -0
- package/enums/transaction.enums.js +53 -0
- package/index.js +58 -0
- package/package.json +62 -0
- package/providers/base.js +162 -0
- package/providers/manual.js +171 -0
- package/revenue.d.ts +290 -0
- package/schemas/index.js +21 -0
- package/schemas/subscription/index.js +17 -0
- package/schemas/subscription/info.schema.js +115 -0
- package/schemas/subscription/plan.schema.js +48 -0
- package/schemas/transaction/common.schema.js +22 -0
- package/schemas/transaction/gateway.schema.js +69 -0
- package/schemas/transaction/index.js +20 -0
- package/schemas/transaction/payment.schema.js +110 -0
- package/services/payment.service.js +400 -0
- package/services/subscription.service.js +537 -0
- package/services/transaction.service.js +142 -0
- package/utils/hooks.js +44 -0
- package/utils/index.js +8 -0
- package/utils/logger.js +36 -0
- package/utils/transaction-type.js +254 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Classytic (Classytic)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# @classytic/revenue
|
|
2
|
+
|
|
3
|
+
> Enterprise revenue management with subscriptions and payment processing
|
|
4
|
+
|
|
5
|
+
Thin, focused, production-ready library with smart defaults. Built for SaaS, marketplaces, and subscription businesses.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Subscriptions**: Create, renew, upgrade, downgrade with smart proration
|
|
10
|
+
- **Payment Processing**: Multi-gateway support (Stripe, SSLCommerz, bKash, manual)
|
|
11
|
+
- **Transaction Management**: Complete lifecycle with verification and refunds
|
|
12
|
+
- **Provider Pattern**: Pluggable payment providers (like AI SDK)
|
|
13
|
+
- **Framework Agnostic**: Works with Fastify, Express, Nest, or standalone
|
|
14
|
+
- **Model Flexible**: Plain Mongoose OR @classytic/mongokit Repository
|
|
15
|
+
- **TypeScript Ready**: Full type definitions included
|
|
16
|
+
- **Zero Dependencies**: Only requires `mongoose` as peer dependency
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @classytic/revenue
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Transaction Model Setup
|
|
25
|
+
|
|
26
|
+
Spread library enums/schemas into your Transaction model:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import mongoose from 'mongoose';
|
|
30
|
+
import {
|
|
31
|
+
TRANSACTION_STATUS_VALUES,
|
|
32
|
+
LIBRARY_CATEGORIES,
|
|
33
|
+
} from '@classytic/revenue/enums';
|
|
34
|
+
import {
|
|
35
|
+
gatewaySchema,
|
|
36
|
+
currentPaymentSchema,
|
|
37
|
+
paymentDetailsSchema,
|
|
38
|
+
} from '@classytic/revenue/schemas';
|
|
39
|
+
|
|
40
|
+
// Merge library categories with your own
|
|
41
|
+
const MY_CATEGORIES = {
|
|
42
|
+
...LIBRARY_CATEGORIES, // subscription, purchase
|
|
43
|
+
SALARY: 'salary',
|
|
44
|
+
RENT: 'rent',
|
|
45
|
+
EQUIPMENT: 'equipment',
|
|
46
|
+
// Add as many as you need
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const transactionSchema = new mongoose.Schema({
|
|
50
|
+
// Required by library
|
|
51
|
+
organizationId: { type: String, required: true, index: true },
|
|
52
|
+
amount: { type: Number, required: true, min: 0 },
|
|
53
|
+
status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
|
|
54
|
+
category: { type: String, enum: Object.values(MY_CATEGORIES), required: true },
|
|
55
|
+
|
|
56
|
+
// Spread library schemas
|
|
57
|
+
gateway: gatewaySchema,
|
|
58
|
+
currentPayment: currentPaymentSchema,
|
|
59
|
+
paymentDetails: paymentDetailsSchema,
|
|
60
|
+
|
|
61
|
+
// Add your fields
|
|
62
|
+
notes: String,
|
|
63
|
+
invoiceNumber: String,
|
|
64
|
+
}, { timestamps: true });
|
|
65
|
+
|
|
66
|
+
export default mongoose.model('Transaction', transactionSchema);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**See [`examples/transaction.model.js`](examples/transaction.model.js) for complete example with indexes.**
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Minimal Setup (3 lines)
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { createRevenue } from '@classytic/revenue';
|
|
77
|
+
import Transaction from './models/Transaction.js';
|
|
78
|
+
|
|
79
|
+
// Works out-of-box with built-in manual provider
|
|
80
|
+
const revenue = createRevenue({
|
|
81
|
+
models: { Transaction },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create a subscription
|
|
85
|
+
const { subscription, transaction } = await revenue.subscriptions.create({
|
|
86
|
+
data: { organizationId, customerId },
|
|
87
|
+
planKey: 'monthly',
|
|
88
|
+
amount: 99.99,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That's it! The package works immediately with sensible defaults.
|
|
93
|
+
|
|
94
|
+
## Usage Examples
|
|
95
|
+
|
|
96
|
+
### With Payment Provider
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import { createRevenue } from '@classytic/revenue';
|
|
100
|
+
// Future: import { stripe } from '@classytic/revenue-stripe';
|
|
101
|
+
|
|
102
|
+
const revenue = createRevenue({
|
|
103
|
+
models: { Transaction },
|
|
104
|
+
providers: {
|
|
105
|
+
// Built-in manual provider is auto-included
|
|
106
|
+
// stripe: stripe({ apiKey: process.env.STRIPE_KEY }),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Create subscription with payment gateway
|
|
111
|
+
await revenue.subscriptions.create({
|
|
112
|
+
data: { organizationId, customerId },
|
|
113
|
+
planKey: 'monthly',
|
|
114
|
+
amount: 99.99,
|
|
115
|
+
gateway: 'stripe', // or 'manual'
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### With Hooks
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
const revenue = createRevenue({
|
|
123
|
+
models: { Transaction },
|
|
124
|
+
hooks: {
|
|
125
|
+
'payment.verified': async ({ transaction }) => {
|
|
126
|
+
console.log('Payment verified:', transaction._id);
|
|
127
|
+
// Send email, update analytics, etc.
|
|
128
|
+
},
|
|
129
|
+
'subscription.created': async ({ subscription, transaction }) => {
|
|
130
|
+
console.log('New subscription:', subscription._id);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Custom Logger
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import winston from 'winston';
|
|
140
|
+
|
|
141
|
+
const revenue = createRevenue({
|
|
142
|
+
models: { Transaction },
|
|
143
|
+
logger: winston.createLogger({ /* config */ }),
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Core API
|
|
148
|
+
|
|
149
|
+
### Services
|
|
150
|
+
|
|
151
|
+
The `revenue` instance provides three focused services:
|
|
152
|
+
|
|
153
|
+
#### Subscriptions
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// Create subscription
|
|
157
|
+
const { subscription, transaction, paymentIntent } = await revenue.subscriptions.create({
|
|
158
|
+
data: { organizationId, customerId, ... },
|
|
159
|
+
planKey: 'monthly',
|
|
160
|
+
amount: 99.99,
|
|
161
|
+
currency: 'USD',
|
|
162
|
+
gateway: 'manual', // optional
|
|
163
|
+
metadata: { /* ... */ }, // optional
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Renew subscription
|
|
167
|
+
await revenue.subscriptions.renew(subscriptionId, { amount: 99.99 });
|
|
168
|
+
|
|
169
|
+
// Activate subscription
|
|
170
|
+
await revenue.subscriptions.activate(subscriptionId);
|
|
171
|
+
|
|
172
|
+
// Cancel subscription
|
|
173
|
+
await revenue.subscriptions.cancel(subscriptionId, { immediate: true });
|
|
174
|
+
|
|
175
|
+
// Pause/Resume
|
|
176
|
+
await revenue.subscriptions.pause(subscriptionId);
|
|
177
|
+
await revenue.subscriptions.resume(subscriptionId);
|
|
178
|
+
|
|
179
|
+
// Get/List
|
|
180
|
+
await revenue.subscriptions.get(subscriptionId);
|
|
181
|
+
await revenue.subscriptions.list(filters, options);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Payments
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// Verify payment
|
|
188
|
+
const { transaction, paymentResult, status } = await revenue.payments.verify(
|
|
189
|
+
paymentIntentId,
|
|
190
|
+
{ verifiedBy: userId }
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Get payment status
|
|
194
|
+
const { transaction, status, provider } = await revenue.payments.getStatus(paymentIntentId);
|
|
195
|
+
|
|
196
|
+
// Refund payment
|
|
197
|
+
const { transaction, refundResult } = await revenue.payments.refund(
|
|
198
|
+
paymentId,
|
|
199
|
+
amount, // optional, defaults to full refund
|
|
200
|
+
{ reason: 'Customer request' }
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Handle webhook
|
|
204
|
+
const { event, transaction, status } = await revenue.payments.handleWebhook(
|
|
205
|
+
'stripe',
|
|
206
|
+
payload,
|
|
207
|
+
headers
|
|
208
|
+
);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Transactions
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// Get transaction
|
|
215
|
+
const transaction = await revenue.transactions.get(transactionId);
|
|
216
|
+
|
|
217
|
+
// List transactions
|
|
218
|
+
const { transactions, total, page, limit, pages } = await revenue.transactions.list(
|
|
219
|
+
{ organizationId, status: 'verified' },
|
|
220
|
+
{ limit: 50, skip: 0, sort: { createdAt: -1 } }
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Update transaction
|
|
224
|
+
await revenue.transactions.update(transactionId, { notes: 'Updated' });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Note**: For analytics, exports, or complex queries, use Mongoose aggregations directly on your Transaction model. This keeps the service thin and focused.
|
|
228
|
+
|
|
229
|
+
### Providers
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Get specific provider
|
|
233
|
+
const stripeProvider = revenue.getProvider('stripe');
|
|
234
|
+
|
|
235
|
+
// Check capabilities
|
|
236
|
+
const capabilities = stripeProvider.getCapabilities();
|
|
237
|
+
// {
|
|
238
|
+
// supportsWebhooks: true,
|
|
239
|
+
// supportsRefunds: true,
|
|
240
|
+
// supportsPartialRefunds: true,
|
|
241
|
+
// requiresManualVerification: false
|
|
242
|
+
// }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Error Handling
|
|
246
|
+
|
|
247
|
+
All errors are typed with codes for easy handling:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
import {
|
|
251
|
+
TransactionNotFoundError,
|
|
252
|
+
ProviderNotFoundError,
|
|
253
|
+
RefundNotSupportedError
|
|
254
|
+
} from '@classytic/revenue';
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await revenue.payments.verify(intentId);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error instanceof TransactionNotFoundError) {
|
|
260
|
+
console.log('Transaction not found:', error.metadata.transactionId);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (error.code === 'TRANSACTION_NOT_FOUND') {
|
|
264
|
+
// Handle specific error
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (error.retryable) {
|
|
268
|
+
// Retry the operation
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Error Classes
|
|
274
|
+
|
|
275
|
+
- `RevenueError` - Base error class
|
|
276
|
+
- `ConfigurationError` - Configuration issues
|
|
277
|
+
- `ModelNotRegisteredError` - Model not provided
|
|
278
|
+
- `ProviderError` - Provider-related errors
|
|
279
|
+
- `ProviderNotFoundError` - Provider doesn't exist
|
|
280
|
+
- `PaymentIntentCreationError` - Failed to create payment intent
|
|
281
|
+
- `PaymentVerificationError` - Verification failed
|
|
282
|
+
- `NotFoundError` - Resource not found
|
|
283
|
+
- `TransactionNotFoundError` - Transaction not found
|
|
284
|
+
- `SubscriptionNotFoundError` - Subscription not found
|
|
285
|
+
- `ValidationError` - Validation failed
|
|
286
|
+
- `InvalidAmountError` - Invalid amount
|
|
287
|
+
- `MissingRequiredFieldError` - Required field missing
|
|
288
|
+
- `StateError` - Invalid state
|
|
289
|
+
- `AlreadyVerifiedError` - Already verified
|
|
290
|
+
- `InvalidStateTransitionError` - Invalid state change
|
|
291
|
+
- `RefundNotSupportedError` - Provider doesn't support refunds
|
|
292
|
+
- `RefundError` - Refund failed
|
|
293
|
+
|
|
294
|
+
## Enums & Schemas
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
import {
|
|
298
|
+
TRANSACTION_STATUS,
|
|
299
|
+
PAYMENT_GATEWAY_TYPE,
|
|
300
|
+
SUBSCRIPTION_STATUS,
|
|
301
|
+
PLAN_KEYS,
|
|
302
|
+
currentPaymentSchema,
|
|
303
|
+
subscriptionInfoSchema,
|
|
304
|
+
} from '@classytic/revenue';
|
|
305
|
+
|
|
306
|
+
// Use in your models
|
|
307
|
+
const organizationSchema = new Schema({
|
|
308
|
+
currentPayment: currentPaymentSchema,
|
|
309
|
+
subscription: subscriptionInfoSchema,
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## TypeScript
|
|
314
|
+
|
|
315
|
+
Full TypeScript support included:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { createRevenue, Revenue, RevenueOptions } from '@classytic/revenue';
|
|
319
|
+
|
|
320
|
+
const options: RevenueOptions = {
|
|
321
|
+
models: { Transaction: TransactionModel },
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const revenue: Revenue = createRevenue(options);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Advanced Usage
|
|
328
|
+
|
|
329
|
+
### Custom Providers
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
import { PaymentProvider } from '@classytic/revenue';
|
|
333
|
+
|
|
334
|
+
class MyCustomProvider extends PaymentProvider {
|
|
335
|
+
name = 'my-gateway';
|
|
336
|
+
|
|
337
|
+
async createIntent(params) {
|
|
338
|
+
// Implementation
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async verifyPayment(intentId) {
|
|
342
|
+
// Implementation
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getCapabilities() {
|
|
346
|
+
return {
|
|
347
|
+
supportsWebhooks: true,
|
|
348
|
+
supportsRefunds: true,
|
|
349
|
+
supportsPartialRefunds: false,
|
|
350
|
+
requiresManualVerification: false,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const revenue = createRevenue({
|
|
356
|
+
models: { Transaction },
|
|
357
|
+
providers: {
|
|
358
|
+
'my-gateway': new MyCustomProvider(),
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### DI Container Access
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
const revenue = createRevenue({ models: { Transaction } });
|
|
367
|
+
|
|
368
|
+
// Access container
|
|
369
|
+
const models = revenue.container.get('models');
|
|
370
|
+
const providers = revenue.container.get('providers');
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Hook Events
|
|
374
|
+
|
|
375
|
+
Available hook events:
|
|
376
|
+
|
|
377
|
+
- `payment.verified` - Payment verified
|
|
378
|
+
- `payment.failed` - Payment failed
|
|
379
|
+
- `subscription.created` - Subscription created
|
|
380
|
+
- `subscription.renewed` - Subscription renewed
|
|
381
|
+
- `subscription.activated` - Subscription activated
|
|
382
|
+
- `subscription.cancelled` - Subscription cancelled
|
|
383
|
+
- `subscription.paused` - Subscription paused
|
|
384
|
+
- `subscription.resumed` - Subscription resumed
|
|
385
|
+
- `transaction.created` - Transaction created
|
|
386
|
+
- `transaction.updated` - Transaction updated
|
|
387
|
+
|
|
388
|
+
Hooks are fire-and-forget - they never break the main flow. Errors are logged but don't throw.
|
|
389
|
+
|
|
390
|
+
## Architecture
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
@classytic/revenue (core package)
|
|
394
|
+
├── Builder (createRevenue)
|
|
395
|
+
├── DI Container
|
|
396
|
+
├── Services (focused on lifecycle)
|
|
397
|
+
│ ├── SubscriptionService
|
|
398
|
+
│ ├── PaymentService
|
|
399
|
+
│ └── TransactionService
|
|
400
|
+
├── Providers
|
|
401
|
+
│ ├── base.js (interface)
|
|
402
|
+
│ └── manual.js (built-in)
|
|
403
|
+
├── Error classes
|
|
404
|
+
└── Schemas & Enums
|
|
405
|
+
|
|
406
|
+
@classytic/revenue-stripe (future)
|
|
407
|
+
@classytic/revenue-sslcommerz (future)
|
|
408
|
+
@classytic/revenue-fastify (framework adapter, future)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Design Principles
|
|
412
|
+
|
|
413
|
+
- **KISS**: Keep It Simple, Stupid
|
|
414
|
+
- **DRY**: Don't Repeat Yourself
|
|
415
|
+
- **SOLID**: Single responsibility, focused services
|
|
416
|
+
- **Immutable**: Revenue instance is deeply frozen
|
|
417
|
+
- **Thin Core**: Core operations only, users extend as needed
|
|
418
|
+
- **Smart Defaults**: Works out-of-box with minimal config
|
|
419
|
+
|
|
420
|
+
## Migration from Legacy API
|
|
421
|
+
|
|
422
|
+
If you're using the old `initializeRevenue()` API:
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
// ❌ Old (legacy API - removed)
|
|
426
|
+
import { initializeRevenue, monetization, payment } from '@classytic/revenue';
|
|
427
|
+
initializeRevenue({ TransactionModel, transactionService });
|
|
428
|
+
await monetization.createSubscription(params);
|
|
429
|
+
|
|
430
|
+
// ✅ New (DI-based API)
|
|
431
|
+
import { createRevenue } from '@classytic/revenue';
|
|
432
|
+
const revenue = createRevenue({ models: { Transaction } });
|
|
433
|
+
await revenue.subscriptions.create(params);
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Documentation
|
|
437
|
+
|
|
438
|
+
- **[Building Payment Providers](../docs/guides/PROVIDER_GUIDE.md)** - Create custom payment integrations
|
|
439
|
+
- **[Examples](../docs/examples/)** - Complete usage examples
|
|
440
|
+
- **[Full Documentation](../docs/README.md)** - Comprehensive guides
|
|
441
|
+
|
|
442
|
+
## Support
|
|
443
|
+
|
|
444
|
+
- **GitHub**: https://github.com/classytic/revenue
|
|
445
|
+
- **Issues**: https://github.com/classytic/revenue/issues
|
|
446
|
+
- **npm**: https://npmjs.com/package/@classytic/revenue
|
|
447
|
+
|
|
448
|
+
## License
|
|
449
|
+
|
|
450
|
+
MIT © Classytic (Classytic)
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
**Built with ❤️ following SOLID principles and industry best practices**
|
package/core/builder.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revenue Builder - Main Entry Point
|
|
3
|
+
* @classytic/revenue
|
|
4
|
+
*
|
|
5
|
+
* Factory function to create revenue instance
|
|
6
|
+
* Inspired by: AI SDK, LangChain, Prisma Client
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Container } from './container.js';
|
|
10
|
+
import { SubscriptionService } from '../services/subscription.service.js';
|
|
11
|
+
import { PaymentService } from '../services/payment.service.js';
|
|
12
|
+
import { TransactionService } from '../services/transaction.service.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create revenue instance with dependency injection
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
* @param {Object} options.models - Mongoose models { Transaction, Subscription, etc. }
|
|
19
|
+
* @param {Object} options.providers - Payment providers { manual, stripe, etc. }
|
|
20
|
+
* @param {Object} options.hooks - Event hooks
|
|
21
|
+
* @param {Object} options.config - Additional configuration
|
|
22
|
+
* @param {Object} options.logger - Logger instance
|
|
23
|
+
* @returns {Revenue} Revenue instance
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```javascript
|
|
27
|
+
* import { createRevenue, ManualProvider } from '@classytic/revenue';
|
|
28
|
+
*
|
|
29
|
+
* const revenue = createRevenue({
|
|
30
|
+
* models: {
|
|
31
|
+
* Transaction: TransactionModel,
|
|
32
|
+
* Subscription: SubscriptionModel,
|
|
33
|
+
* },
|
|
34
|
+
* providers: {
|
|
35
|
+
* manual: new ManualProvider(),
|
|
36
|
+
* },
|
|
37
|
+
* config: {
|
|
38
|
+
* targetModels: ['Subscription', 'Membership'],
|
|
39
|
+
* categoryMappings: {
|
|
40
|
+
* Subscription: 'platform_subscription',
|
|
41
|
+
* Membership: 'gym_membership',
|
|
42
|
+
* },
|
|
43
|
+
* },
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Use anywhere
|
|
47
|
+
* const subscription = await revenue.subscriptions.create({ ... });
|
|
48
|
+
* await revenue.payments.verify(txnId);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function createRevenue(options = {}) {
|
|
52
|
+
// Validate required options
|
|
53
|
+
if (!options.models || !options.models.Transaction) {
|
|
54
|
+
throw new Error('createRevenue(): options.models.Transaction is required');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create DI container
|
|
58
|
+
const container = new Container();
|
|
59
|
+
|
|
60
|
+
// Register models
|
|
61
|
+
container.singleton('models', options.models);
|
|
62
|
+
|
|
63
|
+
// Register providers
|
|
64
|
+
const providers = options.providers || {};
|
|
65
|
+
container.singleton('providers', providers);
|
|
66
|
+
|
|
67
|
+
// Register hooks
|
|
68
|
+
container.singleton('hooks', options.hooks || {});
|
|
69
|
+
|
|
70
|
+
// Register config
|
|
71
|
+
const config = {
|
|
72
|
+
targetModels: ['Subscription', 'Membership'],
|
|
73
|
+
categoryMappings: {},
|
|
74
|
+
...options.config,
|
|
75
|
+
};
|
|
76
|
+
container.singleton('config', config);
|
|
77
|
+
|
|
78
|
+
// Register logger
|
|
79
|
+
container.singleton('logger', options.logger || console);
|
|
80
|
+
|
|
81
|
+
// Create service instances (lazy-loaded)
|
|
82
|
+
const services = {
|
|
83
|
+
subscriptions: null,
|
|
84
|
+
payments: null,
|
|
85
|
+
transactions: null,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Create revenue instance
|
|
89
|
+
const revenue = {
|
|
90
|
+
/**
|
|
91
|
+
* Get container (for advanced usage)
|
|
92
|
+
*/
|
|
93
|
+
container,
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Registered payment providers
|
|
97
|
+
*/
|
|
98
|
+
providers,
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Configuration
|
|
102
|
+
*/
|
|
103
|
+
config,
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Subscription service
|
|
107
|
+
* Lazy-loaded on first access
|
|
108
|
+
*/
|
|
109
|
+
get subscriptions() {
|
|
110
|
+
if (!services.subscriptions) {
|
|
111
|
+
services.subscriptions = new SubscriptionService(container);
|
|
112
|
+
}
|
|
113
|
+
return services.subscriptions;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Payment service
|
|
118
|
+
* Lazy-loaded on first access
|
|
119
|
+
*/
|
|
120
|
+
get payments() {
|
|
121
|
+
if (!services.payments) {
|
|
122
|
+
services.payments = new PaymentService(container);
|
|
123
|
+
}
|
|
124
|
+
return services.payments;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Transaction service
|
|
129
|
+
* Lazy-loaded on first access
|
|
130
|
+
*/
|
|
131
|
+
get transactions() {
|
|
132
|
+
if (!services.transactions) {
|
|
133
|
+
services.transactions = new TransactionService(container);
|
|
134
|
+
}
|
|
135
|
+
return services.transactions;
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get a specific provider
|
|
140
|
+
*/
|
|
141
|
+
getProvider(name) {
|
|
142
|
+
const provider = providers[name];
|
|
143
|
+
if (!provider) {
|
|
144
|
+
throw new Error(`Provider "${name}" not found. Available: ${Object.keys(providers).join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
return provider;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Deeply freeze the revenue object (truly immutable)
|
|
151
|
+
Object.freeze(revenue);
|
|
152
|
+
Object.freeze(providers);
|
|
153
|
+
Object.freeze(config);
|
|
154
|
+
|
|
155
|
+
return revenue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Revenue instance type (for documentation)
|
|
160
|
+
* @typedef {Object} Revenue
|
|
161
|
+
* @property {Container} container - DI container (readonly)
|
|
162
|
+
* @property {Object} providers - Payment providers (readonly, frozen)
|
|
163
|
+
* @property {Object} config - Configuration (readonly, frozen)
|
|
164
|
+
* @property {SubscriptionService} subscriptions - Subscription service
|
|
165
|
+
* @property {PaymentService} payments - Payment service
|
|
166
|
+
* @property {TransactionService} transactions - Transaction service
|
|
167
|
+
* @property {Function} getProvider - Get payment provider
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
export default createRevenue;
|