@classytic/revenue 0.2.4 → 1.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.
Files changed (111) hide show
  1. package/README.md +498 -501
  2. package/dist/actions-CwG-b7fR.d.ts +519 -0
  3. package/dist/core/index.d.ts +884 -0
  4. package/dist/core/index.js +2941 -0
  5. package/dist/core/index.js.map +1 -0
  6. package/dist/enums/index.d.ts +130 -0
  7. package/dist/enums/index.js +167 -0
  8. package/dist/enums/index.js.map +1 -0
  9. package/dist/index-BnJWVXuw.d.ts +378 -0
  10. package/dist/index-ChVD3P9k.d.ts +504 -0
  11. package/dist/index.d.ts +42 -0
  12. package/dist/index.js +4362 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/providers/index.d.ts +132 -0
  15. package/dist/providers/index.js +122 -0
  16. package/dist/providers/index.js.map +1 -0
  17. package/dist/retry-80lBCmSe.d.ts +234 -0
  18. package/dist/schemas/index.d.ts +906 -0
  19. package/dist/schemas/index.js +533 -0
  20. package/dist/schemas/index.js.map +1 -0
  21. package/dist/schemas/validation.d.ts +309 -0
  22. package/dist/schemas/validation.js +249 -0
  23. package/dist/schemas/validation.js.map +1 -0
  24. package/dist/services/index.d.ts +3 -0
  25. package/dist/services/index.js +1632 -0
  26. package/dist/services/index.js.map +1 -0
  27. package/dist/split.enums-DHdM1YAV.d.ts +255 -0
  28. package/dist/split.schema-CETjPq10.d.ts +976 -0
  29. package/dist/utils/index.d.ts +24 -0
  30. package/dist/utils/index.js +1067 -0
  31. package/dist/utils/index.js.map +1 -0
  32. package/package.json +48 -32
  33. package/core/builder.js +0 -219
  34. package/core/container.js +0 -119
  35. package/core/errors.js +0 -262
  36. package/dist/types/core/builder.d.ts +0 -97
  37. package/dist/types/core/container.d.ts +0 -57
  38. package/dist/types/core/errors.d.ts +0 -122
  39. package/dist/types/enums/escrow.enums.d.ts +0 -24
  40. package/dist/types/enums/index.d.ts +0 -69
  41. package/dist/types/enums/monetization.enums.d.ts +0 -6
  42. package/dist/types/enums/payment.enums.d.ts +0 -16
  43. package/dist/types/enums/split.enums.d.ts +0 -25
  44. package/dist/types/enums/subscription.enums.d.ts +0 -15
  45. package/dist/types/enums/transaction.enums.d.ts +0 -24
  46. package/dist/types/index.d.ts +0 -22
  47. package/dist/types/providers/base.d.ts +0 -128
  48. package/dist/types/schemas/escrow/hold.schema.d.ts +0 -54
  49. package/dist/types/schemas/escrow/index.d.ts +0 -6
  50. package/dist/types/schemas/index.d.ts +0 -506
  51. package/dist/types/schemas/split/index.d.ts +0 -8
  52. package/dist/types/schemas/split/split.schema.d.ts +0 -142
  53. package/dist/types/schemas/subscription/index.d.ts +0 -152
  54. package/dist/types/schemas/subscription/info.schema.d.ts +0 -128
  55. package/dist/types/schemas/subscription/plan.schema.d.ts +0 -39
  56. package/dist/types/schemas/transaction/common.schema.d.ts +0 -12
  57. package/dist/types/schemas/transaction/gateway.schema.d.ts +0 -86
  58. package/dist/types/schemas/transaction/index.d.ts +0 -202
  59. package/dist/types/schemas/transaction/payment.schema.d.ts +0 -145
  60. package/dist/types/services/escrow.service.d.ts +0 -51
  61. package/dist/types/services/monetization.service.d.ts +0 -193
  62. package/dist/types/services/payment.service.d.ts +0 -117
  63. package/dist/types/services/transaction.service.d.ts +0 -40
  64. package/dist/types/utils/category-resolver.d.ts +0 -46
  65. package/dist/types/utils/commission-split.d.ts +0 -56
  66. package/dist/types/utils/commission.d.ts +0 -29
  67. package/dist/types/utils/hooks.d.ts +0 -17
  68. package/dist/types/utils/index.d.ts +0 -6
  69. package/dist/types/utils/logger.d.ts +0 -12
  70. package/dist/types/utils/subscription/actions.d.ts +0 -28
  71. package/dist/types/utils/subscription/index.d.ts +0 -2
  72. package/dist/types/utils/subscription/period.d.ts +0 -47
  73. package/dist/types/utils/transaction-type.d.ts +0 -102
  74. package/enums/escrow.enums.js +0 -36
  75. package/enums/index.d.ts +0 -116
  76. package/enums/index.js +0 -110
  77. package/enums/monetization.enums.js +0 -15
  78. package/enums/payment.enums.js +0 -64
  79. package/enums/split.enums.js +0 -37
  80. package/enums/subscription.enums.js +0 -33
  81. package/enums/transaction.enums.js +0 -69
  82. package/index.js +0 -76
  83. package/providers/base.js +0 -162
  84. package/schemas/escrow/hold.schema.js +0 -62
  85. package/schemas/escrow/index.js +0 -15
  86. package/schemas/index.d.ts +0 -33
  87. package/schemas/index.js +0 -27
  88. package/schemas/split/index.js +0 -16
  89. package/schemas/split/split.schema.js +0 -86
  90. package/schemas/subscription/index.js +0 -17
  91. package/schemas/subscription/info.schema.js +0 -115
  92. package/schemas/subscription/plan.schema.js +0 -48
  93. package/schemas/transaction/common.schema.js +0 -22
  94. package/schemas/transaction/gateway.schema.js +0 -69
  95. package/schemas/transaction/index.js +0 -20
  96. package/schemas/transaction/payment.schema.js +0 -110
  97. package/services/escrow.service.js +0 -353
  98. package/services/monetization.service.js +0 -675
  99. package/services/payment.service.js +0 -535
  100. package/services/transaction.service.js +0 -142
  101. package/utils/category-resolver.js +0 -74
  102. package/utils/commission-split.js +0 -180
  103. package/utils/commission.js +0 -83
  104. package/utils/hooks.js +0 -44
  105. package/utils/index.d.ts +0 -164
  106. package/utils/index.js +0 -16
  107. package/utils/logger.js +0 -36
  108. package/utils/subscription/actions.js +0 -68
  109. package/utils/subscription/index.js +0 -20
  110. package/utils/subscription/period.js +0 -123
  111. package/utils/transaction-type.js +0 -254
package/README.md CHANGED
@@ -1,689 +1,686 @@
1
1
  # @classytic/revenue
2
2
 
3
- > Enterprise revenue management with subscriptions and payment processing
3
+ > Modern, Type-safe Revenue Management for Node.js
4
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
- - **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)
15
- - **Commission Tracking**: Automatic platform commission calculation with gateway fee deduction
16
- - **Provider Pattern**: Pluggable payment providers (like LangChain/Vercel AI SDK)
17
- - **Framework Agnostic**: Works with Express, Fastify, Next.js, or standalone
18
- - **TypeScript Ready**: Full type definitions included
5
+ Enterprise-grade library for subscriptions, payments, escrow, and multi-party splits. Built with TypeScript, Zod validation, and resilience patterns.
19
6
 
20
7
  ## Installation
21
8
 
22
9
  ```bash
23
- npm install @classytic/revenue
24
- npm install @classytic/revenue-manual # For manual payments
10
+ npm install @classytic/revenue @classytic/revenue-manual
25
11
  ```
26
12
 
27
13
  ## Quick Start
28
14
 
29
- ### Single-Tenant (Simple SaaS)
15
+ ### Fluent Builder API (Recommended)
30
16
 
31
- ```javascript
32
- import { createRevenue } from '@classytic/revenue';
17
+ ```typescript
18
+ import { Revenue, Money, loggingPlugin } from '@classytic/revenue';
33
19
  import { ManualProvider } from '@classytic/revenue-manual';
34
20
 
21
+ const revenue = Revenue
22
+ .create({ defaultCurrency: 'USD' })
23
+ .withModels({ Transaction, Subscription })
24
+ .withProvider('manual', new ManualProvider())
25
+ .withProvider('stripe', new StripeProvider({ apiKey: '...' }))
26
+ .withPlugin(loggingPlugin())
27
+ .withRetry({ maxAttempts: 3, baseDelay: 1000 })
28
+ .withCircuitBreaker()
29
+ .withCommission(10, 2.5) // 10% platform, 2.5% gateway fee
30
+ .forEnvironment('production')
31
+ .build();
32
+
33
+ // Access services
34
+ await revenue.monetization.create({ ... });
35
+ await revenue.payments.verify(transactionId);
36
+ await revenue.escrow.hold(transactionId);
37
+ ```
38
+
39
+ ### Shorthand Factory
40
+
41
+ ```typescript
42
+ import { createRevenue } from '@classytic/revenue';
43
+
35
44
  const revenue = createRevenue({
36
- models: { Transaction },
45
+ models: { Transaction, Subscription },
37
46
  providers: { manual: new ManualProvider() },
47
+ options: { defaultCurrency: 'USD' },
38
48
  });
49
+ ```
39
50
 
40
- // Create subscription (no organizationId needed)
41
- const { transaction } = await revenue.monetization.create({
42
- data: { customerId: user._id },
43
- planKey: 'monthly',
44
- monetizationType: 'subscription',
45
- amount: 2999, // $29.99
46
- gateway: 'manual',
47
- paymentData: { method: 'card' },
48
- });
51
+ ---
49
52
 
50
- // Verify → Refund (use transaction._id - works for all providers)
51
- await revenue.payments.verify(transaction._id);
52
- ```
53
+ ## Core Concepts
53
54
 
54
- ### Multi-Tenant (Marketplace/Platform)
55
+ ### Money (Integer-Safe Currency)
55
56
 
56
- ```javascript
57
- // Same API, just pass organizationId
58
- const { transaction } = await revenue.monetization.create({
59
- data: {
60
- organizationId: vendor._id, // ← Multi-tenant
61
- customerId: customer._id,
62
- referenceId: order._id,
63
- referenceModel: 'Order',
64
- },
65
- planKey: 'one_time',
66
- monetizationType: 'purchase', // One-time purchase
67
- amount: 1500,
68
- gateway: 'manual',
69
- paymentData: { method: 'bkash' },
70
- });
71
- ```
57
+ ```typescript
58
+ import { Money } from '@classytic/revenue';
72
59
 
73
- **Works for both!** Same API, different use cases.
60
+ // Create from cents (safe)
61
+ const price = Money.usd(1999); // $19.99
62
+ const price2 = Money.of(19.99, 'USD'); // Auto-converts to 1999 cents
74
63
 
75
- ## Transaction Model Setup
64
+ // Arithmetic
65
+ const discounted = price.multiply(0.9); // 10% off
66
+ const withTax = price.add(Money.usd(200));
67
+ const perPerson = price.divide(3);
76
68
 
77
- The library requires a Transaction model with specific fields and provides reusable schemas:
69
+ // Format
70
+ console.log(price.format()); // "$19.99"
71
+ console.log(price.toUnit()); // 19.99
72
+ console.log(price.amount); // 1999 (integer cents)
78
73
 
79
- ```javascript
80
- import mongoose from 'mongoose';
81
- import {
82
- TRANSACTION_TYPE_VALUES,
83
- TRANSACTION_STATUS_VALUES,
84
- } from '@classytic/revenue/enums';
85
- import {
86
- gatewaySchema,
87
- paymentDetailsSchema,
88
- } from '@classytic/revenue/schemas';
74
+ // Split fairly (handles rounding)
75
+ const [a, b, c] = Money.usd(100).allocate([1, 1, 1]); // [34, 33, 33] cents
76
+ ```
89
77
 
90
- const transactionSchema = new mongoose.Schema({
91
- // ============ REQUIRED BY LIBRARY ============
92
- amount: { type: Number, required: true, min: 0 },
93
- type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // 'income' | 'expense'
94
- method: { type: String, required: true }, // 'manual' | 'bkash' | 'card' | etc.
95
- status: { type: String, enum: TRANSACTION_STATUS_VALUES, required: true },
96
- category: { type: String, required: true }, // Your custom categories
97
-
98
- // ============ MULTI-TENANT (optional) ============
99
- organizationId: { type: String, index: true }, // For multi-tenant platforms
100
-
101
- // ============ LIBRARY SCHEMAS (nested) ============
102
- gateway: gatewaySchema, // Payment gateway details
103
- paymentDetails: paymentDetailsSchema, // Payment info (wallet, bank, etc.)
104
-
105
- // ============ POLYMORPHIC REFERENCE (recommended) ============
106
- // Links transaction to any entity (Order, Subscription, Enrollment, etc.)
107
- referenceId: {
108
- type: mongoose.Schema.Types.ObjectId,
109
- refPath: 'referenceModel',
110
- },
111
- referenceModel: {
112
- type: String,
113
- enum: ['Subscription', 'Order', 'Enrollment', 'Membership'], // Your models
114
- },
78
+ ### Result Type (No Throws)
115
79
 
116
- // ============ YOUR CUSTOM FIELDS ============
117
- customerId: String,
118
- currency: { type: String, default: 'BDT' },
119
- verifiedAt: Date,
120
- verifiedBy: mongoose.Schema.Types.ObjectId,
121
- refundedAmount: Number,
122
- idempotencyKey: { type: String, unique: true, sparse: true },
123
- metadata: mongoose.Schema.Types.Mixed,
124
- }, { timestamps: true });
80
+ ```typescript
81
+ import { Result, ok, err, match } from '@classytic/revenue';
82
+
83
+ // Execute with Result
84
+ const result = await revenue.execute(
85
+ () => riskyOperation(),
86
+ { idempotencyKey: 'order_123' }
87
+ );
88
+
89
+ // Pattern matching
90
+ match(result, {
91
+ ok: (value) => console.log('Success:', value),
92
+ err: (error) => console.log('Error:', error.message),
93
+ });
125
94
 
126
- export default mongoose.model('Transaction', transactionSchema);
95
+ // Or simple check
96
+ if (result.ok) {
97
+ console.log(result.value);
98
+ } else {
99
+ console.log(result.error);
100
+ }
127
101
  ```
128
102
 
129
- ## Available Schemas
103
+ ### Type-Safe Events
130
104
 
131
- | Schema | Purpose | Key Fields |
132
- |--------|---------|------------|
133
- | `gatewaySchema` | Payment gateway integration | `type`, `sessionId`, `paymentIntentId` |
134
- | `paymentDetailsSchema` | Payment method info | `walletNumber`, `trxId`, `bankName` |
135
- | `commissionSchema` | Commission tracking (marketplace) | `rate`, `grossAmount`, `gatewayFeeAmount`, `netAmount` |
136
- | `currentPaymentSchema` | Latest payment (for Order/Subscription models) | `transactionId`, `status`, `verifiedAt` |
137
- | `subscriptionInfoSchema` | Subscription details (for Order models) | `planKey`, `startDate`, `endDate` |
105
+ ```typescript
106
+ // Subscribe to events
107
+ revenue.on('payment.succeeded', (event) => {
108
+ console.log('Transaction:', event.transactionId);
109
+ console.log('Amount:', event.transaction.amount);
110
+ });
138
111
 
139
- **Usage:** Import and use as nested objects (NOT spread):
112
+ revenue.on('subscription.renewed', (event) => {
113
+ sendEmail(event.subscription.customerId, 'Renewed!');
114
+ });
140
115
 
141
- ```javascript
142
- import { gatewaySchema } from '@classytic/revenue/schemas';
116
+ revenue.on('escrow.released', (event) => {
117
+ console.log('Released:', event.releasedAmount);
118
+ });
143
119
 
144
- const schema = new mongoose.Schema({
145
- gateway: gatewaySchema, // Correct - nested
146
- // ...gatewaySchema, // ❌ Wrong - don't spread
120
+ // Wildcard - catch all events
121
+ revenue.on('*', (event) => {
122
+ analytics.track(event.type, event);
147
123
  });
148
124
  ```
149
125
 
150
- ## Core API
126
+ ### Validation (Zod v4)
151
127
 
152
- ### Subscriptions
128
+ ```typescript
129
+ import { CreatePaymentSchema, validate, safeValidate } from '@classytic/revenue';
153
130
 
154
- ```javascript
155
- // Create subscription
156
- const { subscription, transaction, paymentIntent } =
157
- await revenue.monetization.create({
158
- data: { organizationId, customerId },
159
- planKey: 'monthly',
160
- amount: 1500,
161
- currency: 'BDT',
162
- gateway: 'manual',
163
- paymentData: { method: 'bkash', walletNumber: '01712345678' },
164
- });
131
+ // Validate input (throws on error)
132
+ const payment = validate(CreatePaymentSchema, userInput);
165
133
 
166
- // Verify and activate
167
- await revenue.payments.verify(transaction._id);
168
- await revenue.monetization.activate(subscription._id);
134
+ // Safe validation (returns result)
135
+ const result = safeValidate(CreatePaymentSchema, userInput);
136
+ if (!result.success) {
137
+ console.log(result.error.issues);
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Services
144
+
145
+ ### Monetization (Purchases & Subscriptions)
169
146
 
170
- // Renew subscription
171
- await revenue.monetization.renew(subscription._id, {
147
+ ```typescript
148
+ // One-time purchase
149
+ const { transaction, paymentIntent } = await revenue.monetization.create({
150
+ data: {
151
+ customerId: user._id,
152
+ organizationId: org._id,
153
+ referenceId: order._id,
154
+ referenceModel: 'Order',
155
+ },
156
+ planKey: 'one_time',
157
+ monetizationType: 'purchase',
158
+ amount: 1500,
172
159
  gateway: 'manual',
173
- paymentData: { method: 'nagad' },
160
+ paymentData: { method: 'card' },
174
161
  });
175
162
 
176
- // Pause/Resume
177
- await revenue.monetization.pause(subscription._id, { reason: 'Customer request' });
178
- await revenue.monetization.resume(subscription._id, { extendPeriod: true });
163
+ // Recurring subscription
164
+ const { subscription, transaction } = await revenue.monetization.create({
165
+ data: { customerId: user._id },
166
+ planKey: 'monthly',
167
+ monetizationType: 'subscription',
168
+ amount: 2999,
169
+ gateway: 'stripe',
170
+ });
179
171
 
180
- // Cancel
172
+ // Lifecycle management
173
+ await revenue.monetization.activate(subscription._id);
174
+ await revenue.monetization.renew(subscription._id, { gateway: 'stripe' });
175
+ await revenue.monetization.pause(subscription._id, { reason: 'Vacation' });
176
+ await revenue.monetization.resume(subscription._id);
181
177
  await revenue.monetization.cancel(subscription._id, { immediate: true });
182
178
  ```
183
179
 
184
180
  ### Payments
185
181
 
186
- ```javascript
187
- // Verify payment (admin approval for manual)
188
- const { transaction } = await revenue.payments.verify(paymentIntentId, {
189
- verifiedBy: adminUserId,
190
- });
182
+ ```typescript
183
+ // Verify payment
184
+ const { transaction, paymentResult } = await revenue.payments.verify(
185
+ transactionId,
186
+ { verifiedBy: adminId }
187
+ );
191
188
 
192
- // Get payment status
193
- const { status } = await revenue.payments.getStatus(paymentIntentId);
189
+ // Get status
190
+ const { status, provider } = await revenue.payments.getStatus(transactionId);
194
191
 
195
- // Refund (creates separate EXPENSE transaction)
196
- const { transaction, refundTransaction } = await revenue.payments.refund(
192
+ // Full refund
193
+ const { refundTransaction } = await revenue.payments.refund(transactionId);
194
+
195
+ // Partial refund
196
+ const { refundTransaction } = await revenue.payments.refund(
197
197
  transactionId,
198
- 500, // Amount or null for full refund
199
- { reason: 'Customer requested' }
198
+ 500, // Amount in cents
199
+ { reason: 'Partial return' }
200
200
  );
201
201
 
202
- // Handle webhook (for automated providers like Stripe)
202
+ // Handle webhook
203
203
  const { event, transaction } = await revenue.payments.handleWebhook(
204
204
  'stripe',
205
- webhookPayload,
205
+ payload,
206
206
  headers
207
207
  );
208
208
  ```
209
209
 
210
- ### Transactions
210
+ ### Escrow (Hold/Release)
211
211
 
212
- ```javascript
213
- // Get transaction by ID
214
- const transaction = await revenue.transactions.get(transactionId);
212
+ ```typescript
213
+ // Hold funds in escrow
214
+ await revenue.escrow.hold(transactionId, {
215
+ holdUntil: new Date('2024-12-31'),
216
+ reason: 'Awaiting delivery confirmation',
217
+ });
215
218
 
216
- // List with filters
217
- const { transactions, total } = await revenue.transactions.list(
218
- { type: 'income', status: 'verified' },
219
- { limit: 50, sort: { createdAt: -1 } }
220
- );
219
+ // Release to recipient
220
+ await revenue.escrow.release(transactionId, {
221
+ recipientId: vendorId,
222
+ recipientType: 'organization',
223
+ amount: 800, // Partial release
224
+ });
221
225
 
222
- // Calculate net revenue
223
- const income = await revenue.transactions.list({ type: 'income' });
224
- const expense = await revenue.transactions.list({ type: 'expense' });
225
- const netRevenue = income.total - expense.total;
226
- ```
226
+ // Multi-party split
227
+ await revenue.escrow.split(transactionId, [
228
+ { type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
229
+ { type: 'affiliate_commission', recipientId: 'aff_123', rate: 0.05 },
230
+ ]);
227
231
 
228
- ## Transaction Types (Income vs Expense)
232
+ // Cancel hold
233
+ await revenue.escrow.cancelHold(transactionId, { reason: 'Order cancelled' });
234
+ ```
229
235
 
230
- The library uses **double-entry accounting**:
236
+ ---
231
237
 
232
- - **INCOME** (`'income'`): Money coming in - payments, subscriptions
233
- - **EXPENSE** (`'expense'`): Money going out - refunds, payouts
238
+ ## Plugins
234
239
 
235
- ```javascript
236
- const revenue = createRevenue({
237
- models: { Transaction },
238
- config: {
239
- transactionTypeMapping: {
240
- subscription: 'income',
241
- purchase: 'income',
242
- refund: 'expense', // Refunds create separate expense transactions
240
+ ```typescript
241
+ import { loggingPlugin, auditPlugin, metricsPlugin, definePlugin } from '@classytic/revenue';
242
+
243
+ // Built-in plugins
244
+ const revenue = Revenue
245
+ .create()
246
+ .withPlugin(loggingPlugin({ level: 'info' }))
247
+ .withPlugin(auditPlugin({ store: saveToDatabase }))
248
+ .withPlugin(metricsPlugin({ onMetric: sendToDatadog }))
249
+ .build();
250
+
251
+ // Custom plugin
252
+ const rateLimitPlugin = definePlugin({
253
+ name: 'rate-limit',
254
+ hooks: {
255
+ 'payment.create.before': async (ctx, input, next) => {
256
+ if (await isRateLimited(input.customerId)) {
257
+ throw new Error('Rate limited');
258
+ }
259
+ return next();
243
260
  },
244
261
  },
245
262
  });
246
263
  ```
247
264
 
248
- **Refund Pattern:**
249
- - Refund creates NEW transaction with `type: 'expense'`
250
- - Original transaction status becomes `'refunded'` or `'partially_refunded'`
251
- - Both linked via metadata for audit trail
252
- - Calculate net: `SUM(income) - SUM(expense)`
265
+ ---
253
266
 
254
- ## Custom Categories
267
+ ## Resilience
255
268
 
256
- Map logical entities to transaction categories:
269
+ ### Retry with Exponential Backoff
257
270
 
258
- ```javascript
259
- const revenue = createRevenue({
260
- models: { Transaction },
261
- config: {
262
- categoryMappings: {
263
- Order: 'order_subscription',
264
- PlatformSubscription: 'platform_subscription',
265
- Membership: 'gym_membership',
266
- Enrollment: 'course_enrollment',
267
- },
268
- },
269
- });
271
+ ```typescript
272
+ import { retry, retryWithResult, isRetryableError } from '@classytic/revenue';
270
273
 
271
- // Usage
272
- await revenue.monetization.create({
273
- entity: 'Order', // Maps to 'order_subscription' category
274
- monetizationType: 'subscription',
275
- // ...
276
- });
277
- ```
274
+ // Simple retry
275
+ const data = await retry(
276
+ () => fetchPaymentStatus(id),
277
+ {
278
+ maxAttempts: 5,
279
+ baseDelay: 1000,
280
+ maxDelay: 30000,
281
+ backoffMultiplier: 2,
282
+ jitter: 0.1,
283
+ }
284
+ );
278
285
 
279
- **Note:** `entity` is a logical identifier (not a database model name) for organizing your business logic.
286
+ // Retry with Result (no throws)
287
+ const result = await retryWithResult(() => processPayment());
288
+ if (!result.ok) {
289
+ console.log('All retries failed:', result.error.errors);
290
+ }
291
+ ```
280
292
 
281
- ## Commission Tracking (Marketplace)
293
+ ### Circuit Breaker
282
294
 
283
- Automatically calculate platform commission with gateway fee deduction:
295
+ ```typescript
296
+ import { CircuitBreaker, createCircuitBreaker } from '@classytic/revenue';
284
297
 
285
- ```javascript
286
- const revenue = createRevenue({
287
- models: { Transaction },
288
- config: {
289
- // Commission rates by category
290
- commissionRates: {
291
- 'product_order': 0.10, // 10% platform commission
292
- 'course_enrollment': 0.10, // 10% on courses
293
- 'gym_membership': 0, // No commission
294
- },
295
-
296
- // Gateway fees (deducted from commission)
297
- gatewayFeeRates: {
298
- 'stripe': 0.029, // 2.9% Stripe fee
299
- 'bkash': 0.018, // 1.8% bKash fee
300
- 'manual': 0, // No fee
301
- },
302
- },
298
+ const breaker = createCircuitBreaker({
299
+ failureThreshold: 5,
300
+ resetTimeout: 30000,
303
301
  });
304
302
 
305
- // Commission calculated automatically
306
- const { transaction } = await revenue.monetization.create({
307
- amount: 10000, // $100
308
- entity: 'ProductOrder', // → 10% commission
309
- gateway: 'stripe', // → 2.9% fee
310
- });
303
+ const result = await breaker.execute(() => callExternalAPI());
311
304
 
312
- console.log(transaction.commission);
313
- // {
314
- // rate: 0.10,
315
- // grossAmount: 1000, // $10 (10% of $100)
316
- // gatewayFeeAmount: 290, // $2.90 (2.9% of $100)
317
- // netAmount: 710, // $7.10 (platform keeps)
318
- // status: 'pending'
319
- // }
320
-
321
- // Query pending commissions
322
- const pending = await Transaction.find({ 'commission.status': 'pending' });
305
+ // Check state
306
+ console.log(breaker.getState()); // 'closed' | 'open' | 'half-open'
323
307
  ```
324
308
 
325
- **Refund handling:** Commission automatically reversed proportionally when refunds are processed.
326
-
327
- **See:** [`examples/commission-tracking.js`](examples/commission-tracking.js) for complete guide.
309
+ ### Idempotency
328
310
 
329
- ## Subscription Utilities
330
-
331
- Universal helpers for period calculation, proration, and action eligibility:
311
+ ```typescript
312
+ import { IdempotencyManager } from '@classytic/revenue';
332
313
 
333
- ```javascript
334
- import {
335
- // Period calculation
336
- calculatePeriodRange,
337
- calculateProratedAmount,
338
- addDuration,
339
-
340
- // Action eligibility
341
- canRenewSubscription,
342
- canPauseSubscription,
343
- isSubscriptionActive,
344
- } from '@classytic/revenue/utils';
345
-
346
- // Calculate period
347
- const { startDate, endDate } = calculatePeriodRange({
348
- duration: 30,
349
- unit: 'days',
350
- });
314
+ const idempotency = new IdempotencyManager({ ttl: 86400000 }); // 24h
351
315
 
352
- // Calculate prorated refund
353
- const refund = calculateProratedAmount({
354
- amountPaid: 1500,
355
- startDate: sub.startDate,
356
- endDate: sub.endDate,
357
- asOfDate: new Date(),
358
- });
316
+ const result = await idempotency.execute(
317
+ 'payment_order_123',
318
+ { amount: 1999, customerId: 'cust_1' },
319
+ () => chargeCard()
320
+ );
359
321
 
360
- // Check eligibility
361
- if (canRenewSubscription(membership)) {
362
- await revenue.monetization.renew(membership.subscriptionId);
363
- }
322
+ // Same key + same params = cached result
323
+ // Same key + different params = error
364
324
  ```
365
325
 
366
- ## Escrow & Multi-Party Splits (v0.1.0+)
326
+ ---
367
327
 
368
- **NEW:** Platform-as-intermediary payment flow for marketplaces, group buy, and affiliate systems.
328
+ ## Transaction Model Setup
369
329
 
370
- ### Basic Escrow Flow
330
+ **ONE Transaction model = Universal Financial Ledger**
371
331
 
372
- ```javascript
373
- // 1. Customer makes purchase
374
- const { transaction } = await revenue.monetization.create({
375
- amount: 1000,
376
- gateway: 'stripe',
377
- // ...
378
- });
332
+ The Transaction model is the ONLY required model. Use it for subscriptions, purchases, refunds, and operational expenses. The Subscription model is **optional** (only for tracking subscription state).
379
333
 
380
- // 2. Verify payment
381
- await revenue.payments.verify(transaction._id);
334
+ ```typescript
335
+ import mongoose from 'mongoose';
336
+ import {
337
+ // Enums
338
+ TRANSACTION_TYPE_VALUES,
339
+ TRANSACTION_STATUS_VALUES,
340
+ // Mongoose schemas (compose into your model)
341
+ gatewaySchema,
342
+ paymentDetailsSchema,
343
+ commissionSchema,
344
+ holdSchema,
345
+ splitSchema,
346
+ } from '@classytic/revenue';
382
347
 
383
- // 3. Hold in escrow
384
- await revenue.escrow.hold(transaction._id);
348
+ // Your app-specific categories
349
+ const CATEGORIES = [
350
+ 'platform_subscription',
351
+ 'course_enrollment',
352
+ 'product_order',
353
+ 'refund',
354
+ 'rent',
355
+ 'salary',
356
+ 'utilities',
357
+ ];
385
358
 
386
- // 4. Split to multiple recipients
387
- await revenue.escrow.split(transaction._id, [
388
- { type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
389
- { type: 'affiliate_commission', recipientId: 'affiliate-123', rate: 0.05 },
390
- ]);
391
- // Auto-releases remainder to organization (85%)
359
+ const transactionSchema = new mongoose.Schema({
360
+ // Core fields
361
+ organizationId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
362
+ customerId: { type: mongoose.Schema.Types.ObjectId, index: true },
363
+ type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // income | expense
364
+ category: { type: String, enum: CATEGORIES, index: true },
365
+ status: { type: String, enum: TRANSACTION_STATUS_VALUES, default: 'pending' },
366
+ amount: { type: Number, required: true, min: 0 },
367
+ currency: { type: String, default: 'USD' },
368
+ method: { type: String, required: true },
392
369
 
393
- // Or manually release
394
- await revenue.escrow.release(transaction._id, {
395
- recipientId: 'org-123',
396
- recipientType: 'organization',
397
- });
398
- ```
370
+ // Library schemas (compose, don't spread)
371
+ gateway: gatewaySchema,
372
+ commission: commissionSchema,
373
+ paymentDetails: paymentDetailsSchema,
374
+ hold: holdSchema,
375
+ splits: [splitSchema],
399
376
 
400
- ### Affiliate Commission (Simplified API)
377
+ // Polymorphic reference (link to any entity)
378
+ referenceId: { type: mongoose.Schema.Types.ObjectId, refPath: 'referenceModel' },
379
+ referenceModel: { type: String, enum: ['Subscription', 'Order', 'Enrollment'] },
401
380
 
402
- ```javascript
403
- import { calculateCommissionWithSplits } from '@classytic/revenue';
381
+ // Idempotency & verification
382
+ idempotencyKey: { type: String, unique: true, sparse: true },
383
+ verifiedAt: Date,
384
+ verifiedBy: mongoose.Schema.Types.Mixed, // ObjectId or 'system'
385
+
386
+ // Refunds
387
+ refundedAmount: Number,
388
+ refundedAt: Date,
404
389
 
405
- const commission = calculateCommissionWithSplits(
406
- 5000, // amount
407
- 0.10, // platform rate
408
- 0.029, // gateway fee
409
- {
410
- affiliateRate: 0.05,
411
- affiliateId: 'affiliate-123',
412
- }
413
- );
390
+ metadata: mongoose.Schema.Types.Mixed,
391
+ }, { timestamps: true });
414
392
 
415
- // Returns:
416
- // {
417
- // grossAmount: 500, // Platform: 10%
418
- // netAmount: 355, // After gateway fee (2.9%)
419
- // affiliate: {
420
- // grossAmount: 250, // Affiliate: 5%
421
- // netAmount: 250,
422
- // },
423
- // splits: [...]
424
- // }
393
+ export const Transaction = mongoose.model('Transaction', transactionSchema);
425
394
  ```
426
395
 
427
- ### Multi-Party Splits
396
+ ### Available Schemas
428
397
 
429
- ```javascript
430
- import { calculateSplits } from '@classytic/revenue';
398
+ | Schema | Purpose | Usage |
399
+ |--------|---------|-------|
400
+ | `gatewaySchema` | Payment gateway details | `gateway: gatewaySchema` |
401
+ | `commissionSchema` | Platform commission | `commission: commissionSchema` |
402
+ | `paymentDetailsSchema` | Manual payment info | `paymentDetails: paymentDetailsSchema` |
403
+ | `holdSchema` | Escrow hold/release | `hold: holdSchema` |
404
+ | `splitSchema` | Multi-party splits | `splits: [splitSchema]` |
405
+ | `currentPaymentSchema` | For Order/Subscription models (includes refund tracking) | `currentPayment: currentPaymentSchema` |
431
406
 
432
- const splits = calculateSplits(10000, [
433
- { type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
434
- { type: 'affiliate_commission', recipientId: 'level1', rate: 0.05 },
435
- { type: 'affiliate_commission', recipientId: 'level2', rate: 0.02 },
436
- { type: 'partner_commission', recipientId: 'partner', rate: 0.03 },
437
- ], 0.029); // Gateway fee
438
-
439
- // Returns splits array with calculated amounts
440
- // Organization receives: 8000 (80%)
441
- ```
442
-
443
- ### Schemas for Escrow
444
-
445
- Add to your transaction model when using escrow:
407
+ **Usage:** Import and use as nested objects (NOT spread):
446
408
 
447
- ```javascript
448
- import { holdSchema, splitsSchema } from '@classytic/revenue';
409
+ ```typescript
410
+ import { gatewaySchema, commissionSchema } from '@classytic/revenue';
449
411
 
450
- TransactionSchema.add(holdSchema); // Adds hold/release tracking
451
- TransactionSchema.add(splitsSchema); // Adds multi-party splits
412
+ const schema = new mongoose.Schema({
413
+ gateway: gatewaySchema, // Correct - nested
414
+ commission: commissionSchema,
415
+ // ...gatewaySchema, // ❌ Wrong - don't spread
416
+ });
452
417
  ```
453
418
 
454
- **Use Cases:**
455
- - E-commerce marketplaces (hold until delivery confirmed)
456
- - Course platforms with affiliates
457
- - Group buy / crowdfunding
458
- - Multi-level marketing
459
- - SaaS reseller programs
419
+ ---
460
420
 
461
- **See:** [`ESCROW_FEATURES.md`](../../ESCROW_FEATURES.md) and [`examples/escrow-flow.js`](examples/escrow-flow.js)
421
+ ## Group Payments (Split Pay)
462
422
 
463
- ## Polymorphic References
423
+ Multiple payers can contribute to one purchase using `referenceId`:
464
424
 
465
- Link transactions to any entity (Order, Subscription, Enrollment):
425
+ ```typescript
426
+ // Order total: $100 (10000 cents)
427
+ const orderId = new mongoose.Types.ObjectId();
428
+ const orderTotal = 10000;
466
429
 
467
- ```javascript
468
- // Create transaction linked to Order
469
- const { transaction } = await revenue.monetization.create({
430
+ // Friend 1 pays $40
431
+ await revenue.monetization.create({
470
432
  data: {
471
- organizationId,
472
- customerId,
473
- referenceId: order._id, // ⭐ Direct field (not metadata)
474
- referenceModel: 'Order', // ⭐ Model name
433
+ customerId: friend1,
434
+ organizationId: restaurantId,
435
+ referenceId: orderId,
436
+ referenceModel: 'Order',
475
437
  },
476
- amount: 1500,
477
- // ...
438
+ planKey: 'split_payment',
439
+ monetizationType: 'purchase',
440
+ amount: 4000,
441
+ gateway: 'stripe',
442
+ metadata: { splitGroup: 'dinner_dec_10' },
478
443
  });
479
444
 
480
- // Query all transactions for an order
481
- const orderTransactions = await Transaction.find({
482
- referenceModel: 'Order',
483
- referenceId: order._id,
445
+ // Friend 2 pays $35
446
+ await revenue.monetization.create({
447
+ data: {
448
+ customerId: friend2,
449
+ organizationId: restaurantId,
450
+ referenceId: orderId,
451
+ referenceModel: 'Order',
452
+ },
453
+ planKey: 'split_payment',
454
+ monetizationType: 'purchase',
455
+ amount: 3500,
456
+ gateway: 'stripe',
457
+ metadata: { splitGroup: 'dinner_dec_10' },
484
458
  });
485
459
 
486
- // Use Mongoose populate
487
- const transactions = await Transaction.find({ ... })
488
- .populate('referenceId'); // Populates based on referenceModel
489
- ```
490
-
491
- **Why top-level?** Enables proper Mongoose queries and population. Storing in metadata prevents querying and indexing.
492
-
493
- ## Hooks
494
-
495
- ```javascript
496
- const revenue = createRevenue({
497
- models: { Transaction },
498
- hooks: {
499
- // Monetization lifecycle (specific)
500
- 'purchase.created': async ({ transaction, isFree }) => {
501
- console.log('One-time purchase:', transaction._id);
502
- },
503
- 'subscription.created': async ({ transaction, isFree }) => {
504
- console.log('Recurring subscription:', transaction._id);
505
- },
506
- 'free.created': async ({ transaction }) => {
507
- console.log('Free access granted:', transaction._id);
508
- },
509
-
510
- // Generic event (fires for all types)
511
- 'monetization.created': async ({ transaction, monetizationType }) => {
512
- console.log(`${monetizationType} created:`, transaction._id);
513
- },
514
-
515
- // Payment lifecycle
516
- 'payment.verified': async ({ transaction }) => {
517
- // Send confirmation email
518
- },
519
- 'payment.failed': async ({ transaction, error, provider }) => {
520
- // Alert admin or send customer notification
521
- console.error('Payment failed:', error);
522
- },
523
- 'payment.refunded': async ({ refundTransaction }) => {
524
- // Process refund notification
525
- },
526
-
527
- // Subscription management (requires Subscription model)
528
- 'subscription.activated': async ({ subscription }) => {
529
- // Subscription activated after payment
530
- },
531
- 'subscription.renewed': async ({ subscription, renewalCount }) => {
532
- // Subscription renewed
533
- },
534
- 'subscription.paused': async ({ subscription }) => {
535
- // Subscription paused
536
- },
537
- 'subscription.resumed': async ({ subscription }) => {
538
- // Subscription resumed
539
- },
540
- 'subscription.cancelled': async ({ subscription }) => {
541
- // Subscription cancelled
542
- },
460
+ // Friend 3 pays $25
461
+ await revenue.monetization.create({
462
+ data: {
463
+ customerId: friend3,
464
+ organizationId: restaurantId,
465
+ referenceId: orderId,
466
+ referenceModel: 'Order',
543
467
  },
468
+ planKey: 'split_payment',
469
+ monetizationType: 'purchase',
470
+ amount: 2500,
471
+ gateway: 'stripe',
472
+ metadata: { splitGroup: 'dinner_dec_10' },
544
473
  });
545
474
  ```
546
475
 
547
- **Available hooks:**
476
+ ### Check Payment Status
548
477
 
549
- **Monetization Events (specific):**
550
- - `purchase.created` - One-time purchase
551
- - `subscription.created` - Recurring subscription
552
- - `free.created` - Free access granted
553
- - `monetization.created` - Generic event (fires for all types)
554
-
555
- **Payment Events:**
556
- - `payment.verified` - Payment confirmed
557
- - `payment.failed` - Payment verification failed
558
- - `payment.refunded` - Refund processed
559
- - `payment.webhook.{type}` - Webhook events from providers
560
-
561
- **Subscription Management Events (requires Subscription model):**
562
- - `subscription.activated`, `subscription.renewed`
563
- - `subscription.paused`, `subscription.resumed`, `subscription.cancelled`
478
+ ```typescript
479
+ // Get all contributions for an order
480
+ const contributions = await Transaction.find({
481
+ referenceId: orderId,
482
+ referenceModel: 'Order',
483
+ });
564
484
 
565
- ## Provider Patterns
485
+ // Calculate totals
486
+ const verified = contributions.filter(t => t.status === 'verified');
487
+ const totalPaid = verified.reduce((sum, t) => sum + t.amount, 0);
488
+ const remaining = orderTotal - totalPaid;
489
+ const isFullyPaid = totalPaid >= orderTotal;
490
+
491
+ console.log({
492
+ totalPaid, // 10000
493
+ remaining, // 0
494
+ isFullyPaid, // true
495
+ payers: verified.map(t => ({
496
+ customerId: t.customerId,
497
+ amount: t.amount,
498
+ paidAt: t.verifiedAt,
499
+ })),
500
+ });
501
+ ```
566
502
 
567
- Ready-to-use patterns for popular payment gateways (copy to your project):
503
+ ### Query by Split Group
568
504
 
569
- ### Available Patterns
505
+ ```typescript
506
+ // Find all payments in a split group
507
+ const groupPayments = await Transaction.find({
508
+ 'metadata.splitGroup': 'dinner_dec_10',
509
+ });
570
510
 
571
- | Pattern | Use Case | Location |
572
- |---------|----------|----------|
573
- | **stripe-checkout** | Single-tenant Stripe | [`provider-patterns/stripe-checkout/`](../provider-patterns/stripe-checkout/) |
574
- | **stripe-connect-standard** | Multi-tenant marketplace | [`provider-patterns/stripe-connect-standard/`](../provider-patterns/stripe-connect-standard/) |
575
- | **stripe-platform-manual** | Platform collects, manual payout | [`provider-patterns/stripe-platform-manual/`](../provider-patterns/stripe-platform-manual/) |
576
- | **sslcommerz** | Bangladesh payment gateway | [`provider-patterns/sslcommerz/`](../provider-patterns/sslcommerz/) |
511
+ // Pending payers
512
+ const pending = await Transaction.find({
513
+ referenceId: orderId,
514
+ status: 'pending',
515
+ });
516
+ ```
577
517
 
578
- **See:** [`provider-patterns/INDEX.md`](../provider-patterns/INDEX.md) for complete guide.
518
+ ---
579
519
 
580
520
  ## Building Custom Providers
581
521
 
582
- Create providers for any payment gateway:
583
-
584
- ```javascript
585
- import { PaymentProvider, PaymentIntent, PaymentResult } from '@classytic/revenue';
522
+ ```typescript
523
+ import { PaymentProvider, PaymentIntent, PaymentResult, RefundResult, WebhookEvent } from '@classytic/revenue';
524
+ import type { CreateIntentParams, ProviderCapabilities } from '@classytic/revenue';
586
525
 
587
526
  export class StripeProvider extends PaymentProvider {
588
- constructor(config) {
527
+ public override readonly name = 'stripe';
528
+ private stripe: Stripe;
529
+
530
+ constructor(config: { apiKey: string }) {
589
531
  super(config);
590
- this.name = 'stripe';
591
532
  this.stripe = new Stripe(config.apiKey);
592
533
  }
593
534
 
594
- async createIntent(params) {
535
+ async createIntent(params: CreateIntentParams): Promise<PaymentIntent> {
595
536
  const intent = await this.stripe.paymentIntents.create({
596
537
  amount: params.amount,
597
- currency: params.currency,
538
+ currency: params.currency ?? 'usd',
539
+ metadata: params.metadata,
598
540
  });
599
541
 
600
542
  return new PaymentIntent({
601
543
  id: intent.id,
602
- sessionId: null, // No session for direct intents
603
- paymentIntentId: intent.id, // Available immediately
604
- provider: 'stripe',
544
+ paymentIntentId: intent.id,
545
+ sessionId: null,
546
+ provider: this.name,
605
547
  status: intent.status,
606
548
  amount: intent.amount,
607
549
  currency: intent.currency,
608
- clientSecret: intent.client_secret,
609
- raw: intent,
550
+ clientSecret: intent.client_secret!,
551
+ metadata: params.metadata ?? {},
610
552
  });
611
553
  }
612
554
 
613
- async verifyPayment(intentId) {
555
+ async verifyPayment(intentId: string): Promise<PaymentResult> {
614
556
  const intent = await this.stripe.paymentIntents.retrieve(intentId);
615
557
  return new PaymentResult({
616
558
  id: intent.id,
617
- provider: 'stripe',
559
+ provider: this.name,
618
560
  status: intent.status === 'succeeded' ? 'succeeded' : 'failed',
619
- paidAt: new Date(),
620
- raw: intent,
561
+ amount: intent.amount,
562
+ currency: intent.currency,
563
+ paidAt: intent.status === 'succeeded' ? new Date() : undefined,
564
+ metadata: {},
621
565
  });
622
566
  }
623
567
 
624
- // Implement: getStatus(), refund(), handleWebhook()
625
- }
626
- ```
627
-
628
- **See:** [`docs/guides/PROVIDER_GUIDE.md`](../docs/guides/PROVIDER_GUIDE.md) for complete guide.
629
-
630
- ## TypeScript
568
+ async getStatus(intentId: string): Promise<PaymentResult> {
569
+ return this.verifyPayment(intentId);
570
+ }
631
571
 
632
- Full TypeScript support included:
572
+ async refund(paymentId: string, amount?: number | null): Promise<RefundResult> {
573
+ const refund = await this.stripe.refunds.create({
574
+ payment_intent: paymentId,
575
+ amount: amount ?? undefined,
576
+ });
633
577
 
634
- ```typescript
635
- import { createRevenue, Revenue, PaymentService } from '@classytic/revenue';
636
- import { TRANSACTION_TYPE, TRANSACTION_STATUS } from '@classytic/revenue/enums';
578
+ return new RefundResult({
579
+ id: refund.id,
580
+ provider: this.name,
581
+ status: refund.status === 'succeeded' ? 'succeeded' : 'failed',
582
+ amount: refund.amount,
583
+ currency: refund.currency,
584
+ refundedAt: new Date(),
585
+ metadata: {},
586
+ });
587
+ }
637
588
 
638
- const revenue: Revenue = createRevenue({
639
- models: { Transaction },
640
- });
589
+ async handleWebhook(payload: unknown, headers?: Record<string, string>): Promise<WebhookEvent> {
590
+ const sig = headers?.['stripe-signature'];
591
+ const event = this.stripe.webhooks.constructEvent(
592
+ payload as string,
593
+ sig!,
594
+ this.config.webhookSecret as string
595
+ );
596
+
597
+ return new WebhookEvent({
598
+ id: event.id,
599
+ provider: this.name,
600
+ type: event.type,
601
+ data: event.data.object as any,
602
+ createdAt: new Date(event.created * 1000),
603
+ });
604
+ }
641
605
 
642
- // All services are fully typed
643
- const payment = await revenue.payments.verify(id);
644
- const subscription = await revenue.monetization.create({ ... });
606
+ override getCapabilities(): ProviderCapabilities {
607
+ return {
608
+ supportsWebhooks: true,
609
+ supportsRefunds: true,
610
+ supportsPartialRefunds: true,
611
+ requiresManualVerification: false,
612
+ };
613
+ }
614
+ }
645
615
  ```
646
616
 
647
- ## Examples
648
-
649
- - [`examples/single-tenant.js`](examples/single-tenant.js) - Simple SaaS (no organizations)
650
- - [`examples/transaction.model.js`](examples/transaction.model.js) - Complete model setup
651
- - [`examples/complete-flow.js`](examples/complete-flow.js) - Full lifecycle (types, refs, state)
652
- - [`examples/commission-tracking.js`](examples/commission-tracking.js) - Commission calculation
653
- - [`examples/hooks-v0.2.0.js`](examples/hooks-v0.2.0.js) - v0.2.0 semantic hooks (NEW)
617
+ ---
654
618
 
655
619
  ## Error Handling
656
620
 
657
- ```javascript
658
- import {
621
+ ```typescript
622
+ import {
623
+ RevenueError,
659
624
  TransactionNotFoundError,
660
- ProviderNotFoundError,
661
625
  AlreadyVerifiedError,
662
626
  RefundError,
627
+ ProviderNotFoundError,
628
+ ValidationError,
629
+ isRevenueError,
630
+ isRetryable,
663
631
  } from '@classytic/revenue';
664
632
 
665
633
  try {
666
634
  await revenue.payments.verify(id);
667
635
  } catch (error) {
668
636
  if (error instanceof AlreadyVerifiedError) {
669
- console.log('Already verified');
637
+ console.log('Already verified:', error.metadata.transactionId);
670
638
  } else if (error instanceof TransactionNotFoundError) {
671
- console.log('Transaction not found');
639
+ console.log('Not found');
640
+ } else if (isRevenueError(error) && isRetryable(error)) {
641
+ // Retry the operation
672
642
  }
673
643
  }
674
644
  ```
675
645
 
676
- ## Documentation
646
+ ---
647
+
648
+ ## TypeScript
649
+
650
+ Full TypeScript support with exported types:
677
651
 
678
- - **[Provider Guide](../docs/guides/PROVIDER_GUIDE.md)** - Build custom payment providers
679
- - **[Architecture](../docs/README.md#architecture)** - System design and patterns
680
- - **[API Reference](../docs/README.md)** - Complete API documentation
652
+ ```typescript
653
+ import type {
654
+ Revenue,
655
+ TransactionDocument,
656
+ SubscriptionDocument,
657
+ PaymentProviderInterface,
658
+ CreateIntentParams,
659
+ ProviderCapabilities,
660
+ RevenueEvents,
661
+ MonetizationCreateParams,
662
+ } from '@classytic/revenue';
663
+ ```
681
664
 
682
- ## Support
665
+ ---
666
+
667
+ ## Testing
668
+
669
+ ```bash
670
+ # Run all tests (75 tests)
671
+ npm test
672
+
673
+ # Run integration tests (requires MongoDB)
674
+ npm test -- tests/integration/
675
+
676
+ # Watch mode
677
+ npm run test:watch
678
+
679
+ # Coverage
680
+ npm run test:coverage
681
+ ```
683
682
 
684
- - **GitHub**: [classytic/revenue](https://github.com/classytic/revenue)
685
- - **Issues**: [Report bugs](https://github.com/classytic/revenue/issues)
686
- - **NPM**: [@classytic/revenue](https://www.npmjs.com/package/@classytic/revenue)
683
+ ---
687
684
 
688
685
  ## License
689
686