@classytic/revenue 1.0.2 → 1.1.2

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 (53) hide show
  1. package/README.md +603 -486
  2. package/dist/application/services/index.d.ts +6 -0
  3. package/dist/application/services/index.js +3288 -0
  4. package/dist/application/services/index.js.map +1 -0
  5. package/dist/core/events.d.ts +455 -0
  6. package/dist/core/events.js +122 -0
  7. package/dist/core/events.js.map +1 -0
  8. package/dist/core/index.d.ts +12 -889
  9. package/dist/core/index.js +2361 -705
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/enums/index.d.ts +54 -25
  12. package/dist/enums/index.js +143 -14
  13. package/dist/enums/index.js.map +1 -1
  14. package/dist/escrow.enums-CE0VQsfe.d.ts +76 -0
  15. package/dist/{index-BnJWVXuw.d.ts → index-DxIK0UmZ.d.ts} +281 -26
  16. package/dist/index-EnfKzDbs.d.ts +806 -0
  17. package/dist/{index-ChVD3P9k.d.ts → index-cLJBLUvx.d.ts} +55 -81
  18. package/dist/index.d.ts +16 -15
  19. package/dist/index.js +2583 -2066
  20. package/dist/index.js.map +1 -1
  21. package/dist/infrastructure/plugins/index.d.ts +267 -0
  22. package/dist/infrastructure/plugins/index.js +292 -0
  23. package/dist/infrastructure/plugins/index.js.map +1 -0
  24. package/dist/money-widWVD7r.d.ts +111 -0
  25. package/dist/payment.enums-C1BiGlRa.d.ts +69 -0
  26. package/dist/plugin-Bb9HOE10.d.ts +336 -0
  27. package/dist/providers/index.d.ts +19 -6
  28. package/dist/providers/index.js +22 -3
  29. package/dist/providers/index.js.map +1 -1
  30. package/dist/reconciliation/index.d.ts +215 -0
  31. package/dist/reconciliation/index.js +140 -0
  32. package/dist/reconciliation/index.js.map +1 -0
  33. package/dist/{retry-80lBCmSe.d.ts → retry-D4hFUwVk.d.ts} +1 -41
  34. package/dist/schemas/index.d.ts +1927 -166
  35. package/dist/schemas/index.js +357 -40
  36. package/dist/schemas/index.js.map +1 -1
  37. package/dist/schemas/validation.d.ts +87 -12
  38. package/dist/schemas/validation.js +71 -17
  39. package/dist/schemas/validation.js.map +1 -1
  40. package/dist/settlement.enums-ByC1x0ye.d.ts +130 -0
  41. package/dist/settlement.schema-CpamV7ZY.d.ts +343 -0
  42. package/dist/split.enums-DG3TxQf9.d.ts +42 -0
  43. package/dist/tax-CV8A0sxl.d.ts +60 -0
  44. package/dist/utils/index.d.ts +487 -13
  45. package/dist/utils/index.js +370 -235
  46. package/dist/utils/index.js.map +1 -1
  47. package/package.json +27 -13
  48. package/dist/actions-CwG-b7fR.d.ts +0 -519
  49. package/dist/services/index.d.ts +0 -3
  50. package/dist/services/index.js +0 -1632
  51. package/dist/services/index.js.map +0 -1
  52. package/dist/split.enums-Bh24jw8p.d.ts +0 -255
  53. package/dist/split.schema-DYVP7Wu2.d.ts +0 -958
package/README.md CHANGED
@@ -1,50 +1,197 @@
1
1
  # @classytic/revenue
2
2
 
3
- > Modern, Type-safe Revenue Management for Node.js
3
+ > **Universal financial ledger for SaaS & marketplaces**
4
4
 
5
- Enterprise-grade library for subscriptions, payments, escrow, and multi-party splits. Built with TypeScript, Zod validation, and resilience patterns.
5
+ Track subscriptions, purchases, refunds, escrow, and commission splits in **ONE Transaction model**. Built for enterprise with state machines, automatic retry logic, and multi-gateway support.
6
+
7
+ [![npm version](https://badge.fury.io/js/@classytic%2Frevenue.svg)](https://www.npmjs.com/package/@classytic/revenue)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue)](https://www.typescriptlang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ ---
12
+
13
+ ## What Is This?
14
+
15
+ A TypeScript library that handles **all financial transactions** in one unified model:
16
+
17
+ ```typescript
18
+ // Subscription payment
19
+ { type: 'subscription', flow: 'inflow', amount: 2999 }
20
+
21
+ // Product purchase
22
+ { type: 'product_order', flow: 'inflow', amount: 1500 }
23
+
24
+ // Refund
25
+ { type: 'refund', flow: 'outflow', amount: 1500 }
26
+
27
+ // Operational expense
28
+ { type: 'rent', flow: 'outflow', amount: 50000 }
29
+ ```
30
+
31
+ **One table. Query by type. Calculate P&L. Track cash flow.**
32
+
33
+ ---
34
+ ## Unified Cashflow Model (Shared Types)
35
+
36
+ `@classytic/revenue` re-exports the unified transaction types from `@classytic/shared-types`. If you want a single Transaction model across revenue + payroll, define your schema using the shared types. The shared types are an interface only — you own the schema, enums, and indexes. There is no required “common schema”.
37
+
38
+ Type safety is provided by `ITransaction` only. Transaction categories (`type`) are app-defined; `flow` (`inflow`/`outflow`) is the only shared enum.
39
+
40
+ ```typescript
41
+ import type { ITransaction } from '@classytic/shared-types';
42
+ // or: import type { ITransaction } from '@classytic/revenue';
43
+ ```
44
+
45
+
46
+ ## Why Use This?
47
+
48
+ **Instead of:**
49
+ - Separate tables for subscriptions, orders, refunds, invoices
50
+ - Scattered payment logic across your codebase
51
+ - Manual state management and validation
52
+ - Building payment provider integrations from scratch
53
+
54
+ **You get:**
55
+ - ✅ **ONE Transaction model** = Simpler schema, easier queries
56
+ - ✅ **State machines** = Prevents invalid transitions (can't refund a pending payment)
57
+ - ✅ **Provider abstraction** = Swap Stripe/PayPal/SSLCommerz without code changes
58
+ - ✅ **Production-ready** = Retry, circuit breaker, idempotency built-in
59
+ - ✅ **Plugins** = Optional tax, logging, audit trails
60
+ - ✅ **Type-safe** = Full TypeScript + Zod v4 validation
61
+ - ✅ **Integer money** = No floating-point errors
62
+
63
+ ---
64
+
65
+ ## When to Use This
66
+
67
+ | Use Case | Example |
68
+ |----------|---------|
69
+ | **SaaS billing** | Monthly/annual subscriptions with auto-renewal |
70
+ | **Marketplace payouts** | Creator platforms, affiliate commissions |
71
+ | **E-commerce** | Product purchases with refunds |
72
+ | **Escrow** | Hold funds until delivery/conditions met |
73
+ | **Multi-party splits** | Revenue sharing (70% creator, 20% affiliate, 10% platform) |
74
+ | **Financial reporting** | P&L statements, cash flow tracking |
75
+
76
+ ---
6
77
 
7
78
  ## Installation
8
79
 
9
80
  ```bash
10
- npm install @classytic/revenue @classytic/revenue-manual
81
+ npm install @classytic/revenue mongoose zod
82
+ ```
83
+
84
+ **Peer Dependencies:**
85
+ - `mongoose` ^8.0.0 || ^9.0.0
86
+ - `zod` ^4.1.13
87
+
88
+ **Provider Packages** (install as needed):
89
+ ```bash
90
+ npm install @classytic/revenue-manual # For cash/bank transfers
91
+ # Coming soon: @classytic/revenue-stripe, @classytic/revenue-sslcommerz
11
92
  ```
12
93
 
94
+ ---
95
+
13
96
  ## Quick Start
14
97
 
15
- ### Fluent Builder API (Recommended)
98
+ ### 1. Define Your Transaction Model
99
+
100
+ Copy the complete model from [examples/05-transaction-model.ts](./examples/05-transaction-model.ts):
101
+
102
+ ```typescript
103
+ import mongoose, { Schema } from 'mongoose';
104
+ import type { ITransaction } from '@classytic/shared-types';
105
+ import {
106
+ TRANSACTION_FLOW_VALUES,
107
+ TRANSACTION_STATUS_VALUES,
108
+ gatewaySchema,
109
+ commissionSchema,
110
+ } from '@classytic/revenue';
111
+
112
+ // Your business categories
113
+ const CATEGORIES = {
114
+ PLATFORM_SUBSCRIPTION: 'platform_subscription',
115
+ COURSE_ENROLLMENT: 'course_enrollment',
116
+ PRODUCT_ORDER: 'product_order',
117
+ REFUND: 'refund',
118
+ RENT: 'rent',
119
+ SALARY: 'salary',
120
+ };
121
+
122
+ const transactionSchema = new Schema<ITransaction>({
123
+ organizationId: { type: Schema.Types.ObjectId, ref: 'Organization', required: true },
124
+ customerId: { type: Schema.Types.ObjectId, ref: 'Customer' },
125
+ sourceId: { type: Schema.Types.ObjectId },
126
+ sourceModel: { type: String }, // your app’s model name
127
+ type: { type: String, enum: Object.values(CATEGORIES), required: true }, // category
128
+ flow: { type: String, enum: TRANSACTION_FLOW_VALUES, required: true },
129
+ status: { type: String, enum: TRANSACTION_STATUS_VALUES, default: 'pending' },
130
+ amount: { type: Number, required: true },
131
+ currency: { type: String, default: 'USD' },
132
+ method: { type: String, required: true },
133
+ gateway: gatewaySchema,
134
+ commission: commissionSchema,
135
+ // ... see full model in examples
136
+ }, { timestamps: true });
137
+
138
+ export const Transaction = mongoose.model('Transaction', transactionSchema);
139
+ ```
140
+
141
+ When you call `monetization.create`, you can optionally pass `sourceId`/`sourceModel` in the input; revenue stores those as `sourceId`/`sourceModel` on the transaction for unified cashflow queries. If you create transactions yourself, set `sourceId`/`sourceModel` directly.
142
+
143
+ ### 2. Initialize Revenue
16
144
 
17
145
  ```typescript
18
- import { Revenue, Money, loggingPlugin } from '@classytic/revenue';
146
+ import { Revenue } from '@classytic/revenue';
19
147
  import { ManualProvider } from '@classytic/revenue-manual';
20
148
 
21
- const revenue = Revenue
22
- .create({ defaultCurrency: 'USD' })
23
- .withModels({ Transaction, Subscription })
149
+ const revenue = Revenue.create({
150
+ defaultCurrency: 'USD',
151
+ commissionRate: 0.10, // 10% platform fee
152
+ gatewayFeeRate: 0.029, // 2.9% payment processor
153
+ })
154
+ .withModels({ Transaction })
24
155
  .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
156
  .build();
157
+ ```
32
158
 
33
- // Access services
34
- await revenue.monetization.create({ ... });
35
- await revenue.payments.verify(transactionId);
36
- await revenue.escrow.hold(transactionId);
159
+ ### 3. Create a Payment
160
+
161
+ ```typescript
162
+ // Create subscription payment
163
+ const { transaction, subscription } = await revenue.monetization.create({
164
+ data: {
165
+ organizationId: 'org_123',
166
+ customerId: 'user_456',
167
+ },
168
+ planKey: 'monthly',
169
+ monetizationType: 'subscription',
170
+ amount: 2999, // $29.99 in cents
171
+ gateway: 'manual',
172
+ });
173
+
174
+ console.log(transaction.status); // 'pending'
175
+ ```
176
+
177
+ ### 4. Verify Payment
178
+
179
+ ```typescript
180
+ await revenue.payments.verify(transaction._id);
181
+
182
+ // Transaction: 'pending' → 'verified'
183
+ // Subscription: 'pending' → 'active'
37
184
  ```
38
185
 
39
- ### Shorthand Factory
186
+ ### 5. Handle Refunds
40
187
 
41
188
  ```typescript
42
- import { createRevenue } from '@classytic/revenue';
189
+ // Full refund
190
+ await revenue.payments.refund(transaction._id);
43
191
 
44
- const revenue = createRevenue({
45
- models: { Transaction, Subscription },
46
- providers: { manual: new ManualProvider() },
47
- options: { defaultCurrency: 'USD' },
192
+ // Partial refund: $10.00
193
+ await revenue.payments.refund(transaction._id, 1000, {
194
+ reason: 'customer_request',
48
195
  });
49
196
  ```
50
197
 
@@ -52,567 +199,496 @@ const revenue = createRevenue({
52
199
 
53
200
  ## Core Concepts
54
201
 
55
- ### Money (Integer-Safe Currency)
202
+ ### 1. Transaction Model (Required)
56
203
 
57
- ```typescript
58
- import { Money } from '@classytic/revenue';
204
+ **The universal ledger.** Every financial event becomes a transaction:
59
205
 
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
206
+ ```typescript
207
+ // Query subscriptions
208
+ const subscriptions = await Transaction.find({
209
+ type: 'platform_subscription',
210
+ status: 'verified'
211
+ });
63
212
 
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);
213
+ // Calculate revenue
214
+ const income = await Transaction.aggregate([
215
+ { $match: { flow: 'inflow', status: 'verified' } },
216
+ { $group: { _id: null, total: { $sum: '$amount' } } },
217
+ ]);
68
218
 
69
- // Format
70
- console.log(price.format()); // "$19.99"
71
- console.log(price.toUnit()); // 19.99
72
- console.log(price.amount); // 1999 (integer cents)
219
+ const expenses = await Transaction.aggregate([
220
+ { $match: { flow: 'outflow', status: 'verified' } },
221
+ { $group: { _id: null, total: { $sum: '$amount' } } },
222
+ ]);
73
223
 
74
- // Split fairly (handles rounding)
75
- const [a, b, c] = Money.usd(100).allocate([1, 1, 1]); // [34, 33, 33] cents
224
+ const netRevenue = income[0].total - expenses[0].total;
76
225
  ```
77
226
 
78
- ### Result Type (No Throws)
227
+ ### 2. Payment Providers (Required)
228
+
229
+ **How money flows in.** Providers are swappable:
79
230
 
80
231
  ```typescript
81
- import { Result, ok, err, match } from '@classytic/revenue';
232
+ import { ManualProvider } from '@classytic/revenue-manual';
233
+ // import { StripeProvider } from '@classytic/revenue-stripe'; // Coming soon
82
234
 
83
- // Execute with Result
84
- const result = await revenue.execute(
85
- () => riskyOperation(),
86
- { idempotencyKey: 'order_123' }
87
- );
235
+ revenue
236
+ .withProvider('manual', new ManualProvider())
237
+ .withProvider('stripe', new StripeProvider({ apiKey: '...' }));
88
238
 
89
- // Pattern matching
90
- match(result, {
91
- ok: (value) => console.log('Success:', value),
92
- err: (error) => console.log('Error:', error.message),
239
+ // Use any provider
240
+ await revenue.monetization.create({
241
+ gateway: 'manual', // or 'stripe'
242
+ // ...
93
243
  });
94
-
95
- // Or simple check
96
- if (result.ok) {
97
- console.log(result.value);
98
- } else {
99
- console.log(result.error);
100
- }
101
244
  ```
102
245
 
103
- ### Type-Safe Events
246
+ ### 3. Plugins (Optional)
247
+
248
+ **Extend behavior.** Plugins add features without coupling:
104
249
 
105
250
  ```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
- });
251
+ import { loggingPlugin, createTaxPlugin } from '@classytic/revenue/plugins';
111
252
 
112
- revenue.on('subscription.renewed', (event) => {
113
- sendEmail(event.subscription.customerId, 'Renewed!');
114
- });
253
+ revenue
254
+ .withPlugin(loggingPlugin({ level: 'info' }))
255
+ .withPlugin(createTaxPlugin({
256
+ getTaxConfig: async (orgId) => ({
257
+ isRegistered: true,
258
+ defaultRate: 0.15, // 15% tax
259
+ pricesIncludeTax: false,
260
+ }),
261
+ }));
262
+ ```
115
263
 
116
- revenue.on('escrow.released', (event) => {
117
- console.log('Released:', event.releasedAmount);
118
- });
264
+ ---
119
265
 
120
- // Wildcard - catch all events
121
- revenue.on('*', (event) => {
122
- analytics.track(event.type, event);
123
- });
124
- ```
266
+ ## Common Operations
125
267
 
126
- ### Validation (Zod v4)
268
+ ### Create Subscription
127
269
 
128
270
  ```typescript
129
- import { CreatePaymentSchema, validate, safeValidate } from '@classytic/revenue';
271
+ const { subscription, transaction } = await revenue.monetization.create({
272
+ data: {
273
+ organizationId: 'org_123',
274
+ customerId: 'user_456',
275
+ },
276
+ planKey: 'monthly_premium',
277
+ monetizationType: 'subscription',
278
+ amount: 2999, // $29.99/month
279
+ gateway: 'manual',
280
+ });
130
281
 
131
- // Validate input (throws on error)
132
- const payment = validate(CreatePaymentSchema, userInput);
282
+ // Later: Renew
283
+ await revenue.monetization.renew(subscription._id);
133
284
 
134
- // Safe validation (returns result)
135
- const result = safeValidate(CreatePaymentSchema, userInput);
136
- if (!result.success) {
137
- console.log(result.error.issues);
138
- }
285
+ // Cancel
286
+ await revenue.monetization.cancel(subscription._id, {
287
+ reason: 'customer_requested',
288
+ });
139
289
  ```
140
290
 
141
- ---
142
-
143
- ## Services
144
-
145
- ### Monetization (Purchases & Subscriptions)
291
+ ### Create One-Time Purchase
146
292
 
147
293
  ```typescript
148
- // One-time purchase
149
- const { transaction, paymentIntent } = await revenue.monetization.create({
294
+ const { transaction } = await revenue.monetization.create({
150
295
  data: {
151
- customerId: user._id,
152
- organizationId: org._id,
153
- referenceId: order._id,
154
- referenceModel: 'Order',
296
+ organizationId: 'org_123',
297
+ customerId: 'user_456',
298
+ sourceId: order._id, // optional: stored as sourceId
299
+ sourceModel: 'Order', // optional: stored as sourceModel
155
300
  },
156
301
  planKey: 'one_time',
157
302
  monetizationType: 'purchase',
158
- amount: 1500,
303
+ amount: 10000, // $100.00
159
304
  gateway: 'manual',
160
- paymentData: { method: 'card' },
161
305
  });
306
+ ```
162
307
 
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',
308
+ ### Query Transactions
309
+
310
+ ```typescript
311
+ // By type (category)
312
+ const subscriptions = await Transaction.find({
313
+ type: 'platform_subscription',
314
+ status: 'verified',
315
+ });
316
+
317
+ // By source (sourceId/sourceModel on the transaction)
318
+ const orderPayments = await Transaction.find({
319
+ sourceModel: 'Order',
320
+ sourceId: orderId,
170
321
  });
171
322
 
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);
177
- await revenue.monetization.cancel(subscription._id, { immediate: true });
323
+ // By customer
324
+ const customerTransactions = await Transaction.find({
325
+ customerId: userId,
326
+ flow: 'inflow',
327
+ }).sort({ createdAt: -1 });
178
328
  ```
179
329
 
180
- ### Payments
330
+ ---
331
+
332
+ ## Advanced Features
333
+
334
+ ### State Machines (Data Integrity)
335
+
336
+ Prevent invalid transitions automatically:
181
337
 
182
338
  ```typescript
183
- // Verify payment
184
- const { transaction, paymentResult } = await revenue.payments.verify(
185
- transactionId,
186
- { verifiedBy: adminId }
187
- );
339
+ import { TRANSACTION_STATE_MACHINE } from '@classytic/revenue';
188
340
 
189
- // Get status
190
- const { status, provider } = await revenue.payments.getStatus(transactionId);
341
+ // Valid
342
+ await revenue.payments.verify(transaction._id); // pending → verified
191
343
 
192
- // Full refund
193
- const { refundTransaction } = await revenue.payments.refund(transactionId);
344
+ // Invalid (throws InvalidStateTransitionError)
345
+ await revenue.payments.verify(completedTransaction._id); // completed → verified
194
346
 
195
- // Partial refund
196
- const { refundTransaction } = await revenue.payments.refund(
197
- transactionId,
198
- 500, // Amount in cents
199
- { reason: 'Partial return' }
347
+ // Check if transition is valid
348
+ const canRefund = TRANSACTION_STATE_MACHINE.canTransition(
349
+ transaction.status,
350
+ 'refunded'
200
351
  );
201
352
 
202
- // Handle webhook
203
- const { event, transaction } = await revenue.payments.handleWebhook(
204
- 'stripe',
205
- payload,
206
- headers
207
- );
353
+ // Get allowed next states
354
+ const allowed = TRANSACTION_STATE_MACHINE.getAllowedTransitions('verified');
355
+ // ['completed', 'refunded', 'partially_refunded', 'cancelled']
356
+
357
+ // Check if state is terminal
358
+ const isDone = TRANSACTION_STATE_MACHINE.isTerminalState('refunded'); // true
359
+ ```
360
+
361
+ **Available State Machines:**
362
+ - `TRANSACTION_STATE_MACHINE` - Payment lifecycle
363
+ - `SUBSCRIPTION_STATE_MACHINE` - Subscription states
364
+ - `SETTLEMENT_STATE_MACHINE` - Payout tracking
365
+ - `HOLD_STATE_MACHINE` - Escrow holds
366
+ - `SPLIT_STATE_MACHINE` - Revenue splits
367
+
368
+ ### Audit Trail (Track State Changes)
369
+
370
+ Every state transition is automatically logged:
371
+
372
+ ```typescript
373
+ import { getAuditTrail } from '@classytic/revenue';
374
+
375
+ const transaction = await Transaction.findById(txId);
376
+ const history = getAuditTrail(transaction);
377
+
378
+ console.log(history);
379
+ // [
380
+ // {
381
+ // resourceType: 'transaction',
382
+ // fromState: 'pending',
383
+ // toState: 'verified',
384
+ // changedAt: 2025-01-15T10:30:00.000Z,
385
+ // changedBy: 'admin_123',
386
+ // reason: 'Payment verified'
387
+ // }
388
+ // ]
208
389
  ```
209
390
 
210
- ### Escrow (Hold/Release)
391
+ ### Escrow (Marketplaces)
392
+
393
+ Hold funds until conditions met:
211
394
 
212
395
  ```typescript
213
- // Hold funds in escrow
214
- await revenue.escrow.hold(transactionId, {
215
- holdUntil: new Date('2024-12-31'),
216
- reason: 'Awaiting delivery confirmation',
396
+ // Create & verify transaction
397
+ const { transaction } = await revenue.monetization.create({ amount: 10000, ... });
398
+ await revenue.payments.verify(transaction._id);
399
+
400
+ // Hold in escrow
401
+ await revenue.escrow.hold(transaction._id, {
402
+ reason: 'pending_delivery',
403
+ holdUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
217
404
  });
218
405
 
219
- // Release to recipient
220
- await revenue.escrow.release(transactionId, {
221
- recipientId: vendorId,
406
+ // Release to seller after delivery confirmed
407
+ await revenue.escrow.release(transaction._id, {
408
+ recipientId: 'seller_123',
222
409
  recipientType: 'organization',
223
- amount: 800, // Partial release
410
+ reason: 'delivery_confirmed',
224
411
  });
412
+ ```
225
413
 
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
- ]);
414
+ ### Commission Splits (Affiliates)
231
415
 
232
- // Cancel hold
233
- await revenue.escrow.cancelHold(transactionId, { reason: 'Order cancelled' });
234
- ```
416
+ Split revenue between multiple parties:
235
417
 
236
- ---
418
+ ```typescript
419
+ await revenue.escrow.split(transaction._id, {
420
+ splits: [
421
+ { recipientId: 'creator_123', recipientType: 'user', percentage: 70 },
422
+ { recipientId: 'affiliate_456', recipientType: 'user', percentage: 10 },
423
+ ],
424
+ organizationPercentage: 20, // Platform keeps 20%
425
+ });
426
+
427
+ // Creates 3 transactions:
428
+ // - Creator: $70.00
429
+ // - Affiliate: $10.00
430
+ // - Platform: $20.00
431
+ ```
237
432
 
238
- ## Plugins
433
+ ### Events (React to Changes)
239
434
 
240
435
  ```typescript
241
- import { loggingPlugin, auditPlugin, metricsPlugin, definePlugin } from '@classytic/revenue';
436
+ import { EventBus } from '@classytic/revenue/events';
242
437
 
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();
438
+ revenue.events.on('payment:verified', async (event) => {
439
+ // Grant access
440
+ await grantAccess(event.transaction.customerId);
250
441
 
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();
260
- },
261
- },
442
+ // Send email
443
+ await sendEmail(event.transaction.customerId, 'Payment received!');
262
444
  });
263
- ```
264
445
 
265
- ---
446
+ revenue.events.on('subscription:cancelled', async (event) => {
447
+ await removeAccess(event.subscription.customerId);
448
+ });
266
449
 
267
- ## Resilience
450
+ // Other events:
451
+ // - monetization:created, payment:failed, payment:refunded
452
+ // - subscription:activated, subscription:renewed
453
+ // - escrow:held, escrow:released, settlement:completed
454
+ ```
455
+
456
+ ### Tax Plugin (Optional)
268
457
 
269
- ### Retry with Exponential Backoff
458
+ Automatically calculate and track tax:
270
459
 
271
460
  ```typescript
272
- import { retry, retryWithResult, isRetryableError } from '@classytic/revenue';
273
-
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
- );
461
+ import { createTaxPlugin } from '@classytic/revenue/plugins';
285
462
 
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
- }
463
+ const revenue = Revenue.create()
464
+ .withModels({ Transaction })
465
+ .withProvider('manual', new ManualProvider())
466
+ .withPlugin(createTaxPlugin({
467
+ getTaxConfig: async (organizationId) => ({
468
+ isRegistered: true,
469
+ defaultRate: 0.15, // 15% tax
470
+ pricesIncludeTax: false, // Tax-exclusive pricing
471
+ exemptCategories: ['education', 'donation'],
472
+ }),
473
+ }))
474
+ .build();
475
+
476
+ // Tax calculated automatically
477
+ const { transaction } = await revenue.monetization.create({
478
+ amount: 10000, // $100.00
479
+ // ...
480
+ });
481
+
482
+ console.log(transaction.tax);
483
+ // {
484
+ // rate: 0.15,
485
+ // baseAmount: 10000,
486
+ // taxAmount: 1500, // $15.00
487
+ // totalAmount: 11500, // $115.00
488
+ // }
489
+
490
+ // Tax automatically reversed on refunds
491
+ await revenue.payments.refund(transaction._id);
291
492
  ```
292
493
 
293
- ### Circuit Breaker
494
+ ### Custom Plugins
294
495
 
295
496
  ```typescript
296
- import { CircuitBreaker, createCircuitBreaker } from '@classytic/revenue';
497
+ import { definePlugin } from '@classytic/revenue/plugins';
297
498
 
298
- const breaker = createCircuitBreaker({
299
- failureThreshold: 5,
300
- resetTimeout: 30000,
301
- });
499
+ const notificationPlugin = definePlugin({
500
+ name: 'notifications',
501
+ version: '1.0.0',
502
+ hooks: {
503
+ 'payment.verify.after': async (ctx, input, next) => {
504
+ const result = await next();
505
+
506
+ // Send notification
507
+ await sendPushNotification({
508
+ userId: result.transaction.customerId,
509
+ message: 'Payment verified!',
510
+ });
302
511
 
303
- const result = await breaker.execute(() => callExternalAPI());
512
+ return result;
513
+ },
514
+ },
515
+ });
304
516
 
305
- // Check state
306
- console.log(breaker.getState()); // 'closed' | 'open' | 'half-open'
517
+ revenue.withPlugin(notificationPlugin);
307
518
  ```
308
519
 
309
- ### Idempotency
520
+ ### Resilience Patterns
521
+
522
+ Built-in retry, circuit breaker, and idempotency:
310
523
 
311
524
  ```typescript
525
+ // Automatic retry on provider failures
526
+ await revenue.payments.verify(transaction._id);
527
+ // Retries 3x with exponential backoff
528
+
529
+ // Manual idempotency
312
530
  import { IdempotencyManager } from '@classytic/revenue';
313
531
 
314
- const idempotency = new IdempotencyManager({ ttl: 86400000 }); // 24h
532
+ const idem = new IdempotencyManager();
315
533
 
316
- const result = await idempotency.execute(
317
- 'payment_order_123',
318
- { amount: 1999, customerId: 'cust_1' },
319
- () => chargeCard()
534
+ const result = await idem.execute(
535
+ 'charge_user_123',
536
+ { amount: 2999 },
537
+ () => revenue.monetization.create({ ... })
320
538
  );
321
539
 
322
- // Same key + same params = cached result
323
- // Same key + different params = error
540
+ // Second call returns cached result (no duplicate charge)
324
541
  ```
325
542
 
326
- ---
327
-
328
- ## Transaction Model Setup
329
-
330
- **ONE Transaction model = Universal Financial Ledger**
543
+ ### Money Utilities
331
544
 
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).
545
+ No floating-point errors. All amounts in smallest currency unit (cents):
333
546
 
334
547
  ```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';
548
+ import { Money, toSmallestUnit, fromSmallestUnit } from '@classytic/revenue';
347
549
 
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
- ];
358
-
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 },
550
+ // Create Money instances
551
+ const price = Money.usd(1999); // $19.99
552
+ const euro = Money.of(2999, 'EUR'); // €29.99
369
553
 
370
- // Library schemas (compose, don't spread)
371
- gateway: gatewaySchema,
372
- commission: commissionSchema,
373
- paymentDetails: paymentDetailsSchema,
374
- hold: holdSchema,
375
- splits: [splitSchema],
376
-
377
- // Polymorphic reference (link to any entity)
378
- referenceId: { type: mongoose.Schema.Types.ObjectId, refPath: 'referenceModel' },
379
- referenceModel: { type: String, enum: ['Subscription', 'Order', 'Enrollment'] },
380
-
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,
389
-
390
- metadata: mongoose.Schema.Types.Mixed,
391
- }, { timestamps: true });
554
+ // Conversions
555
+ toSmallestUnit(19.99, 'USD'); // 1999 cents
556
+ fromSmallestUnit(1999, 'USD'); // 19.99
392
557
 
393
- export const Transaction = mongoose.model('Transaction', transactionSchema);
394
- ```
558
+ // Arithmetic (immutable)
559
+ const total = price.add(Money.usd(500)); // $24.99
560
+ const discounted = price.multiply(0.9); // $17.99
395
561
 
396
- ### Available Schemas
562
+ // Fair allocation (handles rounding)
563
+ const [a, b, c] = Money.usd(100).allocate([1, 1, 1]);
564
+ // [34, 33, 33] cents - total = 100 ✓
397
565
 
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 | `currentPayment: currentPaymentSchema` |
566
+ // Formatting
567
+ price.format(); // "$19.99"
568
+ ```
406
569
 
407
- **Usage:** Import and use as nested objects (NOT spread):
570
+ ---
408
571
 
409
- ```typescript
410
- import { gatewaySchema, commissionSchema } from '@classytic/revenue';
572
+ ## When to Use What
411
573
 
412
- const schema = new mongoose.Schema({
413
- gateway: gatewaySchema, // ✅ Correct - nested
414
- commission: commissionSchema,
415
- // ...gatewaySchema, // Wrong - don't spread
416
- });
417
- ```
574
+ | Feature | Use Case |
575
+ |---------|----------|
576
+ | `monetization.create()` | New payment (subscription, purchase, free item) |
577
+ | `payments.verify()` | Mark payment successful after gateway confirmation |
578
+ | `payments.refund()` | Return money to customer (full or partial) |
579
+ | `escrow.hold()` | Marketplace - hold funds until delivery confirmed |
580
+ | `escrow.split()` | Affiliate/creator revenue sharing |
581
+ | Plugins | Tax calculation, logging, audit trails, metrics |
582
+ | Events | Send emails, grant/revoke access, analytics |
583
+ | State machines | Validate transitions, get allowed next actions |
418
584
 
419
585
  ---
420
586
 
421
- ## Group Payments (Split Pay)
587
+ ## Real-World Example
422
588
 
423
- Multiple payers can contribute to one purchase using `referenceId`:
589
+ **Course marketplace with affiliates:**
424
590
 
425
591
  ```typescript
426
- // Order total: $100 (10000 cents)
427
- const orderId = new mongoose.Types.ObjectId();
428
- const orderTotal = 10000;
429
-
430
- // Friend 1 pays $40
431
- await revenue.monetization.create({
592
+ // 1. Student buys course ($99)
593
+ const { transaction } = await revenue.monetization.create({
432
594
  data: {
433
- customerId: friend1,
434
- organizationId: restaurantId,
435
- referenceId: orderId,
436
- referenceModel: 'Order',
595
+ organizationId: 'org_123',
596
+ customerId: 'student_456',
597
+ sourceId: enrollmentId,
598
+ sourceModel: 'Enrollment',
437
599
  },
438
- planKey: 'split_payment',
600
+ planKey: 'one_time',
439
601
  monetizationType: 'purchase',
440
- amount: 4000,
602
+ entity: 'CourseEnrollment',
603
+ amount: 9900,
441
604
  gateway: 'stripe',
442
- metadata: { splitGroup: 'dinner_dec_10' },
443
605
  });
444
606
 
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' },
458
- });
607
+ // 2. Payment verified → Grant course access
608
+ await revenue.payments.verify(transaction._id);
459
609
 
460
- // Friend 3 pays $25
461
- await revenue.monetization.create({
462
- data: {
463
- customerId: friend3,
464
- organizationId: restaurantId,
465
- referenceId: orderId,
466
- referenceModel: 'Order',
467
- },
468
- planKey: 'split_payment',
469
- monetizationType: 'purchase',
470
- amount: 2500,
471
- gateway: 'stripe',
472
- metadata: { splitGroup: 'dinner_dec_10' },
610
+ // 3. Hold in escrow (30-day refund window)
611
+ await revenue.escrow.hold(transaction._id);
612
+
613
+ // 4. After 30 days, split revenue
614
+ await revenue.escrow.split(transaction._id, {
615
+ splits: [
616
+ { recipientId: 'creator_123', percentage: 70 }, // $69.30
617
+ { recipientId: 'affiliate_456', percentage: 10 }, // $9.90
618
+ ],
619
+ organizationPercentage: 20, // $19.80 (platform)
473
620
  });
621
+
622
+ // 5. Calculate P&L
623
+ const income = await Transaction.aggregate([
624
+ { $match: { flow: 'inflow', status: 'verified' } },
625
+ { $group: { _id: null, total: { $sum: '$amount' } } },
626
+ ]);
474
627
  ```
475
628
 
476
- ### Check Payment Status
629
+ ---
630
+
631
+ ## Submodule Imports
632
+
633
+ Tree-shakable imports for smaller bundles:
477
634
 
478
635
  ```typescript
479
- // Get all contributions for an order
480
- const contributions = await Transaction.find({
481
- referenceId: orderId,
482
- referenceModel: 'Order',
483
- });
636
+ // Plugins
637
+ import { loggingPlugin, auditPlugin, createTaxPlugin } from '@classytic/revenue/plugins';
484
638
 
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
- ```
639
+ // Enums
640
+ import { TRANSACTION_STATUS, PAYMENT_STATUS } from '@classytic/revenue/enums';
502
641
 
503
- ### Query by Split Group
642
+ // Events
643
+ import { EventBus } from '@classytic/revenue/events';
504
644
 
505
- ```typescript
506
- // Find all payments in a split group
507
- const groupPayments = await Transaction.find({
508
- 'metadata.splitGroup': 'dinner_dec_10',
509
- });
645
+ // Schemas (Mongoose)
646
+ import { transactionSchema, subscriptionSchema } from '@classytic/revenue/schemas';
510
647
 
511
- // Pending payers
512
- const pending = await Transaction.find({
513
- referenceId: orderId,
514
- status: 'pending',
515
- });
516
- ```
648
+ // Validation (Zod)
649
+ import { CreatePaymentSchema } from '@classytic/revenue/schemas/validation';
517
650
 
518
- ---
651
+ // Utilities
652
+ import { retry, calculateCommission } from '@classytic/revenue/utils';
519
653
 
520
- ## Building Custom Providers
654
+ // Reconciliation
655
+ import { reconcileSettlement } from '@classytic/revenue/reconciliation';
521
656
 
522
- ```typescript
523
- import { PaymentProvider, PaymentIntent, PaymentResult, RefundResult, WebhookEvent } from '@classytic/revenue';
524
- import type { CreateIntentParams, ProviderCapabilities } from '@classytic/revenue';
657
+ // Services (advanced)
658
+ import { MonetizationService } from '@classytic/revenue/services';
659
+ ```
525
660
 
526
- export class StripeProvider extends PaymentProvider {
527
- public override readonly name = 'stripe';
528
- private stripe: Stripe;
661
+ ---
529
662
 
530
- constructor(config: { apiKey: string }) {
531
- super(config);
532
- this.stripe = new Stripe(config.apiKey);
533
- }
663
+ ## API Reference
534
664
 
535
- async createIntent(params: CreateIntentParams): Promise<PaymentIntent> {
536
- const intent = await this.stripe.paymentIntents.create({
537
- amount: params.amount,
538
- currency: params.currency ?? 'usd',
539
- metadata: params.metadata,
540
- });
541
-
542
- return new PaymentIntent({
543
- id: intent.id,
544
- paymentIntentId: intent.id,
545
- sessionId: null,
546
- provider: this.name,
547
- status: intent.status,
548
- amount: intent.amount,
549
- currency: intent.currency,
550
- clientSecret: intent.client_secret!,
551
- metadata: params.metadata ?? {},
552
- });
553
- }
665
+ ### Services
554
666
 
555
- async verifyPayment(intentId: string): Promise<PaymentResult> {
556
- const intent = await this.stripe.paymentIntents.retrieve(intentId);
557
- return new PaymentResult({
558
- id: intent.id,
559
- provider: this.name,
560
- status: intent.status === 'succeeded' ? 'succeeded' : 'failed',
561
- amount: intent.amount,
562
- currency: intent.currency,
563
- paidAt: intent.status === 'succeeded' ? new Date() : undefined,
564
- metadata: {},
565
- });
566
- }
667
+ | Service | Methods |
668
+ |---------|---------|
669
+ | `revenue.monetization` | `create()`, `renew()`, `cancel()`, `pause()`, `resume()` |
670
+ | `revenue.payments` | `verify()`, `refund()`, `getStatus()`, `handleWebhook()` |
671
+ | `revenue.transactions` | `get()`, `list()`, `update()` |
672
+ | `revenue.escrow` | `hold()`, `release()`, `cancel()`, `split()`, `getStatus()` |
673
+ | `revenue.settlement` | `createFromSplits()`, `processPending()`, `complete()`, `fail()`, `getSummary()` |
567
674
 
568
- async getStatus(intentId: string): Promise<PaymentResult> {
569
- return this.verifyPayment(intentId);
570
- }
675
+ ### State Machines
571
676
 
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
- });
577
-
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
- }
677
+ All state machines provide:
678
+ - `canTransition(from, to)` - Check if transition is valid
679
+ - `validate(from, to, id)` - Validate or throw error
680
+ - `getAllowedTransitions(state)` - Get next allowed states
681
+ - `isTerminalState(state)` - Check if state is final
588
682
 
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
- }
683
+ ### Utilities
605
684
 
606
- override getCapabilities(): ProviderCapabilities {
607
- return {
608
- supportsWebhooks: true,
609
- supportsRefunds: true,
610
- supportsPartialRefunds: true,
611
- requiresManualVerification: false,
612
- };
613
- }
614
- }
615
- ```
685
+ | Function | Purpose |
686
+ |----------|---------|
687
+ | `calculateCommission(amount, rate, gatewayFee)` | Calculate platform commission |
688
+ | `calculateCommissionWithSplits(...)` | Commission with affiliate support |
689
+ | `reverseTax(originalTax, refundAmount)` | Proportional tax reversal |
690
+ | `retry(fn, options)` | Retry with exponential backoff |
691
+ | `reconcileSettlement(gatewayData, dbData)` | Gateway reconciliation |
616
692
 
617
693
  ---
618
694
 
@@ -620,68 +696,109 @@ export class StripeProvider extends PaymentProvider {
620
696
 
621
697
  ```typescript
622
698
  import {
623
- RevenueError,
624
- TransactionNotFoundError,
625
- AlreadyVerifiedError,
699
+ PaymentIntentCreationError,
700
+ InvalidStateTransitionError,
701
+ InvalidAmountError,
626
702
  RefundError,
627
- ProviderNotFoundError,
628
- ValidationError,
629
- isRevenueError,
630
- isRetryable,
631
703
  } from '@classytic/revenue';
632
704
 
633
705
  try {
634
- await revenue.payments.verify(id);
706
+ await revenue.monetization.create({ amount: -100 }); // Invalid
635
707
  } catch (error) {
636
- if (error instanceof AlreadyVerifiedError) {
637
- console.log('Already verified:', error.metadata.transactionId);
638
- } else if (error instanceof TransactionNotFoundError) {
639
- console.log('Not found');
640
- } else if (isRevenueError(error) && isRetryable(error)) {
641
- // Retry the operation
708
+ if (error instanceof InvalidAmountError) {
709
+ console.error('Amount must be positive');
710
+ } else if (error instanceof PaymentIntentCreationError) {
711
+ console.error('Payment gateway failed:', error.message);
642
712
  }
643
713
  }
714
+
715
+ // Or use Result type (no exceptions)
716
+ import { Result } from '@classytic/revenue';
717
+
718
+ const result = await revenue.execute(
719
+ () => revenue.payments.verify(txId),
720
+ { idempotencyKey: 'verify_123' }
721
+ );
722
+
723
+ if (result.ok) {
724
+ console.log(result.value);
725
+ } else {
726
+ console.error(result.error);
727
+ }
644
728
  ```
645
729
 
646
730
  ---
647
731
 
648
- ## TypeScript
732
+ ## TypeScript Support
649
733
 
650
- Full TypeScript support with exported types:
734
+ Full type safety with auto-completion:
651
735
 
652
736
  ```typescript
653
737
  import type {
654
- Revenue,
655
738
  TransactionDocument,
656
739
  SubscriptionDocument,
657
- PaymentProviderInterface,
658
- CreateIntentParams,
659
- ProviderCapabilities,
660
- RevenueEvents,
661
- MonetizationCreateParams,
740
+ CommissionInfo,
741
+ RevenueConfig,
662
742
  } from '@classytic/revenue';
743
+
744
+ const transaction: TransactionDocument = await revenue.transactions.get(txId);
745
+ const commission: CommissionInfo = transaction.commission;
663
746
  ```
664
747
 
665
748
  ---
666
749
 
667
- ## Testing
750
+ ## Examples
668
751
 
669
- ```bash
670
- # Run all tests (84 tests)
671
- npm test
752
+ - [Quick Start](./examples/01-quick-start.ts) - Basic setup and first payment
753
+ - [Subscriptions](./examples/02-subscriptions.ts) - Recurring billing
754
+ - [Escrow & Splits](./examples/03-escrow-splits.ts) - Marketplace payouts
755
+ - [Events & Plugins](./examples/04-events-plugins.ts) - Extend functionality
756
+ - [Transaction Model](./examples/05-transaction-model.ts) - Complete model setup
757
+ - [Resilience Patterns](./examples/06-resilience.ts) - Retry, circuit breaker
672
758
 
673
- # Run integration tests (requires MongoDB)
674
- npm test -- tests/integration/
759
+ ---
760
+
761
+ ## Built-in Plugins
675
762
 
676
- # Watch mode
677
- npm run test:watch
763
+ ```typescript
764
+ import {
765
+ loggingPlugin,
766
+ auditPlugin,
767
+ metricsPlugin,
768
+ createTaxPlugin
769
+ } from '@classytic/revenue/plugins';
678
770
 
679
- # Coverage
680
- npm run test:coverage
771
+ revenue
772
+ .withPlugin(loggingPlugin({ level: 'info' }))
773
+ .withPlugin(auditPlugin({
774
+ store: async (entry) => {
775
+ await AuditLog.create(entry);
776
+ },
777
+ }))
778
+ .withPlugin(metricsPlugin({
779
+ onMetric: (metric) => {
780
+ statsd.timing(metric.name, metric.duration);
781
+ },
782
+ }))
783
+ .withPlugin(createTaxPlugin({ ... }));
681
784
  ```
682
785
 
683
786
  ---
684
787
 
788
+ ## Contributing
789
+
790
+ Contributions welcome! Open an issue or submit a pull request on [GitHub](https://github.com/classytic/revenue).
791
+
792
+ ---
793
+
685
794
  ## License
686
795
 
687
796
  MIT © [Classytic](https://github.com/classytic)
797
+
798
+ ---
799
+
800
+ ## Support
801
+
802
+ - 📖 [Documentation](https://github.com/classytic/revenue#readme)
803
+ - 🐛 [Issues](https://github.com/classytic/revenue/issues)
804
+ - 💬 [Discussions](https://github.com/classytic/revenue/discussions)