@classytic/revenue 1.0.6 → 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 (54) hide show
  1. package/README.md +581 -633
  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 +2372 -786
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/enums/index.d.ts +29 -8
  12. package/dist/enums/index.js +41 -8
  13. package/dist/enums/index.js.map +1 -1
  14. package/dist/escrow.enums-CE0VQsfe.d.ts +76 -0
  15. package/dist/{index-BnEXsnLJ.d.ts → index-DxIK0UmZ.d.ts} +281 -26
  16. package/dist/index-EnfKzDbs.d.ts +806 -0
  17. package/dist/{index-C5SsOrV0.d.ts → index-cLJBLUvx.d.ts} +55 -111
  18. package/dist/index.d.ts +16 -16
  19. package/dist/index.js +2558 -2192
  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 +1653 -49
  35. package/dist/schemas/index.js +233 -19
  36. package/dist/schemas/index.js.map +1 -1
  37. package/dist/schemas/validation.d.ts +4 -4
  38. package/dist/schemas/validation.js +16 -15
  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 +351 -289
  46. package/dist/utils/index.js.map +1 -1
  47. package/package.json +22 -9
  48. package/dist/actions-Ctf2XUL-.d.ts +0 -519
  49. package/dist/payment.enums-B_RwB8iR.d.ts +0 -184
  50. package/dist/services/index.d.ts +0 -3
  51. package/dist/services/index.js +0 -1702
  52. package/dist/services/index.js.map +0 -1
  53. package/dist/split.schema-DLVF3XBI.d.ts +0 -1122
  54. package/dist/transaction.enums-7uBnuswI.d.ts +0 -87
package/README.md CHANGED
@@ -1,856 +1,804 @@
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
6
 
7
- ## Installation
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)
8
10
 
9
- ```bash
10
- npm install @classytic/revenue @classytic/revenue-manual
11
- ```
11
+ ---
12
12
 
13
- ## Quick Start
13
+ ## What Is This?
14
14
 
15
- ### Fluent Builder API (Recommended)
15
+ A TypeScript library that handles **all financial transactions** in one unified model:
16
16
 
17
17
  ```typescript
18
- import { Revenue, Money, loggingPlugin } from '@classytic/revenue';
19
- import { ManualProvider } from '@classytic/revenue-manual';
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();
18
+ // Subscription payment
19
+ { type: 'subscription', flow: 'inflow', amount: 2999 }
32
20
 
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
21
+ // Product purchase
22
+ { type: 'product_order', flow: 'inflow', amount: 1500 }
40
23
 
41
- ```typescript
42
- import { createRevenue } from '@classytic/revenue';
24
+ // Refund
25
+ { type: 'refund', flow: 'outflow', amount: 1500 }
43
26
 
44
- const revenue = createRevenue({
45
- models: { Transaction, Subscription },
46
- providers: { manual: new ManualProvider() },
47
- options: { defaultCurrency: 'USD' },
48
- });
27
+ // Operational expense
28
+ { type: 'rent', flow: 'outflow', amount: 50000 }
49
29
  ```
50
30
 
31
+ **One table. Query by type. Calculate P&L. Track cash flow.**
32
+
51
33
  ---
34
+ ## Unified Cashflow Model (Shared Types)
52
35
 
53
- ## Core Concepts
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”.
54
37
 
55
- ### Money (Integer-Safe Currency)
38
+ Type safety is provided by `ITransaction` only. Transaction categories (`type`) are app-defined; `flow` (`inflow`/`outflow`) is the only shared enum.
56
39
 
57
40
  ```typescript
58
- import { Money } from '@classytic/revenue';
41
+ import type { ITransaction } from '@classytic/shared-types';
42
+ // or: import type { ITransaction } from '@classytic/revenue';
43
+ ```
59
44
 
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
63
45
 
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);
46
+ ## Why Use This?
68
47
 
69
- // Format
70
- console.log(price.format()); // "$19.99"
71
- console.log(price.toUnit()); // 19.99
72
- console.log(price.amount); // 1999 (integer cents)
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
73
53
 
74
- // Split fairly (handles rounding)
75
- const [a, b, c] = Money.usd(100).allocate([1, 1, 1]); // [34, 33, 33] cents
76
- ```
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
77
62
 
78
- ### Result Type (No Throws)
63
+ ---
79
64
 
80
- ```typescript
81
- import { Result, ok, err, match } from '@classytic/revenue';
65
+ ## When to Use This
82
66
 
83
- // Execute with Result
84
- const result = await revenue.execute(
85
- () => riskyOperation(),
86
- { idempotencyKey: 'order_123' }
87
- );
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 |
88
75
 
89
- // Pattern matching
90
- match(result, {
91
- ok: (value) => console.log('Success:', value),
92
- err: (error) => console.log('Error:', error.message),
93
- });
76
+ ---
94
77
 
95
- // Or simple check
96
- if (result.ok) {
97
- console.log(result.value);
98
- } else {
99
- console.log(result.error);
100
- }
78
+ ## Installation
79
+
80
+ ```bash
81
+ npm install @classytic/revenue mongoose zod
101
82
  ```
102
83
 
103
- ### Type-Safe Events
84
+ **Peer Dependencies:**
85
+ - `mongoose` ^8.0.0 || ^9.0.0
86
+ - `zod` ^4.1.13
104
87
 
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
- });
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
92
+ ```
111
93
 
112
- revenue.on('subscription.renewed', (event) => {
113
- sendEmail(event.subscription.customerId, 'Renewed!');
114
- });
94
+ ---
115
95
 
116
- revenue.on('escrow.released', (event) => {
117
- console.log('Released:', event.releasedAmount);
118
- });
96
+ ## Quick Start
119
97
 
120
- // Wildcard - catch all events
121
- revenue.on('*', (event) => {
122
- analytics.track(event.type, event);
123
- });
124
- ```
98
+ ### 1. Define Your Transaction Model
125
99
 
126
- ### Validation (Zod v4)
100
+ Copy the complete model from [examples/05-transaction-model.ts](./examples/05-transaction-model.ts):
127
101
 
128
102
  ```typescript
103
+ import mongoose, { Schema } from 'mongoose';
104
+ import type { ITransaction } from '@classytic/shared-types';
129
105
  import {
130
- CreatePaymentSchema,
131
- PaymentEntrySchema,
132
- CurrentPaymentInputSchema,
133
- validate,
134
- safeValidate,
135
- validateSplitPayments,
106
+ TRANSACTION_FLOW_VALUES,
107
+ TRANSACTION_STATUS_VALUES,
108
+ gatewaySchema,
109
+ commissionSchema,
136
110
  } from '@classytic/revenue';
137
111
 
138
- // Validate input (throws on error)
139
- const payment = validate(CreatePaymentSchema, userInput);
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
+ };
140
121
 
141
- // Safe validation (returns result)
142
- const result = safeValidate(CreatePaymentSchema, userInput);
143
- if (!result.success) {
144
- console.log(result.error.issues);
145
- }
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 });
146
137
 
147
- // Split payment validation
148
- const splitResult = safeValidate(CurrentPaymentInputSchema, {
149
- amount: 50000,
150
- method: 'split',
151
- payments: [
152
- { method: 'cash', amount: 25000 },
153
- { method: 'bkash', amount: 25000 },
154
- ],
155
- });
138
+ export const Transaction = mongoose.model('Transaction', transactionSchema);
156
139
  ```
157
140
 
158
- ---
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
159
144
 
160
- ## Services
145
+ ```typescript
146
+ import { Revenue } from '@classytic/revenue';
147
+ import { ManualProvider } from '@classytic/revenue-manual';
148
+
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 })
155
+ .withProvider('manual', new ManualProvider())
156
+ .build();
157
+ ```
161
158
 
162
- ### Monetization (Purchases & Subscriptions)
159
+ ### 3. Create a Payment
163
160
 
164
161
  ```typescript
165
- // One-time purchase
166
- const { transaction, paymentIntent } = await revenue.monetization.create({
162
+ // Create subscription payment
163
+ const { transaction, subscription } = await revenue.monetization.create({
167
164
  data: {
168
- customerId: user._id,
169
- organizationId: org._id,
170
- referenceId: order._id,
171
- referenceModel: 'Order',
165
+ organizationId: 'org_123',
166
+ customerId: 'user_456',
172
167
  },
173
- planKey: 'one_time',
174
- monetizationType: 'purchase',
175
- amount: 1500,
176
- gateway: 'manual',
177
- paymentData: { method: 'card' },
178
- });
179
-
180
- // Recurring subscription
181
- const { subscription, transaction } = await revenue.monetization.create({
182
- data: { customerId: user._id },
183
168
  planKey: 'monthly',
184
169
  monetizationType: 'subscription',
185
- amount: 2999,
186
- gateway: 'stripe',
170
+ amount: 2999, // $29.99 in cents
171
+ gateway: 'manual',
187
172
  });
188
173
 
189
- // Lifecycle management
190
- await revenue.monetization.activate(subscription._id);
191
- await revenue.monetization.renew(subscription._id, { gateway: 'stripe' });
192
- await revenue.monetization.pause(subscription._id, { reason: 'Vacation' });
193
- await revenue.monetization.resume(subscription._id);
194
- await revenue.monetization.cancel(subscription._id, { immediate: true });
174
+ console.log(transaction.status); // 'pending'
195
175
  ```
196
176
 
197
- ### Payments
177
+ ### 4. Verify Payment
198
178
 
199
179
  ```typescript
200
- // Verify payment
201
- const { transaction, paymentResult } = await revenue.payments.verify(
202
- transactionId,
203
- { verifiedBy: adminId }
204
- );
180
+ await revenue.payments.verify(transaction._id);
205
181
 
206
- // Get status
207
- const { status, provider } = await revenue.payments.getStatus(transactionId);
182
+ // Transaction: 'pending' → 'verified'
183
+ // Subscription: 'pending' 'active'
184
+ ```
208
185
 
209
- // Full refund
210
- const { refundTransaction } = await revenue.payments.refund(transactionId);
186
+ ### 5. Handle Refunds
211
187
 
212
- // Partial refund
213
- const { refundTransaction } = await revenue.payments.refund(
214
- transactionId,
215
- 500, // Amount in cents
216
- { reason: 'Partial return' }
217
- );
188
+ ```typescript
189
+ // Full refund
190
+ await revenue.payments.refund(transaction._id);
218
191
 
219
- // Handle webhook
220
- const { event, transaction } = await revenue.payments.handleWebhook(
221
- 'stripe',
222
- payload,
223
- headers
224
- );
192
+ // Partial refund: $10.00
193
+ await revenue.payments.refund(transaction._id, 1000, {
194
+ reason: 'customer_request',
195
+ });
225
196
  ```
226
197
 
227
- ### Escrow (Hold/Release)
198
+ ---
199
+
200
+ ## Core Concepts
201
+
202
+ ### 1. Transaction Model (Required)
203
+
204
+ **The universal ledger.** Every financial event becomes a transaction:
228
205
 
229
206
  ```typescript
230
- // Hold funds in escrow
231
- await revenue.escrow.hold(transactionId, {
232
- holdUntil: new Date('2024-12-31'),
233
- reason: 'Awaiting delivery confirmation',
207
+ // Query subscriptions
208
+ const subscriptions = await Transaction.find({
209
+ type: 'platform_subscription',
210
+ status: 'verified'
234
211
  });
235
212
 
236
- // Release to recipient
237
- await revenue.escrow.release(transactionId, {
238
- recipientId: vendorId,
239
- recipientType: 'organization',
240
- amount: 800, // Partial release
241
- });
213
+ // Calculate revenue
214
+ const income = await Transaction.aggregate([
215
+ { $match: { flow: 'inflow', status: 'verified' } },
216
+ { $group: { _id: null, total: { $sum: '$amount' } } },
217
+ ]);
242
218
 
243
- // Multi-party split
244
- await revenue.escrow.split(transactionId, [
245
- { type: 'platform_commission', recipientId: 'platform', rate: 0.10 },
246
- { type: 'affiliate_commission', recipientId: 'aff_123', rate: 0.05 },
219
+ const expenses = await Transaction.aggregate([
220
+ { $match: { flow: 'outflow', status: 'verified' } },
221
+ { $group: { _id: null, total: { $sum: '$amount' } } },
247
222
  ]);
248
223
 
249
- // Cancel hold
250
- await revenue.escrow.cancelHold(transactionId, { reason: 'Order cancelled' });
224
+ const netRevenue = income[0].total - expenses[0].total;
251
225
  ```
252
226
 
253
- ---
227
+ ### 2. Payment Providers (Required)
254
228
 
255
- ## Plugins
229
+ **How money flows in.** Providers are swappable:
256
230
 
257
231
  ```typescript
258
- import { loggingPlugin, auditPlugin, metricsPlugin, definePlugin } from '@classytic/revenue';
232
+ import { ManualProvider } from '@classytic/revenue-manual';
233
+ // import { StripeProvider } from '@classytic/revenue-stripe'; // Coming soon
259
234
 
260
- // Built-in plugins
261
- const revenue = Revenue
262
- .create()
263
- .withPlugin(loggingPlugin({ level: 'info' }))
264
- .withPlugin(auditPlugin({ store: saveToDatabase }))
265
- .withPlugin(metricsPlugin({ onMetric: sendToDatadog }))
266
- .build();
235
+ revenue
236
+ .withProvider('manual', new ManualProvider())
237
+ .withProvider('stripe', new StripeProvider({ apiKey: '...' }));
267
238
 
268
- // Custom plugin
269
- const rateLimitPlugin = definePlugin({
270
- name: 'rate-limit',
271
- hooks: {
272
- 'payment.create.before': async (ctx, input, next) => {
273
- if (await isRateLimited(input.customerId)) {
274
- throw new Error('Rate limited');
275
- }
276
- return next();
277
- },
278
- },
239
+ // Use any provider
240
+ await revenue.monetization.create({
241
+ gateway: 'manual', // or 'stripe'
242
+ // ...
279
243
  });
280
244
  ```
281
245
 
282
- ---
246
+ ### 3. Plugins (Optional)
283
247
 
284
- ## Resilience
285
-
286
- ### Retry with Exponential Backoff
248
+ **Extend behavior.** Plugins add features without coupling:
287
249
 
288
250
  ```typescript
289
- import { retry, retryWithResult, isRetryableError } from '@classytic/revenue';
290
-
291
- // Simple retry
292
- const data = await retry(
293
- () => fetchPaymentStatus(id),
294
- {
295
- maxAttempts: 5,
296
- baseDelay: 1000,
297
- maxDelay: 30000,
298
- backoffMultiplier: 2,
299
- jitter: 0.1,
300
- }
301
- );
251
+ import { loggingPlugin, createTaxPlugin } from '@classytic/revenue/plugins';
302
252
 
303
- // Retry with Result (no throws)
304
- const result = await retryWithResult(() => processPayment());
305
- if (!result.ok) {
306
- console.log('All retries failed:', result.error.errors);
307
- }
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
+ }));
308
262
  ```
309
263
 
310
- ### Circuit Breaker
264
+ ---
265
+
266
+ ## Common Operations
311
267
 
312
- ```typescript
313
- import { CircuitBreaker, createCircuitBreaker } from '@classytic/revenue';
268
+ ### Create Subscription
314
269
 
315
- const breaker = createCircuitBreaker({
316
- failureThreshold: 5,
317
- resetTimeout: 30000,
270
+ ```typescript
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',
318
280
  });
319
281
 
320
- const result = await breaker.execute(() => callExternalAPI());
282
+ // Later: Renew
283
+ await revenue.monetization.renew(subscription._id);
321
284
 
322
- // Check state
323
- console.log(breaker.getState()); // 'closed' | 'open' | 'half-open'
285
+ // Cancel
286
+ await revenue.monetization.cancel(subscription._id, {
287
+ reason: 'customer_requested',
288
+ });
324
289
  ```
325
290
 
326
- ### Idempotency
291
+ ### Create One-Time Purchase
327
292
 
328
293
  ```typescript
329
- import { IdempotencyManager } from '@classytic/revenue';
294
+ const { transaction } = await revenue.monetization.create({
295
+ data: {
296
+ organizationId: 'org_123',
297
+ customerId: 'user_456',
298
+ sourceId: order._id, // optional: stored as sourceId
299
+ sourceModel: 'Order', // optional: stored as sourceModel
300
+ },
301
+ planKey: 'one_time',
302
+ monetizationType: 'purchase',
303
+ amount: 10000, // $100.00
304
+ gateway: 'manual',
305
+ });
306
+ ```
330
307
 
331
- const idempotency = new IdempotencyManager({ ttl: 86400000 }); // 24h
308
+ ### Query Transactions
332
309
 
333
- const result = await idempotency.execute(
334
- 'payment_order_123',
335
- { amount: 1999, customerId: 'cust_1' },
336
- () => chargeCard()
337
- );
310
+ ```typescript
311
+ // By type (category)
312
+ const subscriptions = await Transaction.find({
313
+ type: 'platform_subscription',
314
+ status: 'verified',
315
+ });
338
316
 
339
- // Same key + same params = cached result
340
- // Same key + different params = error
317
+ // By source (sourceId/sourceModel on the transaction)
318
+ const orderPayments = await Transaction.find({
319
+ sourceModel: 'Order',
320
+ sourceId: orderId,
321
+ });
322
+
323
+ // By customer
324
+ const customerTransactions = await Transaction.find({
325
+ customerId: userId,
326
+ flow: 'inflow',
327
+ }).sort({ createdAt: -1 });
341
328
  ```
342
329
 
343
330
  ---
344
331
 
345
- ## Transaction Model Setup
332
+ ## Advanced Features
346
333
 
347
- **ONE Transaction model = Universal Financial Ledger**
334
+ ### State Machines (Data Integrity)
348
335
 
349
- 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).
336
+ Prevent invalid transitions automatically:
350
337
 
351
338
  ```typescript
352
- import mongoose from 'mongoose';
353
- import {
354
- // Enums
355
- TRANSACTION_TYPE_VALUES,
356
- TRANSACTION_STATUS_VALUES,
357
- // Mongoose schemas (compose into your model)
358
- gatewaySchema,
359
- paymentDetailsSchema,
360
- commissionSchema,
361
- holdSchema,
362
- splitSchema,
363
- } from '@classytic/revenue';
339
+ import { TRANSACTION_STATE_MACHINE } from '@classytic/revenue';
364
340
 
365
- // Your app-specific categories
366
- const CATEGORIES = [
367
- 'platform_subscription',
368
- 'course_enrollment',
369
- 'product_order',
370
- 'refund',
371
- 'rent',
372
- 'salary',
373
- 'utilities',
374
- ];
375
-
376
- const transactionSchema = new mongoose.Schema({
377
- // Core fields
378
- organizationId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
379
- customerId: { type: mongoose.Schema.Types.ObjectId, index: true },
380
- type: { type: String, enum: TRANSACTION_TYPE_VALUES, required: true }, // income | expense
381
- category: { type: String, enum: CATEGORIES, index: true },
382
- status: { type: String, enum: TRANSACTION_STATUS_VALUES, default: 'pending' },
383
- amount: { type: Number, required: true, min: 0 },
384
- currency: { type: String, default: 'USD' },
385
- method: { type: String, required: true },
341
+ // Valid
342
+ await revenue.payments.verify(transaction._id); // pending → verified
386
343
 
387
- // Library schemas (compose, don't spread)
388
- gateway: gatewaySchema,
389
- commission: commissionSchema,
390
- paymentDetails: paymentDetailsSchema,
391
- hold: holdSchema,
392
- splits: [splitSchema],
393
-
394
- // Polymorphic reference (link to any entity)
395
- referenceId: { type: mongoose.Schema.Types.ObjectId, refPath: 'referenceModel' },
396
- referenceModel: { type: String, enum: ['Subscription', 'Order', 'Enrollment'] },
397
-
398
- // Idempotency & verification
399
- idempotencyKey: { type: String, unique: true, sparse: true },
400
- verifiedAt: Date,
401
- verifiedBy: mongoose.Schema.Types.Mixed, // ObjectId or 'system'
402
-
403
- // Refunds
404
- refundedAmount: Number,
405
- refundedAt: Date,
406
-
407
- metadata: mongoose.Schema.Types.Mixed,
408
- }, { timestamps: true });
344
+ // Invalid (throws InvalidStateTransitionError)
345
+ await revenue.payments.verify(completedTransaction._id); // completed → verified
409
346
 
410
- export const Transaction = mongoose.model('Transaction', transactionSchema);
347
+ // Check if transition is valid
348
+ const canRefund = TRANSACTION_STATE_MACHINE.canTransition(
349
+ transaction.status,
350
+ 'refunded'
351
+ );
352
+
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
411
359
  ```
412
360
 
413
- ### Available Schemas
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
414
367
 
415
- | Schema | Purpose | Usage |
416
- |--------|---------|-------|
417
- | `gatewaySchema` | Payment gateway details | `gateway: gatewaySchema` |
418
- | `commissionSchema` | Platform commission | `commission: commissionSchema` |
419
- | `paymentDetailsSchema` | Manual payment info | `paymentDetails: paymentDetailsSchema` |
420
- | `holdSchema` | Escrow hold/release | `hold: holdSchema` |
421
- | `splitSchema` | Multi-party splits | `splits: [splitSchema]` |
422
- | `currentPaymentSchema` | For Order/Subscription models | `currentPayment: currentPaymentSchema` |
423
- | `paymentEntrySchema` | Individual payment in split payments | Used within `currentPaymentSchema.payments` |
368
+ ### Audit Trail (Track State Changes)
424
369
 
425
- **Usage:** Import and use as nested objects (NOT spread):
370
+ Every state transition is automatically logged:
426
371
 
427
372
  ```typescript
428
- import { gatewaySchema, commissionSchema } from '@classytic/revenue';
429
-
430
- const schema = new mongoose.Schema({
431
- gateway: gatewaySchema, // Correct - nested
432
- commission: commissionSchema,
433
- // ...gatewaySchema, // ❌ Wrong - don't spread
434
- });
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
+ // ]
435
389
  ```
436
390
 
437
- ---
438
-
439
- ## Group Payments (Split Pay)
391
+ ### Escrow (Marketplaces)
440
392
 
441
- Multiple payers can contribute to one purchase using `referenceId`:
393
+ Hold funds until conditions met:
442
394
 
443
395
  ```typescript
444
- // Order total: $100 (10000 cents)
445
- const orderId = new mongoose.Types.ObjectId();
446
- const orderTotal = 10000;
447
-
448
- // Friend 1 pays $40
449
- await revenue.monetization.create({
450
- data: {
451
- customerId: friend1,
452
- organizationId: restaurantId,
453
- referenceId: orderId,
454
- referenceModel: 'Order',
455
- },
456
- planKey: 'split_payment',
457
- monetizationType: 'purchase',
458
- amount: 4000,
459
- gateway: 'stripe',
460
- metadata: { splitGroup: 'dinner_dec_10' },
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
461
404
  });
462
405
 
463
- // Friend 2 pays $35
464
- await revenue.monetization.create({
465
- data: {
466
- customerId: friend2,
467
- organizationId: restaurantId,
468
- referenceId: orderId,
469
- referenceModel: 'Order',
470
- },
471
- planKey: 'split_payment',
472
- monetizationType: 'purchase',
473
- amount: 3500,
474
- gateway: 'stripe',
475
- metadata: { splitGroup: 'dinner_dec_10' },
476
- });
477
-
478
- // Friend 3 pays $25
479
- await revenue.monetization.create({
480
- data: {
481
- customerId: friend3,
482
- organizationId: restaurantId,
483
- referenceId: orderId,
484
- referenceModel: 'Order',
485
- },
486
- planKey: 'split_payment',
487
- monetizationType: 'purchase',
488
- amount: 2500,
489
- gateway: 'stripe',
490
- metadata: { splitGroup: 'dinner_dec_10' },
406
+ // Release to seller after delivery confirmed
407
+ await revenue.escrow.release(transaction._id, {
408
+ recipientId: 'seller_123',
409
+ recipientType: 'organization',
410
+ reason: 'delivery_confirmed',
491
411
  });
492
412
  ```
493
413
 
494
- ### Check Payment Status
414
+ ### Commission Splits (Affiliates)
415
+
416
+ Split revenue between multiple parties:
495
417
 
496
418
  ```typescript
497
- // Get all contributions for an order
498
- const contributions = await Transaction.find({
499
- referenceId: orderId,
500
- referenceModel: 'Order',
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%
501
425
  });
502
426
 
503
- // Calculate totals
504
- const verified = contributions.filter(t => t.status === 'verified');
505
- const totalPaid = verified.reduce((sum, t) => sum + t.amount, 0);
506
- const remaining = orderTotal - totalPaid;
507
- const isFullyPaid = totalPaid >= orderTotal;
508
-
509
- console.log({
510
- totalPaid, // 10000
511
- remaining, // 0
512
- isFullyPaid, // true
513
- payers: verified.map(t => ({
514
- customerId: t.customerId,
515
- amount: t.amount,
516
- paidAt: t.verifiedAt,
517
- })),
518
- });
427
+ // Creates 3 transactions:
428
+ // - Creator: $70.00
429
+ // - Affiliate: $10.00
430
+ // - Platform: $20.00
519
431
  ```
520
432
 
521
- ### Query by Split Group
433
+ ### Events (React to Changes)
522
434
 
523
435
  ```typescript
524
- // Find all payments in a split group
525
- const groupPayments = await Transaction.find({
526
- 'metadata.splitGroup': 'dinner_dec_10',
527
- });
436
+ import { EventBus } from '@classytic/revenue/events';
437
+
438
+ revenue.events.on('payment:verified', async (event) => {
439
+ // Grant access
440
+ await grantAccess(event.transaction.customerId);
528
441
 
529
- // Pending payers
530
- const pending = await Transaction.find({
531
- referenceId: orderId,
532
- status: 'pending',
442
+ // Send email
443
+ await sendEmail(event.transaction.customerId, 'Payment received!');
533
444
  });
534
- ```
535
445
 
536
- ---
446
+ revenue.events.on('subscription:cancelled', async (event) => {
447
+ await removeAccess(event.subscription.customerId);
448
+ });
537
449
 
538
- ## Multi-Payment Method Support (POS)
450
+ // Other events:
451
+ // - monetization:created, payment:failed, payment:refunded
452
+ // - subscription:activated, subscription:renewed
453
+ // - escrow:held, escrow:released, settlement:completed
454
+ ```
539
455
 
540
- For POS scenarios where customers pay using multiple methods (e.g., cash + bank + mobile wallet):
456
+ ### Tax Plugin (Optional)
541
457
 
542
- ### Schema Structure
458
+ Automatically calculate and track tax:
543
459
 
544
460
  ```typescript
545
- import { currentPaymentSchema, paymentEntrySchema } from '@classytic/revenue';
461
+ import { createTaxPlugin } from '@classytic/revenue/plugins';
546
462
 
547
- // currentPaymentSchema now supports a `payments` array for split payments
548
- const orderSchema = new mongoose.Schema({
549
- currentPayment: currentPaymentSchema,
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
550
479
  // ...
551
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);
552
492
  ```
553
493
 
554
- ### Single Payment (Backward Compatible)
494
+ ### Custom Plugins
555
495
 
556
496
  ```typescript
557
- // Traditional single-method payment
558
- currentPayment: {
559
- amount: 50000, // 500 BDT in paisa
560
- method: 'cash',
561
- status: 'verified',
562
- verifiedAt: new Date(),
563
- verifiedBy: cashierId,
564
- }
497
+ import { definePlugin } from '@classytic/revenue/plugins';
498
+
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
+ });
511
+
512
+ return result;
513
+ },
514
+ },
515
+ });
516
+
517
+ revenue.withPlugin(notificationPlugin);
565
518
  ```
566
519
 
567
- ### Split Payment (Multiple Methods)
520
+ ### Resilience Patterns
521
+
522
+ Built-in retry, circuit breaker, and idempotency:
568
523
 
569
524
  ```typescript
570
- // Customer pays 500 BDT using: 100 cash + 100 bank + 300 bKash
571
- currentPayment: {
572
- amount: 50000, // Total: 500 BDT
573
- method: 'split',
574
- status: 'verified',
575
- payments: [
576
- { method: 'cash', amount: 10000 }, // 100 BDT
577
- { method: 'bank_transfer', amount: 10000, reference: 'TRF123' }, // 100 BDT
578
- { method: 'bkash', amount: 30000, reference: 'TRX456', details: { walletNumber: '01712345678' } }, // 300 BDT
579
- ],
580
- verifiedAt: new Date(),
581
- verifiedBy: cashierId,
582
- }
525
+ // Automatic retry on provider failures
526
+ await revenue.payments.verify(transaction._id);
527
+ // Retries 3x with exponential backoff
528
+
529
+ // Manual idempotency
530
+ import { IdempotencyManager } from '@classytic/revenue';
531
+
532
+ const idem = new IdempotencyManager();
533
+
534
+ const result = await idem.execute(
535
+ 'charge_user_123',
536
+ { amount: 2999 },
537
+ () => revenue.monetization.create({ ... })
538
+ );
539
+
540
+ // Second call returns cached result (no duplicate charge)
583
541
  ```
584
542
 
585
- ### Validation
543
+ ### Money Utilities
544
+
545
+ No floating-point errors. All amounts in smallest currency unit (cents):
586
546
 
587
547
  ```typescript
588
- import {
589
- CurrentPaymentInputSchema,
590
- PaymentEntrySchema,
591
- validateSplitPayments,
592
- safeValidate,
593
- } from '@classytic/revenue';
548
+ import { Money, toSmallestUnit, fromSmallestUnit } from '@classytic/revenue';
594
549
 
595
- // Zod validation (automatically validates totals match)
596
- const result = safeValidate(CurrentPaymentInputSchema, paymentInput);
597
- if (!result.success) {
598
- console.log(result.error.issues); // "Split payments total must equal the transaction amount"
599
- }
550
+ // Create Money instances
551
+ const price = Money.usd(1999); // $19.99
552
+ const euro = Money.of(2999, 'EUR'); // €29.99
600
553
 
601
- // Helper function for runtime validation
602
- const isValid = validateSplitPayments({
603
- amount: 50000,
604
- payments: [
605
- { amount: 10000 },
606
- { amount: 10000 },
607
- { amount: 30000 },
608
- ],
609
- }); // true - totals match
554
+ // Conversions
555
+ toSmallestUnit(19.99, 'USD'); // 1999 cents
556
+ fromSmallestUnit(1999, 'USD'); // 19.99
557
+
558
+ // Arithmetic (immutable)
559
+ const total = price.add(Money.usd(500)); // $24.99
560
+ const discounted = price.multiply(0.9); // $17.99
561
+
562
+ // Fair allocation (handles rounding)
563
+ const [a, b, c] = Money.usd(100).allocate([1, 1, 1]);
564
+ // [34, 33, 33] cents - total = 100 ✓
565
+
566
+ // Formatting
567
+ price.format(); // "$19.99"
610
568
  ```
611
569
 
612
- ### TypeScript Types
570
+ ---
571
+
572
+ ## When to Use What
573
+
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 |
584
+
585
+ ---
586
+
587
+ ## Real-World Example
588
+
589
+ **Course marketplace with affiliates:**
613
590
 
614
591
  ```typescript
615
- import type { PaymentEntry, CurrentPayment } from '@classytic/revenue';
592
+ // 1. Student buys course ($99)
593
+ const { transaction } = await revenue.monetization.create({
594
+ data: {
595
+ organizationId: 'org_123',
596
+ customerId: 'student_456',
597
+ sourceId: enrollmentId,
598
+ sourceModel: 'Enrollment',
599
+ },
600
+ planKey: 'one_time',
601
+ monetizationType: 'purchase',
602
+ entity: 'CourseEnrollment',
603
+ amount: 9900,
604
+ gateway: 'stripe',
605
+ });
616
606
 
617
- const entry: PaymentEntry = {
618
- method: 'bkash',
619
- amount: 30000,
620
- reference: 'TRX456',
621
- details: { walletNumber: '01712345678' },
622
- };
607
+ // 2. Payment verified → Grant course access
608
+ await revenue.payments.verify(transaction._id);
623
609
 
624
- const payment: CurrentPayment = {
625
- amount: 50000,
626
- method: 'split',
627
- status: 'verified',
628
- payments: [entry],
629
- };
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)
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
+ ]);
630
627
  ```
631
628
 
632
629
  ---
633
630
 
634
- ## Building Custom Providers
631
+ ## Submodule Imports
632
+
633
+ Tree-shakable imports for smaller bundles:
635
634
 
636
635
  ```typescript
637
- import { PaymentProvider, PaymentIntent, PaymentResult, RefundResult, WebhookEvent } from '@classytic/revenue';
638
- import type { CreateIntentParams, ProviderCapabilities } from '@classytic/revenue';
636
+ // Plugins
637
+ import { loggingPlugin, auditPlugin, createTaxPlugin } from '@classytic/revenue/plugins';
639
638
 
640
- export class StripeProvider extends PaymentProvider {
641
- public override readonly name = 'stripe';
642
- private stripe: Stripe;
639
+ // Enums
640
+ import { TRANSACTION_STATUS, PAYMENT_STATUS } from '@classytic/revenue/enums';
643
641
 
644
- constructor(config: { apiKey: string }) {
645
- super(config);
646
- this.stripe = new Stripe(config.apiKey);
647
- }
642
+ // Events
643
+ import { EventBus } from '@classytic/revenue/events';
648
644
 
649
- async createIntent(params: CreateIntentParams): Promise<PaymentIntent> {
650
- const intent = await this.stripe.paymentIntents.create({
651
- amount: params.amount,
652
- currency: params.currency ?? 'usd',
653
- metadata: params.metadata,
654
- });
655
-
656
- return new PaymentIntent({
657
- id: intent.id,
658
- paymentIntentId: intent.id,
659
- sessionId: null,
660
- provider: this.name,
661
- status: intent.status,
662
- amount: intent.amount,
663
- currency: intent.currency,
664
- clientSecret: intent.client_secret!,
665
- metadata: params.metadata ?? {},
666
- });
667
- }
645
+ // Schemas (Mongoose)
646
+ import { transactionSchema, subscriptionSchema } from '@classytic/revenue/schemas';
668
647
 
669
- async verifyPayment(intentId: string): Promise<PaymentResult> {
670
- const intent = await this.stripe.paymentIntents.retrieve(intentId);
671
- return new PaymentResult({
672
- id: intent.id,
673
- provider: this.name,
674
- status: intent.status === 'succeeded' ? 'succeeded' : 'failed',
675
- amount: intent.amount,
676
- currency: intent.currency,
677
- paidAt: intent.status === 'succeeded' ? new Date() : undefined,
678
- metadata: {},
679
- });
680
- }
648
+ // Validation (Zod)
649
+ import { CreatePaymentSchema } from '@classytic/revenue/schemas/validation';
681
650
 
682
- async getStatus(intentId: string): Promise<PaymentResult> {
683
- return this.verifyPayment(intentId);
684
- }
685
-
686
- async refund(paymentId: string, amount?: number | null): Promise<RefundResult> {
687
- const refund = await this.stripe.refunds.create({
688
- payment_intent: paymentId,
689
- amount: amount ?? undefined,
690
- });
691
-
692
- return new RefundResult({
693
- id: refund.id,
694
- provider: this.name,
695
- status: refund.status === 'succeeded' ? 'succeeded' : 'failed',
696
- amount: refund.amount,
697
- currency: refund.currency,
698
- refundedAt: new Date(),
699
- metadata: {},
700
- });
701
- }
651
+ // Utilities
652
+ import { retry, calculateCommission } from '@classytic/revenue/utils';
702
653
 
703
- async handleWebhook(payload: unknown, headers?: Record<string, string>): Promise<WebhookEvent> {
704
- const sig = headers?.['stripe-signature'];
705
- const event = this.stripe.webhooks.constructEvent(
706
- payload as string,
707
- sig!,
708
- this.config.webhookSecret as string
709
- );
710
-
711
- return new WebhookEvent({
712
- id: event.id,
713
- provider: this.name,
714
- type: event.type,
715
- data: event.data.object as any,
716
- createdAt: new Date(event.created * 1000),
717
- });
718
- }
654
+ // Reconciliation
655
+ import { reconcileSettlement } from '@classytic/revenue/reconciliation';
719
656
 
720
- override getCapabilities(): ProviderCapabilities {
721
- return {
722
- supportsWebhooks: true,
723
- supportsRefunds: true,
724
- supportsPartialRefunds: true,
725
- requiresManualVerification: false,
726
- };
727
- }
728
- }
657
+ // Services (advanced)
658
+ import { MonetizationService } from '@classytic/revenue/services';
729
659
  ```
730
660
 
731
661
  ---
732
662
 
663
+ ## API Reference
664
+
665
+ ### Services
666
+
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()` |
674
+
675
+ ### State Machines
676
+
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
682
+
683
+ ### Utilities
684
+
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 |
692
+
693
+ ---
694
+
733
695
  ## Error Handling
734
696
 
735
697
  ```typescript
736
698
  import {
737
- RevenueError,
738
- TransactionNotFoundError,
739
- AlreadyVerifiedError,
699
+ PaymentIntentCreationError,
700
+ InvalidStateTransitionError,
701
+ InvalidAmountError,
740
702
  RefundError,
741
- ProviderNotFoundError,
742
- ValidationError,
743
- isRevenueError,
744
- isRetryable,
745
703
  } from '@classytic/revenue';
746
704
 
747
705
  try {
748
- await revenue.payments.verify(id);
706
+ await revenue.monetization.create({ amount: -100 }); // Invalid
749
707
  } catch (error) {
750
- if (error instanceof AlreadyVerifiedError) {
751
- console.log('Already verified:', error.metadata.transactionId);
752
- } else if (error instanceof TransactionNotFoundError) {
753
- console.log('Not found');
754
- } else if (isRevenueError(error) && isRetryable(error)) {
755
- // 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);
756
712
  }
757
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
+ }
758
728
  ```
759
729
 
760
730
  ---
761
731
 
762
- ## TypeScript
732
+ ## TypeScript Support
763
733
 
764
- Full TypeScript support with exported types:
734
+ Full type safety with auto-completion:
765
735
 
766
736
  ```typescript
767
737
  import type {
768
- Revenue,
769
738
  TransactionDocument,
770
739
  SubscriptionDocument,
771
- PaymentProviderInterface,
772
- CreateIntentParams,
773
- ProviderCapabilities,
774
- RevenueEvents,
775
- MonetizationCreateParams,
776
- // Multi-payment types
777
- PaymentEntry,
778
- CurrentPayment,
779
- PaymentEntryInput,
780
- CurrentPaymentInput,
740
+ CommissionInfo,
741
+ RevenueConfig,
781
742
  } from '@classytic/revenue';
743
+
744
+ const transaction: TransactionDocument = await revenue.transactions.get(txId);
745
+ const commission: CommissionInfo = transaction.commission;
782
746
  ```
783
747
 
784
- ### Type Guards
748
+ ---
749
+
750
+ ## Examples
751
+
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
758
+
759
+ ---
785
760
 
786
- Runtime type checking for all enum values:
761
+ ## Built-in Plugins
787
762
 
788
763
  ```typescript
789
764
  import {
790
- isTransactionType,
791
- isTransactionStatus,
792
- isPaymentStatus,
793
- isSubscriptionStatus,
794
- isMonetizationType,
795
- isHoldStatus,
796
- isSplitType,
797
- } from '@classytic/revenue';
765
+ loggingPlugin,
766
+ auditPlugin,
767
+ metricsPlugin,
768
+ createTaxPlugin
769
+ } from '@classytic/revenue/plugins';
798
770
 
799
- // Validate and narrow types at runtime
800
- if (isTransactionStatus(userInput)) {
801
- // userInput is narrowed to TransactionStatusValue
802
- console.log('Valid status:', userInput);
803
- }
804
-
805
- // Useful for API input validation
806
- function processPayment(status: unknown) {
807
- if (!isPaymentStatus(status)) {
808
- throw new Error('Invalid payment status');
809
- }
810
- // status is now typed as PaymentStatusValue
811
- }
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({ ... }));
812
784
  ```
813
785
 
814
- **Available type guards:**
815
-
816
- | Guard | Validates |
817
- |-------|-----------|
818
- | `isTransactionType` | `'income'` \| `'expense'` |
819
- | `isTransactionStatus` | `'pending'` \| `'verified'` \| `'completed'` \| ... |
820
- | `isLibraryCategory` | `'subscription'` \| `'purchase'` |
821
- | `isPaymentStatus` | `'pending'` \| `'succeeded'` \| `'failed'` \| ... |
822
- | `isPaymentGatewayType` | `'manual'` \| `'automatic'` |
823
- | `isGatewayType` | `'redirect'` \| `'direct'` \| `'webhook'` |
824
- | `isSubscriptionStatus` | `'active'` \| `'paused'` \| `'cancelled'` \| ... |
825
- | `isPlanKey` | `'monthly'` \| `'yearly'` \| `'one_time'` \| ... |
826
- | `isMonetizationType` | `'subscription'` \| `'purchase'` |
827
- | `isHoldStatus` | `'held'` \| `'released'` \| `'partially_released'` \| ... |
828
- | `isReleaseReason` | `'completed'` \| `'cancelled'` \| `'refunded'` \| ... |
829
- | `isHoldReason` | `'escrow'` \| `'dispute'` \| `'verification'` \| ... |
830
- | `isSplitType` | `'platform_commission'` \| `'affiliate_commission'` \| ... |
831
- | `isSplitStatus` | `'pending'` \| `'processed'` \| `'failed'` |
832
- | `isPayoutMethod` | `'bank_transfer'` \| `'wallet'` \| `'manual'` |
833
-
834
786
  ---
835
787
 
836
- ## Testing
788
+ ## Contributing
837
789
 
838
- ```bash
839
- # Run all tests (196 tests)
840
- npm test
790
+ Contributions welcome! Open an issue or submit a pull request on [GitHub](https://github.com/classytic/revenue).
841
791
 
842
- # Run integration tests (requires MongoDB)
843
- npm test -- tests/integration/
792
+ ---
844
793
 
845
- # Watch mode
846
- npm run test:watch
794
+ ## License
847
795
 
848
- # Coverage
849
- npm run test:coverage
850
- ```
796
+ MIT © [Classytic](https://github.com/classytic)
851
797
 
852
798
  ---
853
799
 
854
- ## License
800
+ ## Support
855
801
 
856
- MIT © [Classytic](https://github.com/classytic)
802
+ - 📖 [Documentation](https://github.com/classytic/revenue#readme)
803
+ - 🐛 [Issues](https://github.com/classytic/revenue/issues)
804
+ - 💬 [Discussions](https://github.com/classytic/revenue/discussions)