@classytic/revenue 1.1.2 → 1.1.4

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 (89) hide show
  1. package/README.md +8 -7
  2. package/dist/application/services/index.d.mts +4 -0
  3. package/dist/application/services/index.mjs +3 -0
  4. package/dist/base-CsTlVQJe.d.mts +136 -0
  5. package/dist/base-DCoyIUj6.mjs +152 -0
  6. package/dist/category-resolver-DV83N8ok.mjs +284 -0
  7. package/dist/commission-split-BzB8cd39.mjs +485 -0
  8. package/dist/core/events.d.mts +294 -0
  9. package/dist/core/events.mjs +100 -0
  10. package/dist/core/index.d.mts +9 -0
  11. package/dist/core/index.mjs +8 -0
  12. package/dist/enums/index.d.mts +157 -0
  13. package/dist/enums/index.mjs +56 -0
  14. package/dist/errors-rRdOqnWx.d.mts +787 -0
  15. package/dist/escrow.enums-CZGrrdg7.mjs +101 -0
  16. package/dist/{escrow.enums-CE0VQsfe.d.ts → escrow.enums-DwdLuuve.d.mts} +30 -28
  17. package/dist/idempotency-DaYcUGY1.mjs +172 -0
  18. package/dist/index-Dsp7H5Wb.d.mts +471 -0
  19. package/dist/index.d.mts +9 -0
  20. package/dist/index.mjs +38 -0
  21. package/dist/infrastructure/plugins/{index.d.ts → index.d.mts} +81 -109
  22. package/dist/infrastructure/plugins/index.mjs +345 -0
  23. package/dist/money-CvrDOijQ.mjs +271 -0
  24. package/dist/money-DPG8AtJ8.d.mts +112 -0
  25. package/dist/{payment.enums-C1BiGlRa.d.ts → payment.enums-HAuAS9Pp.d.mts} +14 -13
  26. package/dist/payment.enums-tEFVa-Xp.mjs +69 -0
  27. package/dist/plugin-BbK0OVHy.d.mts +327 -0
  28. package/dist/plugin-Cd_V04Em.mjs +210 -0
  29. package/dist/providers/index.d.mts +3 -0
  30. package/dist/providers/index.mjs +3 -0
  31. package/dist/reconciliation/{index.d.ts → index.d.mts} +90 -112
  32. package/dist/reconciliation/index.mjs +192 -0
  33. package/dist/retry-HHCOXYdn.d.mts +186 -0
  34. package/dist/revenue-BhdS7nXh.mjs +553 -0
  35. package/dist/schemas/index.d.mts +2665 -0
  36. package/dist/schemas/index.mjs +717 -0
  37. package/dist/schemas/validation.d.mts +375 -0
  38. package/dist/schemas/validation.mjs +325 -0
  39. package/dist/{settlement.enums-ByC1x0ye.d.ts → settlement.enums-DFhkqZEY.d.mts} +31 -29
  40. package/dist/settlement.schema-DnNSFpGd.d.mts +344 -0
  41. package/dist/settlement.service-DjzAjezU.d.mts +594 -0
  42. package/dist/settlement.service-DmdKv0Zu.mjs +2511 -0
  43. package/dist/split.enums-BrjabxIX.mjs +86 -0
  44. package/dist/split.enums-DmskfLOM.d.mts +43 -0
  45. package/dist/tax-BoCt5cEd.d.mts +61 -0
  46. package/dist/tax-EQ15DO81.mjs +162 -0
  47. package/dist/transaction.enums-pCyMFT4Z.mjs +96 -0
  48. package/dist/utils/{index.d.ts → index.d.mts} +91 -161
  49. package/dist/utils/index.mjs +346 -0
  50. package/package.json +39 -37
  51. package/dist/application/services/index.d.ts +0 -6
  52. package/dist/application/services/index.js +0 -3288
  53. package/dist/application/services/index.js.map +0 -1
  54. package/dist/core/events.d.ts +0 -455
  55. package/dist/core/events.js +0 -122
  56. package/dist/core/events.js.map +0 -1
  57. package/dist/core/index.d.ts +0 -13
  58. package/dist/core/index.js +0 -4591
  59. package/dist/core/index.js.map +0 -1
  60. package/dist/enums/index.d.ts +0 -159
  61. package/dist/enums/index.js +0 -296
  62. package/dist/enums/index.js.map +0 -1
  63. package/dist/index-DxIK0UmZ.d.ts +0 -633
  64. package/dist/index-EnfKzDbs.d.ts +0 -806
  65. package/dist/index-cLJBLUvx.d.ts +0 -478
  66. package/dist/index.d.ts +0 -43
  67. package/dist/index.js +0 -4864
  68. package/dist/index.js.map +0 -1
  69. package/dist/infrastructure/plugins/index.js +0 -292
  70. package/dist/infrastructure/plugins/index.js.map +0 -1
  71. package/dist/money-widWVD7r.d.ts +0 -111
  72. package/dist/plugin-Bb9HOE10.d.ts +0 -336
  73. package/dist/providers/index.d.ts +0 -145
  74. package/dist/providers/index.js +0 -141
  75. package/dist/providers/index.js.map +0 -1
  76. package/dist/reconciliation/index.js +0 -140
  77. package/dist/reconciliation/index.js.map +0 -1
  78. package/dist/retry-D4hFUwVk.d.ts +0 -194
  79. package/dist/schemas/index.d.ts +0 -2655
  80. package/dist/schemas/index.js +0 -841
  81. package/dist/schemas/index.js.map +0 -1
  82. package/dist/schemas/validation.d.ts +0 -384
  83. package/dist/schemas/validation.js +0 -303
  84. package/dist/schemas/validation.js.map +0 -1
  85. package/dist/settlement.schema-CpamV7ZY.d.ts +0 -343
  86. package/dist/split.enums-DG3TxQf9.d.ts +0 -42
  87. package/dist/tax-CV8A0sxl.d.ts +0 -60
  88. package/dist/utils/index.js +0 -1202
  89. package/dist/utils/index.js.map +0 -1
@@ -1,292 +0,0 @@
1
- // @classytic/revenue - Enterprise Revenue Management System
2
-
3
- // src/core/plugin.ts
4
- function definePlugin(plugin) {
5
- return plugin;
6
- }
7
-
8
- // src/infrastructure/plugins/business/logging.plugin.ts
9
- function loggingPlugin(options = {}) {
10
- const level = options.level ?? "info";
11
- return definePlugin({
12
- name: "logging",
13
- version: "1.0.0",
14
- description: "Logs all revenue operations",
15
- hooks: {
16
- "payment.create.after": async (ctx, input, next) => {
17
- ctx.logger[level]("Creating payment", { amount: input.amount, currency: input.currency });
18
- const result = await next();
19
- ctx.logger[level]("Payment created", { paymentIntentId: result?.paymentIntentId });
20
- return result;
21
- },
22
- "payment.verify.after": async (ctx, input, next) => {
23
- ctx.logger[level]("Verifying payment", { id: input.id });
24
- const result = await next();
25
- ctx.logger[level]("Payment verified", { verified: result?.verified });
26
- return result;
27
- },
28
- "payment.refund.after": async (ctx, input, next) => {
29
- ctx.logger[level]("Processing refund", { transactionId: input.transactionId, amount: input.amount });
30
- const result = await next();
31
- ctx.logger[level]("Refund processed", { refundId: result?.refundId });
32
- return result;
33
- }
34
- }
35
- });
36
- }
37
-
38
- // src/infrastructure/plugins/business/audit.plugin.ts
39
- function sanitizeInput(input) {
40
- if (typeof input !== "object" || !input) return {};
41
- const sanitized = { ...input };
42
- delete sanitized.apiKey;
43
- delete sanitized.secretKey;
44
- delete sanitized.password;
45
- return sanitized;
46
- }
47
- function sanitizeOutput(output) {
48
- if (typeof output !== "object" || !output) return {};
49
- return { ...output };
50
- }
51
- function auditPlugin(options = {}) {
52
- const store = options.store ?? (async (entry) => {
53
- });
54
- return definePlugin({
55
- name: "audit",
56
- version: "1.0.0",
57
- description: "Audit trail for all operations",
58
- hooks: {
59
- "payment.create.after": async (ctx, input, next) => {
60
- const result = await next();
61
- await store({
62
- action: "payment.create",
63
- requestId: ctx.meta.requestId,
64
- timestamp: ctx.meta.timestamp,
65
- input: sanitizeInput(input),
66
- output: sanitizeOutput(result),
67
- idempotencyKey: ctx.meta.idempotencyKey
68
- });
69
- return result;
70
- },
71
- "payment.refund.after": async (ctx, input, next) => {
72
- const result = await next();
73
- await store({
74
- action: "payment.refund",
75
- requestId: ctx.meta.requestId,
76
- timestamp: ctx.meta.timestamp,
77
- input: sanitizeInput(input),
78
- output: sanitizeOutput(result),
79
- idempotencyKey: ctx.meta.idempotencyKey
80
- });
81
- return result;
82
- }
83
- }
84
- });
85
- }
86
-
87
- // src/infrastructure/plugins/business/metrics.plugin.ts
88
- function metricsPlugin(options = {}) {
89
- const record = options.onMetric ?? ((metric) => {
90
- });
91
- return definePlugin({
92
- name: "metrics",
93
- version: "1.0.0",
94
- description: "Collects operation metrics",
95
- hooks: {
96
- "payment.create.before": async (_ctx, input, next) => {
97
- const start = Date.now();
98
- try {
99
- const result = await next();
100
- record({
101
- name: "payment.create",
102
- duration: Date.now() - start,
103
- success: true,
104
- amount: input.amount,
105
- currency: input.currency
106
- });
107
- return result;
108
- } catch (error) {
109
- record({
110
- name: "payment.create",
111
- duration: Date.now() - start,
112
- success: false,
113
- error: error.message
114
- });
115
- throw error;
116
- }
117
- }
118
- }
119
- });
120
- }
121
-
122
- // src/shared/utils/calculators/tax.ts
123
- function calculateTax(amount, category, config) {
124
- if (!config?.isRegistered || config.exemptCategories?.includes(category)) {
125
- return {
126
- isApplicable: false,
127
- rate: 0,
128
- baseAmount: amount,
129
- taxAmount: 0,
130
- totalAmount: amount,
131
- pricesIncludeTax: false
132
- };
133
- }
134
- const rate = config.defaultRate;
135
- const [baseAmount, taxAmount, totalAmount] = config.pricesIncludeTax ? [
136
- // Tax-inclusive: extract tax from total
137
- Math.round(amount / (1 + rate)),
138
- // baseAmount
139
- Math.round(amount - amount / (1 + rate)),
140
- // taxAmount
141
- amount
142
- // totalAmount (already integer)
143
- ] : [
144
- // Tax-exclusive: add tax to base
145
- amount,
146
- // baseAmount (already integer)
147
- Math.round(amount * rate),
148
- // taxAmount
149
- Math.round(amount * (1 + rate))
150
- // totalAmount
151
- ];
152
- return {
153
- isApplicable: true,
154
- rate,
155
- baseAmount,
156
- taxAmount,
157
- totalAmount,
158
- pricesIncludeTax: config.pricesIncludeTax
159
- };
160
- }
161
- function getTaxType(transactionFlow, category, exemptCategories = []) {
162
- if (exemptCategories.includes(category)) {
163
- return "exempt";
164
- }
165
- return transactionFlow === "inflow" ? "collected" : "paid";
166
- }
167
-
168
- // src/enums/transaction.enums.ts
169
- var TRANSACTION_FLOW = {
170
- INFLOW: "inflow",
171
- OUTFLOW: "outflow"
172
- };
173
- var TRANSACTION_FLOW_VALUES = Object.values(
174
- TRANSACTION_FLOW
175
- );
176
- var TRANSACTION_STATUS = {
177
- PENDING: "pending",
178
- PAYMENT_INITIATED: "payment_initiated",
179
- PROCESSING: "processing",
180
- REQUIRES_ACTION: "requires_action",
181
- VERIFIED: "verified",
182
- COMPLETED: "completed",
183
- FAILED: "failed",
184
- CANCELLED: "cancelled",
185
- EXPIRED: "expired",
186
- REFUNDED: "refunded",
187
- PARTIALLY_REFUNDED: "partially_refunded"
188
- };
189
- var TRANSACTION_STATUS_VALUES = Object.values(
190
- TRANSACTION_STATUS
191
- );
192
- var LIBRARY_CATEGORIES = {
193
- SUBSCRIPTION: "subscription",
194
- PURCHASE: "purchase"
195
- };
196
- var LIBRARY_CATEGORY_VALUES = Object.values(
197
- LIBRARY_CATEGORIES
198
- );
199
- new Set(TRANSACTION_FLOW_VALUES);
200
- new Set(
201
- TRANSACTION_STATUS_VALUES
202
- );
203
- new Set(LIBRARY_CATEGORY_VALUES);
204
-
205
- // src/shared/utils/validators/category-resolver.ts
206
- function resolveCategory(entity, monetizationType, categoryMappings = {}) {
207
- if (entity && categoryMappings[entity]) {
208
- return categoryMappings[entity];
209
- }
210
- switch (monetizationType) {
211
- case "subscription":
212
- return LIBRARY_CATEGORIES.SUBSCRIPTION;
213
- // 'subscription'
214
- case "purchase":
215
- return LIBRARY_CATEGORIES.PURCHASE;
216
- // 'purchase'
217
- default:
218
- return LIBRARY_CATEGORIES.SUBSCRIPTION;
219
- }
220
- }
221
-
222
- // src/infrastructure/plugins/business/tax.plugin.ts
223
- function createTaxPlugin(options) {
224
- const {
225
- getTaxConfig,
226
- categoryMappings = {},
227
- incomeCategories = ["subscription", "purchase", "course_enrollment", "product_order"]
228
- } = options;
229
- return definePlugin({
230
- name: "tax",
231
- version: "1.0.0",
232
- description: "Automatic tax calculation for transactions",
233
- hooks: {
234
- /**
235
- * Calculate tax before monetization creation
236
- * Injects tax data into the input so it's saved with the transaction
237
- */
238
- "monetization.create.before": async (ctx, input, next) => {
239
- const orgId = input.data.organizationId;
240
- if (!orgId) {
241
- ctx.logger.debug("Tax plugin: No organizationId in input.data, skipping tax calculation");
242
- return next();
243
- }
244
- try {
245
- const config = await getTaxConfig(orgId);
246
- if (!config) {
247
- ctx.logger.debug("Tax plugin: No tax config for org", { orgId });
248
- return next();
249
- }
250
- const category = resolveCategory(
251
- input.entity,
252
- input.monetizationType || "subscription",
253
- categoryMappings
254
- );
255
- const transactionFlow = incomeCategories.includes(category) ? "inflow" : "outflow";
256
- const taxCalc = calculateTax(input.amount, category, config);
257
- const taxType = getTaxType(transactionFlow, category, config.exemptCategories);
258
- input.tax = {
259
- ...taxCalc,
260
- type: taxType
261
- };
262
- ctx.logger.debug("Tax plugin: Tax calculated", {
263
- orgId,
264
- category,
265
- entity: input.entity,
266
- monetizationType: input.monetizationType,
267
- taxAmount: taxCalc.taxAmount,
268
- type: taxType
269
- });
270
- } catch (error) {
271
- ctx.logger.error("Tax plugin: Failed to calculate tax", {
272
- orgId,
273
- error: error.message
274
- });
275
- }
276
- return next();
277
- }
278
- }
279
- });
280
- }
281
-
282
- // src/infrastructure/plugins/index.ts
283
- var plugins_default = {
284
- loggingPlugin,
285
- auditPlugin,
286
- metricsPlugin,
287
- createTaxPlugin
288
- };
289
-
290
- export { auditPlugin, createTaxPlugin, plugins_default as default, definePlugin, loggingPlugin, metricsPlugin };
291
- //# sourceMappingURL=index.js.map
292
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/core/plugin.ts","../../../src/infrastructure/plugins/business/logging.plugin.ts","../../../src/infrastructure/plugins/business/audit.plugin.ts","../../../src/infrastructure/plugins/business/metrics.plugin.ts","../../../src/shared/utils/calculators/tax.ts","../../../src/enums/transaction.enums.ts","../../../src/shared/utils/validators/category-resolver.ts","../../../src/infrastructure/plugins/business/tax.plugin.ts","../../../src/infrastructure/plugins/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Plugin System\n * @classytic/revenue\n *\n * Composable, type-safe plugin architecture\n * Inspired by: Hono middleware, Fastify plugins, Redux middleware\n */\n\nimport type { EventBus, RevenueEvents } from './events.js';\n\n// ============ PLUGIN TYPES ============\n\n/**\n * Plugin context passed to hooks\n */\nexport interface PluginContext {\n /** Event bus for emitting events */\n events: EventBus;\n /** Logger instance */\n logger: PluginLogger;\n /** Plugin-specific storage (use this to share data between hook calls) */\n storage: Map<string, unknown>;\n /** Request metadata */\n meta: {\n idempotencyKey?: string;\n requestId: string;\n timestamp: Date;\n [key: string]: unknown;\n };\n}\n\n/**\n * Plugin logger interface\n */\nexport interface PluginLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n}\n\n/**\n * Hook function type\n */\nexport type HookFn<TInput = unknown, TOutput = unknown> = (\n ctx: PluginContext,\n input: TInput,\n next: () => Promise<TOutput>\n) => Promise<TOutput>;\n\n/**\n * Available hook points\n */\nexport interface PluginHooks {\n // Monetization hooks (NEW - for tax plugin and others)\n 'monetization.create.before': HookFn<MonetizationCreateInput>;\n 'monetization.create.after': HookFn<MonetizationCreateInput, MonetizationCreateOutput>;\n\n // Payment hooks\n 'payment.create.before': HookFn<PaymentCreateInput>;\n 'payment.create.after': HookFn<PaymentCreateInput, PaymentCreateOutput>;\n 'payment.verify.before': HookFn<PaymentVerifyInput>;\n 'payment.verify.after': HookFn<PaymentVerifyInput, PaymentVerifyOutput>;\n 'payment.refund.before': HookFn<RefundInput>;\n 'payment.refund.after': HookFn<RefundInput, RefundOutput>;\n\n // Subscription hooks\n 'subscription.create.before': HookFn<SubscriptionCreateInput>;\n 'subscription.create.after': HookFn<SubscriptionCreateInput, SubscriptionCreateOutput>;\n 'subscription.activate.before': HookFn<SubscriptionActivateInput>;\n 'subscription.activate.after': HookFn<SubscriptionActivateInput, SubscriptionActivateOutput>;\n 'subscription.cancel.before': HookFn<SubscriptionCancelInput>;\n 'subscription.cancel.after': HookFn<SubscriptionCancelInput, SubscriptionCancelOutput>;\n 'subscription.pause.before': HookFn<SubscriptionPauseInput>;\n 'subscription.pause.after': HookFn<SubscriptionPauseInput, SubscriptionPauseOutput>;\n 'subscription.resume.before': HookFn<SubscriptionResumeInput>;\n 'subscription.resume.after': HookFn<SubscriptionResumeInput, SubscriptionResumeOutput>;\n\n // Transaction hooks\n 'transaction.create.before': HookFn<TransactionCreateInput>;\n 'transaction.create.after': HookFn<TransactionCreateInput, TransactionCreateOutput>;\n 'transaction.update.before': HookFn<TransactionUpdateInput>;\n 'transaction.update.after': HookFn<TransactionUpdateInput, TransactionUpdateOutput>;\n\n // Escrow hooks\n 'escrow.hold.before': HookFn<EscrowHoldInput>;\n 'escrow.hold.after': HookFn<EscrowHoldInput, EscrowHoldOutput>;\n 'escrow.release.before': HookFn<EscrowReleaseInput>;\n 'escrow.release.after': HookFn<EscrowReleaseInput, EscrowReleaseOutput>;\n}\n\n/**\n * Clean, explicit hook input/output types\n * Self-documenting and fully type-safe - no more `as any` needed!\n */\n\n/**\n * Data passed to monetization.create hooks\n * Includes all parameters plus tax if injected by tax plugin\n */\nexport interface MonetizationCreateInput {\n data: {\n organizationId?: string;\n customerId?: string;\n sourceId?: string;\n sourceModel?: string;\n };\n planKey: string;\n amount: number;\n currency?: string;\n gateway?: string;\n entity?: string | null;\n monetizationType?: 'subscription' | 'purchase' | 'free';\n paymentData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n idempotencyKey?: string | null;\n // Tax injected by tax plugin (optional)\n tax?: {\n isApplicable: boolean;\n rate: number;\n baseAmount: number;\n taxAmount: number;\n totalAmount: number;\n pricesIncludeTax: boolean;\n type: 'collected' | 'paid' | 'exempt';\n };\n}\n\nexport interface MonetizationCreateOutput {\n transactionId?: string;\n subscriptionId?: string;\n}\n\n/**\n * Data passed to payment.create hooks\n */\nexport interface PaymentCreateInput {\n transactionId: string;\n amount: number;\n currency: string;\n gateway: string;\n paymentData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PaymentCreateOutput {\n paymentIntentId: string;\n clientSecret?: string;\n}\n\nexport interface PaymentVerifyInput {\n id: string;\n verifiedBy?: string;\n}\n\nexport interface PaymentVerifyOutput {\n verified: boolean;\n}\n\nexport interface RefundInput {\n transactionId: string;\n amount?: number;\n reason?: string;\n}\n\nexport interface RefundOutput {\n refundId: string;\n}\n\n/**\n * Data passed to subscription.create hooks\n * Note: subscriptionId is undefined in .before hook (not yet created)\n * and populated in .after hook (already created)\n */\nexport interface SubscriptionCreateInput {\n subscriptionId?: string;\n planKey: string;\n customerId?: string;\n organizationId?: string;\n entity?: string | null;\n}\n\nexport interface SubscriptionCreateOutput {\n subscription: unknown;\n transaction?: unknown;\n}\n\n/**\n * Data passed to subscription.activate hooks\n */\nexport interface SubscriptionActivateInput {\n subscriptionId: string;\n transactionId?: string;\n activatedAt?: Date;\n}\n\nexport interface SubscriptionActivateOutput {\n activated: boolean;\n activatedAt: Date;\n}\n\n/**\n * Data passed to subscription.cancel hooks\n */\nexport interface SubscriptionCancelInput {\n subscriptionId: string;\n immediate?: boolean;\n reason?: string;\n}\n\nexport interface SubscriptionCancelOutput {\n cancelled: boolean;\n effectiveDate?: Date;\n}\n\n/**\n * Data passed to subscription.pause hooks\n */\nexport interface SubscriptionPauseInput {\n subscriptionId: string;\n reason?: string;\n}\n\nexport interface SubscriptionPauseOutput {\n paused: boolean;\n pausedAt: Date;\n}\n\n/**\n * Data passed to subscription.resume hooks\n */\nexport interface SubscriptionResumeInput {\n subscriptionId: string;\n extendPeriod?: boolean;\n}\n\nexport interface SubscriptionResumeOutput {\n resumed: boolean;\n resumedAt: Date;\n}\n\n/**\n * Data passed to transaction.create hooks\n */\nexport interface TransactionCreateInput {\n amount: number;\n currency: string;\n type: string;\n organizationId?: string;\n customerId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface TransactionCreateOutput {\n transactionId: string;\n}\n\nexport interface TransactionUpdateInput {\n transactionId: string;\n updates: Record<string, unknown>;\n}\n\nexport interface TransactionUpdateOutput {\n transaction: unknown;\n}\n\nexport interface EscrowHoldInput {\n transactionId: string;\n reason?: string;\n}\n\nexport interface EscrowHoldOutput {\n held: boolean;\n}\n\nexport interface EscrowReleaseInput {\n transactionId: string;\n recipientId?: string;\n recipientType?: string;\n}\n\nexport interface EscrowReleaseOutput {\n released: boolean;\n}\n\n/**\n * Plugin definition\n */\nexport interface RevenuePlugin {\n /** Unique plugin name */\n name: string;\n /** Plugin version */\n version?: string;\n /** Plugin description */\n description?: string;\n /** Dependencies on other plugins */\n dependencies?: string[];\n /** Hook implementations */\n hooks?: Partial<PluginHooks>;\n /** Event listeners */\n events?: Partial<{\n [K in keyof RevenueEvents]: (event: RevenueEvents[K]) => void | Promise<void>;\n }>;\n /** Initialize plugin */\n init?: (ctx: PluginContext) => void | Promise<void>;\n /** Cleanup plugin */\n destroy?: () => void | Promise<void>;\n}\n\n// ============ PLUGIN MANAGER ============\n\n/**\n * Plugin manager - handles registration and execution\n */\nexport class PluginManager {\n private plugins = new Map<string, RevenuePlugin>();\n private hooks = new Map<string, HookFn[]>();\n private initialized = false;\n\n /**\n * Register a plugin\n */\n register(plugin: RevenuePlugin): this {\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already registered`);\n }\n\n // Check dependencies\n if (plugin.dependencies) {\n for (const dep of plugin.dependencies) {\n if (!this.plugins.has(dep)) {\n throw new Error(\n `Plugin \"${plugin.name}\" requires \"${dep}\" to be registered first`\n );\n }\n }\n }\n\n this.plugins.set(plugin.name, plugin);\n\n // Register hooks\n if (plugin.hooks) {\n for (const [hookName, hookFn] of Object.entries(plugin.hooks)) {\n if (!this.hooks.has(hookName)) {\n this.hooks.set(hookName, []);\n }\n this.hooks.get(hookName)!.push(hookFn as HookFn);\n }\n }\n\n return this;\n }\n\n /**\n * Initialize all plugins\n */\n async init(ctx: PluginContext): Promise<void> {\n if (this.initialized) return;\n\n for (const plugin of this.plugins.values()) {\n if (plugin.init) {\n await plugin.init(ctx);\n }\n\n // Register event listeners\n if (plugin.events) {\n for (const [event, handler] of Object.entries(plugin.events)) {\n ctx.events.on(event as keyof RevenueEvents, handler as any);\n }\n }\n }\n\n this.initialized = true;\n }\n\n /**\n * Execute a hook chain\n */\n async executeHook<TInput, TOutput>(\n hookName: string,\n ctx: PluginContext,\n input: TInput,\n execute: () => Promise<TOutput>\n ): Promise<TOutput> {\n const hooks = this.hooks.get(hookName) ?? [];\n \n if (hooks.length === 0) {\n return execute();\n }\n\n // Build middleware chain\n let index = 0;\n const next = async (): Promise<TOutput> => {\n if (index >= hooks.length) {\n return execute();\n }\n const hook = hooks[index++];\n return hook(ctx, input, next) as Promise<TOutput>;\n };\n\n return next();\n }\n\n /**\n * Check if plugin is registered\n */\n has(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Get a plugin by name\n */\n get(name: string): RevenuePlugin | undefined {\n return this.plugins.get(name);\n }\n\n /**\n * Get all registered plugins\n */\n list(): RevenuePlugin[] {\n return Array.from(this.plugins.values());\n }\n\n /**\n * Destroy all plugins\n */\n async destroy(): Promise<void> {\n for (const plugin of this.plugins.values()) {\n if (plugin.destroy) {\n await plugin.destroy();\n }\n }\n this.plugins.clear();\n this.hooks.clear();\n this.initialized = false;\n }\n}\n\n// ============ BUILT-IN PLUGINS ============\n\n/**\n * Logging plugin - logs all operations\n */\nexport function loggingPlugin(options: { level?: 'debug' | 'info' } = {}): RevenuePlugin {\n const level = options.level ?? 'info';\n \n return {\n name: 'logging',\n version: '1.0.0',\n description: 'Logs all revenue operations',\n hooks: {\n 'payment.create.after': async (ctx, input, next) => {\n ctx.logger[level]('Creating payment', { amount: input.amount, currency: input.currency });\n const result = await next();\n ctx.logger[level]('Payment created', { paymentIntentId: result?.paymentIntentId });\n return result;\n },\n 'payment.verify.after': async (ctx, input, next) => {\n ctx.logger[level]('Verifying payment', { id: input.id });\n const result = await next();\n ctx.logger[level]('Payment verified', { verified: result?.verified });\n return result;\n },\n 'payment.refund.after': async (ctx, input, next) => {\n ctx.logger[level]('Processing refund', { transactionId: input.transactionId, amount: input.amount });\n const result = await next();\n ctx.logger[level]('Refund processed', { refundId: result?.refundId });\n return result;\n },\n },\n };\n}\n\n/**\n * Audit plugin - records all operations for compliance\n */\nexport function auditPlugin(options: { \n store?: (entry: AuditEntry) => Promise<void> \n} = {}): RevenuePlugin {\n const entries: AuditEntry[] = [];\n \n const store = options.store ?? (async (entry: AuditEntry) => {\n entries.push(entry);\n });\n\n return {\n name: 'audit',\n version: '1.0.0',\n description: 'Audit trail for all operations',\n hooks: {\n 'payment.create.after': async (ctx, input, next) => {\n const result = await next();\n await store({\n action: 'payment.create',\n requestId: ctx.meta.requestId,\n timestamp: ctx.meta.timestamp,\n input: sanitizeInput(input),\n output: sanitizeOutput(result),\n idempotencyKey: ctx.meta.idempotencyKey,\n });\n return result;\n },\n 'payment.refund.after': async (ctx, input, next) => {\n const result = await next();\n await store({\n action: 'payment.refund',\n requestId: ctx.meta.requestId,\n timestamp: ctx.meta.timestamp,\n input: sanitizeInput(input),\n output: sanitizeOutput(result),\n idempotencyKey: ctx.meta.idempotencyKey,\n });\n return result;\n },\n },\n };\n}\n\ninterface AuditEntry {\n action: string;\n requestId: string;\n timestamp: Date;\n input: Record<string, unknown>;\n output: Record<string, unknown>;\n idempotencyKey?: string;\n}\n\nfunction sanitizeInput(input: unknown): Record<string, unknown> {\n if (typeof input !== 'object' || !input) return {};\n const sanitized = { ...input } as Record<string, unknown>;\n // Remove sensitive fields\n delete sanitized.apiKey;\n delete sanitized.secretKey;\n delete sanitized.password;\n return sanitized;\n}\n\nfunction sanitizeOutput(output: unknown): Record<string, unknown> {\n if (typeof output !== 'object' || !output) return {};\n return { ...output } as Record<string, unknown>;\n}\n\n/**\n * Metrics plugin - collects operation metrics\n */\nexport function metricsPlugin(options: {\n onMetric?: (metric: Metric) => void;\n} = {}): RevenuePlugin {\n const metrics: Metric[] = [];\n \n const record = options.onMetric ?? ((metric: Metric) => {\n metrics.push(metric);\n });\n\n return {\n name: 'metrics',\n version: '1.0.0',\n description: 'Collects operation metrics',\n hooks: {\n 'payment.create.before': async (_ctx, input, next) => {\n const start = Date.now();\n try {\n const result = await next();\n record({\n name: 'payment.create',\n duration: Date.now() - start,\n success: true,\n amount: input.amount,\n currency: input.currency,\n });\n return result;\n } catch (error) {\n record({\n name: 'payment.create',\n duration: Date.now() - start,\n success: false,\n error: (error as Error).message,\n });\n throw error;\n }\n },\n },\n };\n}\n\ninterface Metric {\n name: string;\n duration: number;\n success: boolean;\n error?: string;\n [key: string]: unknown;\n}\n\n/**\n * Create a custom plugin\n */\nexport function definePlugin(plugin: RevenuePlugin): RevenuePlugin {\n return plugin;\n}\n\nexport default PluginManager;\n\n","/**\n * Logging Plugin\n * @classytic/revenue\n *\n * Logs all revenue operations at specified log level\n */\n\nimport { definePlugin, type RevenuePlugin } from '../../../core/plugin.js';\n\n/**\n * Logging plugin options\n */\nexport interface LoggingPluginOptions {\n /** Log level: 'debug' or 'info' */\n level?: 'debug' | 'info';\n}\n\n/**\n * Logging plugin - logs all operations\n *\n * Logs payment creation, verification, and refund operations\n *\n * @param options - Plugin options\n * @returns Logging plugin\n *\n * @example\n * ```typescript\n * import { Revenue } from '@classytic/revenue';\n * import { loggingPlugin } from '@classytic/revenue/plugins';\n *\n * const revenue = Revenue\n * .create()\n * .withPlugin(loggingPlugin({ level: 'debug' }))\n * .build();\n * ```\n */\nexport function loggingPlugin(options: LoggingPluginOptions = {}): RevenuePlugin {\n const level = options.level ?? 'info';\n\n return definePlugin({\n name: 'logging',\n version: '1.0.0',\n description: 'Logs all revenue operations',\n hooks: {\n 'payment.create.after': async (ctx, input, next) => {\n ctx.logger[level]('Creating payment', { amount: input.amount, currency: input.currency });\n const result = await next();\n ctx.logger[level]('Payment created', { paymentIntentId: result?.paymentIntentId });\n return result;\n },\n 'payment.verify.after': async (ctx, input, next) => {\n ctx.logger[level]('Verifying payment', { id: input.id });\n const result = await next();\n ctx.logger[level]('Payment verified', { verified: result?.verified });\n return result;\n },\n 'payment.refund.after': async (ctx, input, next) => {\n ctx.logger[level]('Processing refund', { transactionId: input.transactionId, amount: input.amount });\n const result = await next();\n ctx.logger[level]('Refund processed', { refundId: result?.refundId });\n return result;\n },\n },\n });\n}\n\nexport default loggingPlugin;\n","/**\n * Audit Plugin\n * @classytic/revenue\n *\n * Records all operations for compliance and audit trails\n */\n\nimport { definePlugin, type RevenuePlugin } from '../../../core/plugin.js';\n\n/**\n * Audit entry record\n */\nexport interface AuditEntry {\n action: string;\n requestId: string;\n timestamp: Date;\n input: Record<string, unknown>;\n output: Record<string, unknown>;\n idempotencyKey?: string;\n}\n\n/**\n * Audit plugin options\n */\nexport interface AuditPluginOptions {\n /** Custom storage function for audit entries */\n store?: (entry: AuditEntry) => Promise<void>;\n}\n\n/**\n * Sanitize input by removing sensitive fields\n * @private\n */\nfunction sanitizeInput(input: unknown): Record<string, unknown> {\n if (typeof input !== 'object' || !input) return {};\n const sanitized = { ...input } as Record<string, unknown>;\n // Remove sensitive fields\n delete sanitized.apiKey;\n delete sanitized.secretKey;\n delete sanitized.password;\n return sanitized;\n}\n\n/**\n * Sanitize output\n * @private\n */\nfunction sanitizeOutput(output: unknown): Record<string, unknown> {\n if (typeof output !== 'object' || !output) return {};\n return { ...output } as Record<string, unknown>;\n}\n\n/**\n * Audit plugin - records all operations for compliance\n *\n * Records payment creation, refunds, and other operations with sanitized data\n *\n * @param options - Plugin options\n * @returns Audit plugin\n *\n * @example\n * ```typescript\n * import { Revenue } from '@classytic/revenue';\n * import { auditPlugin } from '@classytic/revenue/plugins';\n *\n * const revenue = Revenue\n * .create()\n * .withPlugin(auditPlugin({\n * store: async (entry) => {\n * await AuditLog.create(entry);\n * }\n * }))\n * .build();\n * ```\n */\nexport function auditPlugin(options: AuditPluginOptions = {}): RevenuePlugin {\n const entries: AuditEntry[] = [];\n\n const store = options.store ?? (async (entry: AuditEntry) => {\n entries.push(entry);\n });\n\n return definePlugin({\n name: 'audit',\n version: '1.0.0',\n description: 'Audit trail for all operations',\n hooks: {\n 'payment.create.after': async (ctx, input, next) => {\n const result = await next();\n await store({\n action: 'payment.create',\n requestId: ctx.meta.requestId,\n timestamp: ctx.meta.timestamp,\n input: sanitizeInput(input),\n output: sanitizeOutput(result),\n idempotencyKey: ctx.meta.idempotencyKey,\n });\n return result;\n },\n 'payment.refund.after': async (ctx, input, next) => {\n const result = await next();\n await store({\n action: 'payment.refund',\n requestId: ctx.meta.requestId,\n timestamp: ctx.meta.timestamp,\n input: sanitizeInput(input),\n output: sanitizeOutput(result),\n idempotencyKey: ctx.meta.idempotencyKey,\n });\n return result;\n },\n },\n });\n}\n\nexport default auditPlugin;\n","/**\n * Metrics Plugin\n * @classytic/revenue\n *\n * Collects operation metrics (duration, success/failure)\n */\n\nimport { definePlugin, type RevenuePlugin } from '../../../core/plugin.js';\n\n/**\n * Metric record\n */\nexport interface Metric {\n name: string;\n duration: number;\n success: boolean;\n error?: string;\n [key: string]: unknown;\n}\n\n/**\n * Metrics plugin options\n */\nexport interface MetricsPluginOptions {\n /** Callback for each metric */\n onMetric?: (metric: Metric) => void;\n}\n\n/**\n * Metrics plugin - collects operation metrics\n *\n * Tracks duration and success/failure of operations\n *\n * @param options - Plugin options\n * @returns Metrics plugin\n *\n * @example\n * ```typescript\n * import { Revenue } from '@classytic/revenue';\n * import { metricsPlugin } from '@classytic/revenue/plugins';\n *\n * const revenue = Revenue\n * .create()\n * .withPlugin(metricsPlugin({\n * onMetric: (metric) => {\n * // Send to Datadog, Prometheus, etc.\n * statsd.timing(metric.name, metric.duration);\n * if (!metric.success) {\n * statsd.increment(`${metric.name}.error`);\n * }\n * }\n * }))\n * .build();\n * ```\n */\nexport function metricsPlugin(options: MetricsPluginOptions = {}): RevenuePlugin {\n const metrics: Metric[] = [];\n\n const record = options.onMetric ?? ((metric: Metric) => {\n metrics.push(metric);\n });\n\n return definePlugin({\n name: 'metrics',\n version: '1.0.0',\n description: 'Collects operation metrics',\n hooks: {\n 'payment.create.before': async (_ctx, input, next) => {\n const start = Date.now();\n try {\n const result = await next();\n record({\n name: 'payment.create',\n duration: Date.now() - start,\n success: true,\n amount: input.amount,\n currency: input.currency,\n });\n return result;\n } catch (error) {\n record({\n name: 'payment.create',\n duration: Date.now() - start,\n success: false,\n error: (error as Error).message,\n });\n throw error;\n }\n },\n },\n });\n}\n\nexport default metricsPlugin;\n","/**\n * Tax Utilities\n * @classytic/revenue\n *\n * Tax calculation utilities\n * Philosophy: Apps provide rates, library does math (like Stripe)\n */\n\nimport type { TaxConfig, TaxCalculation, TaxType } from '../../types/tax.js';\nimport { ValidationError } from '../../../core/errors.js';\n\n/**\n * Calculate tax for a transaction\n *\n * Handles both tax-inclusive and tax-exclusive pricing:\n * - Tax-exclusive: Customer pays baseAmount + tax\n * - Tax-inclusive: Customer pays totalAmount (which includes tax)\n *\n * @param amount - Transaction amount (in smallest currency unit, e.g., cents)\n * @param category - Transaction category (for exemption check)\n * @param config - Tax configuration from app\n * @returns Tax calculation result\n *\n * @example\n * ```typescript\n * // Tax-exclusive pricing (customer pays base + tax)\n * const result = calculateTax(10000, 'subscription', {\n * isRegistered: true,\n * defaultRate: 0.15,\n * pricesIncludeTax: false,\n * });\n * // result = {\n * // isApplicable: true,\n * // rate: 0.15,\n * // baseAmount: 10000,\n * // taxAmount: 1500,\n * // totalAmount: 11500,\n * // pricesIncludeTax: false\n * // }\n *\n * // Tax-inclusive pricing (price already includes tax)\n * const result2 = calculateTax(11500, 'subscription', {\n * isRegistered: true,\n * defaultRate: 0.15,\n * pricesIncludeTax: true,\n * });\n * // result2 = {\n * // isApplicable: true,\n * // rate: 0.15,\n * // baseAmount: 10000,\n * // taxAmount: 1500,\n * // totalAmount: 11500,\n * // pricesIncludeTax: true\n * // }\n * ```\n */\nexport function calculateTax(\n amount: number,\n category: string,\n config: TaxConfig | null\n): TaxCalculation {\n // No tax if not registered or category is exempt\n if (!config?.isRegistered || config.exemptCategories?.includes(category)) {\n return {\n isApplicable: false,\n rate: 0,\n baseAmount: amount,\n taxAmount: 0,\n totalAmount: amount,\n pricesIncludeTax: false,\n };\n }\n\n const rate = config.defaultRate;\n\n // Calculate based on pricing model\n // Note: amounts are already in smallest unit (cents), so we round to integers\n const [baseAmount, taxAmount, totalAmount] = config.pricesIncludeTax\n ? [\n // Tax-inclusive: extract tax from total\n Math.round(amount / (1 + rate)), // baseAmount\n Math.round(amount - amount / (1 + rate)), // taxAmount\n amount, // totalAmount (already integer)\n ]\n : [\n // Tax-exclusive: add tax to base\n amount, // baseAmount (already integer)\n Math.round(amount * rate), // taxAmount\n Math.round(amount * (1 + rate)), // totalAmount\n ];\n\n return {\n isApplicable: true,\n rate,\n baseAmount,\n taxAmount,\n totalAmount,\n pricesIncludeTax: config.pricesIncludeTax,\n };\n}\n\n/**\n * Get tax type based on transaction flow\n *\n * - Inflow transactions → tax is \"collected\" (you collect from customer)\n * - Outflow transactions → tax is \"paid\" (you pay to supplier)\n * - Exempt categories → \"exempt\"\n *\n * @param transactionFlow - 'inflow' or 'outflow'\n * @param category - Transaction category\n * @param exemptCategories - List of exempt categories\n * @returns Tax type\n *\n * @example\n * ```typescript\n * getTaxType('inflow', 'subscription', []) // 'collected'\n * getTaxType('outflow', 'refund', []) // 'paid'\n * getTaxType('inflow', 'education', ['education']) // 'exempt'\n * ```\n */\nexport function getTaxType(\n transactionFlow: 'inflow' | 'outflow',\n category: string,\n exemptCategories: string[] = []\n): TaxType {\n if (exemptCategories.includes(category)) {\n return 'exempt';\n }\n\n return transactionFlow === 'inflow' ? 'collected' : 'paid';\n}\n\n/**\n * Reverse tax calculation for refunds\n * When refunding a transaction, tax must be reversed proportionally\n *\n * @param originalTax - Tax from original transaction\n * @param originalAmount - Original transaction amount\n * @param refundAmount - Amount being refunded\n * @returns Reversed tax calculation\n *\n * @example\n * ```typescript\n * // Original: $100 + $15 tax = $115\n * const originalTax = {\n * isApplicable: true,\n * rate: 0.15,\n * baseAmount: 10000,\n * taxAmount: 1500,\n * totalAmount: 11500,\n * type: 'collected',\n * };\n *\n * // Refund 50% ($57.50)\n * const refundTax = reverseTax(originalTax, 11500, 5750);\n * // refundTax = {\n * // isApplicable: true,\n * // rate: 0.15,\n * // baseAmount: 5000,\n * // taxAmount: 750,\n * // totalAmount: 5750,\n * // type: 'paid', // Reversed!\n * // }\n * ```\n */\nexport function reverseTax(\n originalTax: TaxCalculation & { type?: TaxType },\n originalAmount: number,\n refundAmount: number\n): TaxCalculation & { type?: TaxType } {\n if (!originalTax.isApplicable) {\n return {\n isApplicable: false,\n rate: 0,\n baseAmount: refundAmount,\n taxAmount: 0,\n totalAmount: refundAmount,\n pricesIncludeTax: false,\n };\n }\n\n // Edge case validations\n if (!originalAmount || originalAmount <= 0) {\n throw new ValidationError('Original amount must be greater than 0', { originalAmount });\n }\n\n if (refundAmount < 0) {\n throw new ValidationError('Refund amount cannot be negative', { refundAmount });\n }\n\n if (refundAmount > originalAmount) {\n throw new ValidationError(\n `Refund amount (${refundAmount}) exceeds original amount (${originalAmount})`,\n { refundAmount, originalAmount }\n );\n }\n\n // Calculate refund ratio\n const refundRatio = refundAmount / originalAmount;\n\n // Reverse tax type: collected → paid, paid → collected\n const reversedType: TaxType | undefined = originalTax.type\n ? originalTax.type === 'collected'\n ? 'paid'\n : originalTax.type === 'paid'\n ? 'collected'\n : 'exempt'\n : undefined;\n\n // Calculate reversed amounts (all in smallest unit, integers only)\n return {\n isApplicable: true,\n rate: originalTax.rate,\n baseAmount: Math.round(originalTax.baseAmount * refundRatio),\n taxAmount: Math.round(originalTax.taxAmount * refundRatio),\n totalAmount: Math.round(originalTax.totalAmount * refundRatio),\n pricesIncludeTax: originalTax.pricesIncludeTax,\n type: reversedType,\n };\n}\n\n/**\n * Validate tax calculation\n * Ensures the math is correct (base + tax = total)\n *\n * @param tax - Tax calculation to validate\n * @returns true if valid\n */\nexport function validateTaxCalculation(tax: TaxCalculation): boolean {\n if (!tax.isApplicable) return true;\n\n const calculatedTotal = tax.baseAmount + tax.taxAmount;\n const diff = Math.abs(calculatedTotal - tax.totalAmount);\n\n // Allow for 1 cent rounding difference\n return diff <= 1;\n}\n","/**\n * Transaction Enums\n * @classytic/revenue\n *\n * Library-managed transaction enums only.\n * Users should define their own categories and merge with these.\n */\n\n// ============ TRANSACTION FLOW ============\n/**\n * Transaction Flow - Directional money movement\n *\n * INFLOW: Money coming in (payments, subscriptions, purchases, receipts)\n * OUTFLOW: Money going out (refunds, payouts, expenses, disbursements)\n *\n * Industry-standard terminology compatible with QuickBooks, Xero, and other accounting systems.\n * Users can map categories to flow directions via transactionTypeMapping config.\n *\n * @example\n * // Revenue platform\n * { type: 'subscription', flow: 'inflow' }\n *\n * // Payroll platform\n * { type: 'salary', flow: 'outflow' }\n *\n * // Marketplace\n * { type: 'commission', flow: 'outflow' } // Paying sellers\n * { type: 'platform_fee', flow: 'inflow' } // Platform revenue\n */\nexport const TRANSACTION_FLOW = {\n INFLOW: 'inflow',\n OUTFLOW: 'outflow',\n} as const;\n\n/** @deprecated Use TRANSACTION_FLOW instead */\nexport const TRANSACTION_TYPE = TRANSACTION_FLOW;\n\nexport type TransactionFlow = typeof TRANSACTION_FLOW;\nexport type TransactionFlowValue = TransactionFlow[keyof TransactionFlow];\nexport const TRANSACTION_FLOW_VALUES = Object.values(\n TRANSACTION_FLOW,\n) as TransactionFlowValue[];\n\n/** @deprecated Use TransactionFlow instead */\nexport type TransactionType = TransactionFlow;\n/** @deprecated Use TransactionFlowValue instead */\nexport type TransactionTypeValue = TransactionFlowValue;\n/** @deprecated Use TRANSACTION_FLOW_VALUES instead */\nexport const TRANSACTION_TYPE_VALUES = TRANSACTION_FLOW_VALUES;\n\n// ============ TRANSACTION STATUS ============\n/**\n * Transaction Status - Library-managed states\n */\nexport const TRANSACTION_STATUS = {\n PENDING: 'pending',\n PAYMENT_INITIATED: 'payment_initiated',\n PROCESSING: 'processing',\n REQUIRES_ACTION: 'requires_action',\n VERIFIED: 'verified',\n COMPLETED: 'completed',\n FAILED: 'failed',\n CANCELLED: 'cancelled',\n EXPIRED: 'expired',\n REFUNDED: 'refunded',\n PARTIALLY_REFUNDED: 'partially_refunded',\n} as const;\n\nexport type TransactionStatus = typeof TRANSACTION_STATUS;\nexport type TransactionStatusValue = TransactionStatus[keyof TransactionStatus];\nexport const TRANSACTION_STATUS_VALUES = Object.values(\n TRANSACTION_STATUS,\n) as TransactionStatusValue[];\n\n// ============ LIBRARY CATEGORIES ============\n/**\n * Categories managed by this library\n *\n * SUBSCRIPTION: Recurring subscription payments\n * PURCHASE: One-time purchases\n *\n * Users should spread these into their own category enums:\n *\n * @example\n * import { LIBRARY_CATEGORIES } from '@classytic/revenue';\n *\n * export const MY_CATEGORIES = {\n * ...LIBRARY_CATEGORIES,\n * SALARY: 'salary',\n * RENT: 'rent',\n * EQUIPMENT: 'equipment',\n * } as const;\n */\nexport const LIBRARY_CATEGORIES = {\n SUBSCRIPTION: 'subscription',\n PURCHASE: 'purchase',\n} as const;\n\nexport type LibraryCategories = typeof LIBRARY_CATEGORIES;\nexport type LibraryCategoryValue = LibraryCategories[keyof LibraryCategories];\nexport const LIBRARY_CATEGORY_VALUES = Object.values(\n LIBRARY_CATEGORIES,\n) as LibraryCategoryValue[];\n\nconst transactionFlowSet = new Set<TransactionFlowValue>(TRANSACTION_FLOW_VALUES);\nconst transactionStatusSet = new Set<TransactionStatusValue>(\n TRANSACTION_STATUS_VALUES,\n);\nconst libraryCategorySet = new Set<LibraryCategoryValue>(LIBRARY_CATEGORY_VALUES);\n\nexport function isLibraryCategory(value: unknown): value is LibraryCategoryValue {\n return typeof value === 'string' && libraryCategorySet.has(value as LibraryCategoryValue);\n}\n\nexport function isTransactionFlow(value: unknown): value is TransactionFlowValue {\n return typeof value === 'string' && transactionFlowSet.has(value as TransactionFlowValue);\n}\n\n/** @deprecated Use isTransactionFlow instead */\nexport function isTransactionType(value: unknown): value is TransactionTypeValue {\n return isTransactionFlow(value);\n}\n\nexport function isTransactionStatus(\n value: unknown,\n): value is TransactionStatusValue {\n return typeof value === 'string' && transactionStatusSet.has(value as TransactionStatusValue);\n}\n","/**\n * Category Resolver Utility\n * @classytic/revenue\n *\n * Resolves transaction category based on categoryMappings\n */\n\nimport { LIBRARY_CATEGORIES } from '../../../enums/transaction.enums.js';\nimport type { MonetizationTypeValue } from '../../types/index.js';\n\n/**\n * Resolve category for a transaction based on entity and monetizationType\n *\n * Resolution Logic:\n * 1. If categoryMappings[entity] exists → use it\n * 2. Otherwise → fall back to default library category\n *\n * @param entity - The logical entity/identifier (e.g., 'Order', 'PlatformSubscription', 'Membership')\n * NOTE: This is NOT a database model name - it's just a logical identifier\n * @param monetizationType - The monetization type ('subscription', 'purchase', 'free')\n * @param categoryMappings - User-defined category mappings from config\n * @returns Category name for the transaction\n *\n * @example\n * // With mapping defined\n * resolveCategory('Order', 'subscription', { Order: 'order_subscription' })\n * // Returns: 'order_subscription'\n *\n * @example\n * // Without mapping, falls back to library default\n * resolveCategory('Order', 'subscription', {})\n * // Returns: 'subscription'\n *\n * @example\n * // Different entities with different mappings\n * const mappings = {\n * Order: 'order_subscription',\n * PlatformSubscription: 'platform_subscription',\n * TenantUpgrade: 'tenant_upgrade',\n * Membership: 'gym_membership',\n * Enrollment: 'course_enrollment',\n * };\n * resolveCategory('PlatformSubscription', 'subscription', mappings)\n * // Returns: 'platform_subscription'\n */\nexport function resolveCategory(\n entity: string | null | undefined,\n monetizationType: MonetizationTypeValue,\n categoryMappings: Record<string, string> = {}\n): string {\n // If user has defined a custom mapping for this entity, use it\n if (entity && categoryMappings[entity]) {\n return categoryMappings[entity];\n }\n\n // Otherwise, fall back to library default based on monetization type\n switch (monetizationType) {\n case 'subscription':\n return LIBRARY_CATEGORIES.SUBSCRIPTION; // 'subscription'\n case 'purchase':\n return LIBRARY_CATEGORIES.PURCHASE; // 'purchase'\n default:\n return LIBRARY_CATEGORIES.SUBSCRIPTION; // Default to subscription\n }\n}\n\n/**\n * Validate that a category is defined in user's Transaction model enum\n * This is informational - actual validation happens at Mongoose schema level\n *\n * @param category - Category to validate\n * @param allowedCategories - List of allowed categories\n * @returns Whether category is valid\n */\nexport function isCategoryValid(\n category: string,\n allowedCategories: string[] = []\n): boolean {\n return allowedCategories.includes(category);\n}\n\nexport default resolveCategory;\n","/**\n * Tax Plugin\n * @classytic/revenue\n *\n * Automatic tax calculation for transactions\n * Integrates with monetization.create.before hook\n */\n\nimport { definePlugin, type MonetizationCreateInput } from '../../../core/plugin.js';\nimport { calculateTax, getTaxType } from '../../../shared/utils/calculators/tax.js';\nimport { resolveCategory } from '../../../shared/utils/validators/category-resolver.js';\nimport type { TaxConfig } from '../../../shared/types/tax.js';\n\n/**\n * Tax Plugin Options\n */\nexport interface TaxPluginOptions {\n /**\n * Function to get tax configuration for an organization\n * Apps implement this to return jurisdiction-specific config\n *\n * @param orgId - Organization ID\n * @returns Tax configuration or null if not registered\n *\n * @example\n * ```typescript\n * getTaxConfig: async (orgId) => {\n * const org = await Organization.findById(orgId);\n * if (!org) return null;\n *\n * return {\n * isRegistered: org.country === 'AU',\n * defaultRate: org.country === 'AU' ? 0.10 : 0, // 10% GST in Australia\n * pricesIncludeTax: org.pricesIncludeTax || false,\n * exemptCategories: ['education', 'medical'],\n * };\n * }\n * ```\n */\n getTaxConfig: (orgId: string) => Promise<TaxConfig | null>;\n\n /**\n * Category mappings for resolving transaction categories\n * Maps entity names to category strings\n *\n * @example\n * ```typescript\n * {\n * Order: 'order_subscription',\n * PlatformSubscription: 'platform_subscription',\n * Membership: 'gym_membership',\n * }\n * ```\n */\n categoryMappings?: Record<string, string>;\n\n /**\n * Categories that represent income (vs expense)\n * Used to determine tax type: 'collected' vs 'paid'\n *\n * Default: ['subscription', 'purchase', 'course_enrollment']\n */\n incomeCategories?: string[];\n}\n\n/**\n * Create Tax Plugin\n *\n * Automatically calculates and applies tax to transactions during monetization.create()\n *\n * @param options - Plugin options\n * @returns Tax plugin\n *\n * @example\n * ```typescript\n * import { Revenue } from '@classytic/revenue';\n * import { createTaxPlugin } from '@classytic/revenue/plugins';\n *\n * const revenue = Revenue\n * .create({ defaultCurrency: 'USD' })\n * .withModels({ Transaction, Subscription })\n * .withProvider('stripe', stripeProvider)\n * .withPlugin(createTaxPlugin({\n * getTaxConfig: async (orgId) => {\n * const org = await Organization.findById(orgId);\n * return {\n * isRegistered: true,\n * defaultRate: 0.15, // 15% tax\n * pricesIncludeTax: false,\n * exemptCategories: ['education'],\n * };\n * },\n * categoryMappings: {\n * Order: 'order_subscription',\n * Membership: 'gym_membership',\n * },\n * }))\n * .build();\n *\n * // Tax is now automatically calculated\n * await revenue.monetization.create({\n * data: { organizationId: 'org_123', customerId: 'cust_456' },\n * planKey: 'monthly',\n * amount: 10000, // $100\n * entity: 'Order',\n * monetizationType: 'subscription',\n * });\n * // → Creates transaction with tax: {\n * // isApplicable: true,\n * // rate: 0.15,\n * // baseAmount: 10000,\n * // taxAmount: 1500,\n * // totalAmount: 11500,\n * // type: 'collected'\n * // }\n * ```\n */\nexport function createTaxPlugin(options: TaxPluginOptions) {\n const {\n getTaxConfig,\n categoryMappings = {},\n incomeCategories = ['subscription', 'purchase', 'course_enrollment', 'product_order'],\n } = options;\n\n return definePlugin({\n name: 'tax',\n version: '1.0.0',\n description: 'Automatic tax calculation for transactions',\n\n hooks: {\n /**\n * Calculate tax before monetization creation\n * Injects tax data into the input so it's saved with the transaction\n */\n 'monetization.create.before': async (ctx, input: MonetizationCreateInput, next) => {\n // Clean, type-safe access - no more (input as any)!\n const orgId = input.data.organizationId;\n\n if (!orgId) {\n ctx.logger.debug('Tax plugin: No organizationId in input.data, skipping tax calculation');\n return next();\n }\n\n try {\n // Get tax config from app\n const config = await getTaxConfig(orgId);\n\n if (!config) {\n ctx.logger.debug('Tax plugin: No tax config for org', { orgId });\n return next();\n }\n\n // Resolve category - clean type-safe access\n const category = resolveCategory(\n input.entity,\n input.monetizationType || 'subscription',\n categoryMappings\n );\n\n // Determine transaction flow (inflow vs outflow)\n const transactionFlow = incomeCategories.includes(category) ? 'inflow' : 'outflow';\n\n // Calculate tax\n const taxCalc = calculateTax(input.amount, category, config);\n\n // Get tax type\n const taxType = getTaxType(transactionFlow, category, config.exemptCategories);\n\n // Inject tax into input - type-safe\n input.tax = {\n ...taxCalc,\n type: taxType,\n };\n\n ctx.logger.debug('Tax plugin: Tax calculated', {\n orgId,\n category,\n entity: input.entity,\n monetizationType: input.monetizationType,\n taxAmount: taxCalc.taxAmount,\n type: taxType,\n });\n } catch (error) {\n // Don't fail the transaction if tax calculation fails\n ctx.logger.error('Tax plugin: Failed to calculate tax', {\n orgId,\n error: (error as Error).message,\n });\n }\n\n return next();\n },\n },\n });\n}\n\nexport default createTaxPlugin;\n","/**\n * Built-in Plugins\n * @classytic/revenue/plugins\n *\n * Collection of built-in plugins for common use cases\n */\n\n// Export plugin functions\nexport { loggingPlugin, type LoggingPluginOptions } from './business/logging.plugin.js';\nexport { auditPlugin, type AuditPluginOptions, type AuditEntry } from './business/audit.plugin.js';\nexport { metricsPlugin, type MetricsPluginOptions, type Metric } from './business/metrics.plugin.js';\nexport { createTaxPlugin, type TaxPluginOptions } from './business/tax.plugin.js';\n\n// Re-export definePlugin for custom plugins\nexport { definePlugin } from '../../core/plugin.js';\n\n// Default export with all plugins\nimport { loggingPlugin } from './business/logging.plugin.js';\nimport { auditPlugin } from './business/audit.plugin.js';\nimport { metricsPlugin } from './business/metrics.plugin.js';\nimport { createTaxPlugin } from './business/tax.plugin.js';\n\nexport default {\n loggingPlugin,\n auditPlugin,\n metricsPlugin,\n createTaxPlugin,\n};\n"]}
@@ -1,111 +0,0 @@
1
- /**
2
- * Money Utility - Integer-safe currency handling
3
- * @classytic/revenue
4
- *
5
- * Never use floating point for money!
6
- * All amounts stored as smallest unit (cents, paisa, etc.)
7
- *
8
- * Inspired by: Stripe, Dinero.js, tc39/proposal-decimal
9
- */
10
- interface MoneyValue {
11
- /** Amount in smallest currency unit (cents, paisa, etc.) */
12
- readonly amount: number;
13
- /** ISO 4217 currency code */
14
- readonly currency: string;
15
- }
16
- /**
17
- * Money class - immutable money representation
18
- */
19
- declare class Money implements MoneyValue {
20
- readonly amount: number;
21
- readonly currency: string;
22
- private constructor();
23
- /**
24
- * Create money from smallest unit (cents, paisa)
25
- * @example Money.cents(1999, 'USD') // $19.99
26
- */
27
- static cents(amount: number, currency?: string): Money;
28
- /**
29
- * Create money from major unit (dollars, taka)
30
- * @example Money.of(19.99, 'USD') // $19.99 (stored as 1999 cents)
31
- */
32
- static of(amount: number, currency?: string): Money;
33
- /**
34
- * Create zero money
35
- */
36
- static zero(currency?: string): Money;
37
- static usd(cents: number): Money;
38
- static eur(cents: number): Money;
39
- static gbp(pence: number): Money;
40
- static bdt(paisa: number): Money;
41
- static inr(paisa: number): Money;
42
- static jpy(yen: number): Money;
43
- /**
44
- * Add two money values (must be same currency)
45
- */
46
- add(other: Money): Money;
47
- /**
48
- * Subtract money (must be same currency)
49
- */
50
- subtract(other: Money): Money;
51
- /**
52
- * Multiply by a factor (rounds to nearest integer)
53
- */
54
- multiply(factor: number): Money;
55
- /**
56
- * Divide by a divisor (rounds to nearest integer)
57
- */
58
- divide(divisor: number): Money;
59
- /**
60
- * Calculate percentage
61
- * @example money.percentage(10) // 10% of money
62
- */
63
- percentage(percent: number): Money;
64
- /**
65
- * Allocate money among recipients (handles rounding)
66
- * @example Money.usd(100).allocate([1, 1, 1]) // [34, 33, 33] cents
67
- */
68
- allocate(ratios: number[]): Money[];
69
- /**
70
- * Split equally among n recipients
71
- */
72
- split(parts: number): Money[];
73
- isZero(): boolean;
74
- isPositive(): boolean;
75
- isNegative(): boolean;
76
- equals(other: Money): boolean;
77
- greaterThan(other: Money): boolean;
78
- lessThan(other: Money): boolean;
79
- greaterThanOrEqual(other: Money): boolean;
80
- lessThanOrEqual(other: Money): boolean;
81
- /**
82
- * Get amount in major unit (dollars, taka)
83
- */
84
- toUnit(): number;
85
- /**
86
- * Format for display
87
- * @example Money.usd(1999).format() // "$19.99"
88
- */
89
- format(locale?: string): string;
90
- /**
91
- * Format without currency symbol
92
- */
93
- formatAmount(locale?: string): string;
94
- /**
95
- * Convert to JSON-serializable object
96
- */
97
- toJSON(): MoneyValue;
98
- /**
99
- * Create from JSON
100
- */
101
- static fromJSON(json: MoneyValue): Money;
102
- toString(): string;
103
- private assertSameCurrency;
104
- }
105
- /**
106
- * Helper functions for legacy compatibility
107
- */
108
- declare function toSmallestUnit(amount: number, currency?: string): number;
109
- declare function fromSmallestUnit(amount: number, currency?: string): number;
110
-
111
- export { Money as M, type MoneyValue as a, fromSmallestUnit as f, toSmallestUnit as t };