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