@classytic/revenue 1.1.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +638 -632
- package/dist/audit-B39B0Sdq.mjs +53 -0
- package/dist/audit-DZ0eTr9g.d.mts +89 -0
- package/dist/bridges/index.d.mts +2 -0
- package/dist/bridges/index.mjs +1 -0
- package/dist/context-DRqSeTPM.d.mts +35 -0
- package/dist/core/state-machines.d.mts +35 -0
- package/dist/core/state-machines.mjs +134 -0
- package/dist/engine-types-CcjIb4Fy.d.mts +611 -0
- package/dist/enums/index.d.mts +3 -157
- package/dist/enums/index.mjs +3 -55
- package/dist/errors-DHa8JVQ-.mjs +92 -0
- package/dist/escrow.schema-BBv9oVEW.mjs +322 -0
- package/dist/escrow.schema-CC8XuD46.d.mts +629 -0
- package/dist/event-constants-CEMitnIV.mjs +53 -0
- package/dist/events/index.d.mts +3 -0
- package/dist/events/index.mjs +4 -0
- package/dist/index.d.mts +77 -9
- package/dist/index.mjs +465 -29
- package/dist/monetization.enums-BtiU3t8o.mjs +39 -0
- package/dist/monetization.enums-D2xbxXJM.d.mts +34 -0
- package/dist/plugins/plugin.interface.d.mts +28 -0
- package/dist/plugins/plugin.interface.mjs +26 -0
- package/dist/providers/index.d.mts +2 -3
- package/dist/providers/index.mjs +2 -2
- package/dist/{base-DCoyIUj6.mjs → registry-DhFMsSn5.mjs} +34 -36
- package/dist/{base-CsTlVQJe.d.mts → registry-SvIGPAx_.d.mts} +73 -66
- package/dist/repositories/create-repositories.d.mts +21 -0
- package/dist/repositories/create-repositories.mjs +12 -0
- package/dist/revenue-bridges-sdlrR85c.d.mts +145 -0
- package/dist/revenue-event-catalog-BX3g7RUi.d.mts +823 -0
- package/dist/revenue-event-catalog-LqxPnsU_.mjs +388 -0
- package/dist/settlement.repository-Cy3mMWGH.mjs +771 -0
- package/dist/shared/index.d.mts +2 -0
- package/dist/shared/index.mjs +4 -0
- package/dist/split.enums-CQE3ekH1.mjs +172 -0
- package/dist/split.enums-Dw4zCrcZ.d.mts +154 -0
- package/dist/splits-BAfY-a9P.mjs +123 -0
- package/dist/validators/index.d.mts +2 -0
- package/dist/validators/index.mjs +3 -0
- package/package.json +33 -37
- package/dist/application/services/index.d.mts +0 -4
- package/dist/application/services/index.mjs +0 -3
- package/dist/category-resolver-DV83N8ok.mjs +0 -284
- package/dist/commission-split-BzB8cd39.mjs +0 -485
- package/dist/core/events.d.mts +0 -294
- package/dist/core/events.mjs +0 -100
- package/dist/core/index.d.mts +0 -9
- package/dist/core/index.mjs +0 -8
- package/dist/errors-rRdOqnWx.d.mts +0 -787
- package/dist/escrow.enums-CZGrrdg7.mjs +0 -101
- package/dist/escrow.enums-DwdLuuve.d.mts +0 -78
- package/dist/idempotency-DaYcUGY1.mjs +0 -172
- package/dist/index-Dsp7H5Wb.d.mts +0 -471
- package/dist/infrastructure/plugins/index.d.mts +0 -239
- package/dist/infrastructure/plugins/index.mjs +0 -345
- package/dist/money-CvrDOijQ.mjs +0 -271
- package/dist/money-DPG8AtJ8.d.mts +0 -112
- package/dist/payment.enums-HAuAS9Pp.d.mts +0 -70
- package/dist/payment.enums-tEFVa-Xp.mjs +0 -69
- package/dist/plugin-BbK0OVHy.d.mts +0 -327
- package/dist/plugin-Cd_V04Em.mjs +0 -210
- package/dist/reconciliation/index.d.mts +0 -193
- package/dist/reconciliation/index.mjs +0 -192
- package/dist/retry-HHCOXYdn.d.mts +0 -186
- package/dist/revenue-BhdS7nXh.mjs +0 -553
- package/dist/schemas/index.d.mts +0 -2665
- package/dist/schemas/index.mjs +0 -717
- package/dist/schemas/validation.d.mts +0 -375
- package/dist/schemas/validation.mjs +0 -325
- package/dist/settlement.enums-DFhkqZEY.d.mts +0 -132
- package/dist/settlement.schema-DnNSFpGd.d.mts +0 -344
- package/dist/settlement.service-DjzAjezU.d.mts +0 -594
- package/dist/settlement.service-DmdKv0Zu.mjs +0 -2511
- package/dist/split.enums-BrjabxIX.mjs +0 -86
- package/dist/split.enums-DmskfLOM.d.mts +0 -43
- package/dist/tax-BoCt5cEd.d.mts +0 -61
- package/dist/tax-EQ15DO81.mjs +0 -162
- package/dist/transaction.enums-pCyMFT4Z.mjs +0 -96
- package/dist/utils/index.d.mts +0 -428
- package/dist/utils/index.mjs +0 -346
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
import { r as definePlugin } from "../../plugin-Cd_V04Em.mjs";
|
|
2
|
-
import { t as resolveCategory } from "../../category-resolver-DV83N8ok.mjs";
|
|
3
|
-
import { n as getTaxType, t as calculateTax } from "../../tax-EQ15DO81.mjs";
|
|
4
|
-
|
|
5
|
-
//#region src/infrastructure/plugins/business/logging.plugin.ts
|
|
6
|
-
/**
|
|
7
|
-
* Logging Plugin
|
|
8
|
-
* @classytic/revenue
|
|
9
|
-
*
|
|
10
|
-
* Logs all revenue operations at specified log level
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* Logging plugin - logs all operations
|
|
14
|
-
*
|
|
15
|
-
* Logs payment creation, verification, and refund operations
|
|
16
|
-
*
|
|
17
|
-
* @param options - Plugin options
|
|
18
|
-
* @returns Logging plugin
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { Revenue } from '@classytic/revenue';
|
|
23
|
-
* import { loggingPlugin } from '@classytic/revenue/plugins';
|
|
24
|
-
*
|
|
25
|
-
* const revenue = Revenue
|
|
26
|
-
* .create()
|
|
27
|
-
* .withPlugin(loggingPlugin({ level: 'debug' }))
|
|
28
|
-
* .build();
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
function loggingPlugin(options = {}) {
|
|
32
|
-
const level = options.level ?? "info";
|
|
33
|
-
return definePlugin({
|
|
34
|
-
name: "logging",
|
|
35
|
-
version: "1.0.0",
|
|
36
|
-
description: "Logs all revenue operations",
|
|
37
|
-
hooks: {
|
|
38
|
-
"payment.create.after": async (ctx, input, next) => {
|
|
39
|
-
ctx.logger[level]("Creating payment", {
|
|
40
|
-
amount: input.amount,
|
|
41
|
-
currency: input.currency
|
|
42
|
-
});
|
|
43
|
-
const result = await next();
|
|
44
|
-
ctx.logger[level]("Payment created", { paymentIntentId: result?.paymentIntentId });
|
|
45
|
-
return result;
|
|
46
|
-
},
|
|
47
|
-
"payment.verify.after": async (ctx, input, next) => {
|
|
48
|
-
ctx.logger[level]("Verifying payment", { id: input.id });
|
|
49
|
-
const result = await next();
|
|
50
|
-
ctx.logger[level]("Payment verified", { verified: result?.verified });
|
|
51
|
-
return result;
|
|
52
|
-
},
|
|
53
|
-
"payment.refund.after": async (ctx, input, next) => {
|
|
54
|
-
ctx.logger[level]("Processing refund", {
|
|
55
|
-
transactionId: input.transactionId,
|
|
56
|
-
amount: input.amount
|
|
57
|
-
});
|
|
58
|
-
const result = await next();
|
|
59
|
-
ctx.logger[level]("Refund processed", { refundId: result?.refundId });
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/infrastructure/plugins/business/audit.plugin.ts
|
|
68
|
-
/**
|
|
69
|
-
* Audit Plugin
|
|
70
|
-
* @classytic/revenue
|
|
71
|
-
*
|
|
72
|
-
* Records all operations for compliance and audit trails
|
|
73
|
-
*/
|
|
74
|
-
/**
|
|
75
|
-
* Sanitize input by removing sensitive fields
|
|
76
|
-
* @private
|
|
77
|
-
*/
|
|
78
|
-
function sanitizeInput(input) {
|
|
79
|
-
if (typeof input !== "object" || !input) return {};
|
|
80
|
-
const sanitized = { ...input };
|
|
81
|
-
delete sanitized.apiKey;
|
|
82
|
-
delete sanitized.secretKey;
|
|
83
|
-
delete sanitized.password;
|
|
84
|
-
return sanitized;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Sanitize output
|
|
88
|
-
* @private
|
|
89
|
-
*/
|
|
90
|
-
function sanitizeOutput(output) {
|
|
91
|
-
if (typeof output !== "object" || !output) return {};
|
|
92
|
-
return { ...output };
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Audit plugin - records all operations for compliance
|
|
96
|
-
*
|
|
97
|
-
* Records payment creation, refunds, and other operations with sanitized data
|
|
98
|
-
*
|
|
99
|
-
* @param options - Plugin options
|
|
100
|
-
* @returns Audit plugin
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```typescript
|
|
104
|
-
* import { Revenue } from '@classytic/revenue';
|
|
105
|
-
* import { auditPlugin } from '@classytic/revenue/plugins';
|
|
106
|
-
*
|
|
107
|
-
* const revenue = Revenue
|
|
108
|
-
* .create()
|
|
109
|
-
* .withPlugin(auditPlugin({
|
|
110
|
-
* store: async (entry) => {
|
|
111
|
-
* await AuditLog.create(entry);
|
|
112
|
-
* }
|
|
113
|
-
* }))
|
|
114
|
-
* .build();
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
function auditPlugin(options = {}) {
|
|
118
|
-
const entries = [];
|
|
119
|
-
const store = options.store ?? (async (entry) => {
|
|
120
|
-
entries.push(entry);
|
|
121
|
-
});
|
|
122
|
-
return definePlugin({
|
|
123
|
-
name: "audit",
|
|
124
|
-
version: "1.0.0",
|
|
125
|
-
description: "Audit trail for all operations",
|
|
126
|
-
hooks: {
|
|
127
|
-
"payment.create.after": async (ctx, input, next) => {
|
|
128
|
-
const result = await next();
|
|
129
|
-
await store({
|
|
130
|
-
action: "payment.create",
|
|
131
|
-
requestId: ctx.meta.requestId,
|
|
132
|
-
timestamp: ctx.meta.timestamp,
|
|
133
|
-
input: sanitizeInput(input),
|
|
134
|
-
output: sanitizeOutput(result),
|
|
135
|
-
idempotencyKey: ctx.meta.idempotencyKey
|
|
136
|
-
});
|
|
137
|
-
return result;
|
|
138
|
-
},
|
|
139
|
-
"payment.refund.after": async (ctx, input, next) => {
|
|
140
|
-
const result = await next();
|
|
141
|
-
await store({
|
|
142
|
-
action: "payment.refund",
|
|
143
|
-
requestId: ctx.meta.requestId,
|
|
144
|
-
timestamp: ctx.meta.timestamp,
|
|
145
|
-
input: sanitizeInput(input),
|
|
146
|
-
output: sanitizeOutput(result),
|
|
147
|
-
idempotencyKey: ctx.meta.idempotencyKey
|
|
148
|
-
});
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region src/infrastructure/plugins/business/metrics.plugin.ts
|
|
157
|
-
/**
|
|
158
|
-
* Metrics Plugin
|
|
159
|
-
* @classytic/revenue
|
|
160
|
-
*
|
|
161
|
-
* Collects operation metrics (duration, success/failure)
|
|
162
|
-
*/
|
|
163
|
-
/**
|
|
164
|
-
* Metrics plugin - collects operation metrics
|
|
165
|
-
*
|
|
166
|
-
* Tracks duration and success/failure of operations
|
|
167
|
-
*
|
|
168
|
-
* @param options - Plugin options
|
|
169
|
-
* @returns Metrics plugin
|
|
170
|
-
*
|
|
171
|
-
* @example
|
|
172
|
-
* ```typescript
|
|
173
|
-
* import { Revenue } from '@classytic/revenue';
|
|
174
|
-
* import { metricsPlugin } from '@classytic/revenue/plugins';
|
|
175
|
-
*
|
|
176
|
-
* const revenue = Revenue
|
|
177
|
-
* .create()
|
|
178
|
-
* .withPlugin(metricsPlugin({
|
|
179
|
-
* onMetric: (metric) => {
|
|
180
|
-
* // Send to Datadog, Prometheus, etc.
|
|
181
|
-
* statsd.timing(metric.name, metric.duration);
|
|
182
|
-
* if (!metric.success) {
|
|
183
|
-
* statsd.increment(`${metric.name}.error`);
|
|
184
|
-
* }
|
|
185
|
-
* }
|
|
186
|
-
* }))
|
|
187
|
-
* .build();
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
function metricsPlugin(options = {}) {
|
|
191
|
-
const metrics = [];
|
|
192
|
-
const record = options.onMetric ?? ((metric) => {
|
|
193
|
-
metrics.push(metric);
|
|
194
|
-
});
|
|
195
|
-
return definePlugin({
|
|
196
|
-
name: "metrics",
|
|
197
|
-
version: "1.0.0",
|
|
198
|
-
description: "Collects operation metrics",
|
|
199
|
-
hooks: { "payment.create.before": async (_ctx, input, next) => {
|
|
200
|
-
const start = Date.now();
|
|
201
|
-
try {
|
|
202
|
-
const result = await next();
|
|
203
|
-
record({
|
|
204
|
-
name: "payment.create",
|
|
205
|
-
duration: Date.now() - start,
|
|
206
|
-
success: true,
|
|
207
|
-
amount: input.amount,
|
|
208
|
-
currency: input.currency
|
|
209
|
-
});
|
|
210
|
-
return result;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
record({
|
|
213
|
-
name: "payment.create",
|
|
214
|
-
duration: Date.now() - start,
|
|
215
|
-
success: false,
|
|
216
|
-
error: error.message
|
|
217
|
-
});
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
|
-
} }
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
//#endregion
|
|
225
|
-
//#region src/infrastructure/plugins/business/tax.plugin.ts
|
|
226
|
-
/**
|
|
227
|
-
* Tax Plugin
|
|
228
|
-
* @classytic/revenue
|
|
229
|
-
*
|
|
230
|
-
* Automatic tax calculation for transactions
|
|
231
|
-
* Integrates with monetization.create.before hook
|
|
232
|
-
*/
|
|
233
|
-
/**
|
|
234
|
-
* Create Tax Plugin
|
|
235
|
-
*
|
|
236
|
-
* Automatically calculates and applies tax to transactions during monetization.create()
|
|
237
|
-
*
|
|
238
|
-
* @param options - Plugin options
|
|
239
|
-
* @returns Tax plugin
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ```typescript
|
|
243
|
-
* import { Revenue } from '@classytic/revenue';
|
|
244
|
-
* import { createTaxPlugin } from '@classytic/revenue/plugins';
|
|
245
|
-
*
|
|
246
|
-
* const revenue = Revenue
|
|
247
|
-
* .create({ defaultCurrency: 'USD' })
|
|
248
|
-
* .withModels({ Transaction, Subscription })
|
|
249
|
-
* .withProvider('stripe', stripeProvider)
|
|
250
|
-
* .withPlugin(createTaxPlugin({
|
|
251
|
-
* getTaxConfig: async (orgId) => {
|
|
252
|
-
* const org = await Organization.findById(orgId);
|
|
253
|
-
* return {
|
|
254
|
-
* isRegistered: true,
|
|
255
|
-
* defaultRate: 0.15, // 15% tax
|
|
256
|
-
* pricesIncludeTax: false,
|
|
257
|
-
* exemptCategories: ['education'],
|
|
258
|
-
* };
|
|
259
|
-
* },
|
|
260
|
-
* categoryMappings: {
|
|
261
|
-
* Order: 'order_subscription',
|
|
262
|
-
* Membership: 'gym_membership',
|
|
263
|
-
* },
|
|
264
|
-
* }))
|
|
265
|
-
* .build();
|
|
266
|
-
*
|
|
267
|
-
* // Tax is now automatically calculated
|
|
268
|
-
* await revenue.monetization.create({
|
|
269
|
-
* data: { organizationId: 'org_123', customerId: 'cust_456' },
|
|
270
|
-
* planKey: 'monthly',
|
|
271
|
-
* amount: 10000, // $100
|
|
272
|
-
* entity: 'Order',
|
|
273
|
-
* monetizationType: 'subscription',
|
|
274
|
-
* });
|
|
275
|
-
* // → Creates transaction with tax: {
|
|
276
|
-
* // isApplicable: true,
|
|
277
|
-
* // rate: 0.15,
|
|
278
|
-
* // baseAmount: 10000,
|
|
279
|
-
* // taxAmount: 1500,
|
|
280
|
-
* // totalAmount: 11500,
|
|
281
|
-
* // type: 'collected'
|
|
282
|
-
* // }
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
function createTaxPlugin(options) {
|
|
286
|
-
const { getTaxConfig, categoryMappings = {}, incomeCategories = [
|
|
287
|
-
"subscription",
|
|
288
|
-
"purchase",
|
|
289
|
-
"course_enrollment",
|
|
290
|
-
"product_order"
|
|
291
|
-
] } = options;
|
|
292
|
-
return definePlugin({
|
|
293
|
-
name: "tax",
|
|
294
|
-
version: "1.0.0",
|
|
295
|
-
description: "Automatic tax calculation for transactions",
|
|
296
|
-
hooks: { "monetization.create.before": async (ctx, input, next) => {
|
|
297
|
-
const orgId = input.data.organizationId;
|
|
298
|
-
if (!orgId) {
|
|
299
|
-
ctx.logger.debug("Tax plugin: No organizationId in input.data, skipping tax calculation");
|
|
300
|
-
return next();
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
const config = await getTaxConfig(orgId);
|
|
304
|
-
if (!config) {
|
|
305
|
-
ctx.logger.debug("Tax plugin: No tax config for org", { orgId });
|
|
306
|
-
return next();
|
|
307
|
-
}
|
|
308
|
-
const category = resolveCategory(input.entity, input.monetizationType || "subscription", categoryMappings);
|
|
309
|
-
const transactionFlow = incomeCategories.includes(category) ? "inflow" : "outflow";
|
|
310
|
-
const taxCalc = calculateTax(input.amount, category, config);
|
|
311
|
-
const taxType = getTaxType(transactionFlow, category, config.exemptCategories);
|
|
312
|
-
input.tax = {
|
|
313
|
-
...taxCalc,
|
|
314
|
-
type: taxType
|
|
315
|
-
};
|
|
316
|
-
ctx.logger.debug("Tax plugin: Tax calculated", {
|
|
317
|
-
orgId,
|
|
318
|
-
category,
|
|
319
|
-
entity: input.entity,
|
|
320
|
-
monetizationType: input.monetizationType,
|
|
321
|
-
taxAmount: taxCalc.taxAmount,
|
|
322
|
-
type: taxType
|
|
323
|
-
});
|
|
324
|
-
} catch (error) {
|
|
325
|
-
ctx.logger.error("Tax plugin: Failed to calculate tax", {
|
|
326
|
-
orgId,
|
|
327
|
-
error: error.message
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
return next();
|
|
331
|
-
} }
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
//#endregion
|
|
336
|
-
//#region src/infrastructure/plugins/index.ts
|
|
337
|
-
var plugins_default = {
|
|
338
|
-
loggingPlugin,
|
|
339
|
-
auditPlugin,
|
|
340
|
-
metricsPlugin,
|
|
341
|
-
createTaxPlugin
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
//#endregion
|
|
345
|
-
export { auditPlugin, createTaxPlugin, plugins_default as default, definePlugin, loggingPlugin, metricsPlugin };
|
package/dist/money-CvrDOijQ.mjs
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
//#region src/shared/utils/formatters/money.ts
|
|
2
|
-
/** Supported currencies with their decimal places */
|
|
3
|
-
const CURRENCIES = {
|
|
4
|
-
USD: {
|
|
5
|
-
code: "USD",
|
|
6
|
-
decimals: 2,
|
|
7
|
-
symbol: "$",
|
|
8
|
-
name: "US Dollar"
|
|
9
|
-
},
|
|
10
|
-
EUR: {
|
|
11
|
-
code: "EUR",
|
|
12
|
-
decimals: 2,
|
|
13
|
-
symbol: "€",
|
|
14
|
-
name: "Euro"
|
|
15
|
-
},
|
|
16
|
-
GBP: {
|
|
17
|
-
code: "GBP",
|
|
18
|
-
decimals: 2,
|
|
19
|
-
symbol: "£",
|
|
20
|
-
name: "British Pound"
|
|
21
|
-
},
|
|
22
|
-
BDT: {
|
|
23
|
-
code: "BDT",
|
|
24
|
-
decimals: 2,
|
|
25
|
-
symbol: "৳",
|
|
26
|
-
name: "Bangladeshi Taka"
|
|
27
|
-
},
|
|
28
|
-
INR: {
|
|
29
|
-
code: "INR",
|
|
30
|
-
decimals: 2,
|
|
31
|
-
symbol: "₹",
|
|
32
|
-
name: "Indian Rupee"
|
|
33
|
-
},
|
|
34
|
-
JPY: {
|
|
35
|
-
code: "JPY",
|
|
36
|
-
decimals: 0,
|
|
37
|
-
symbol: "¥",
|
|
38
|
-
name: "Japanese Yen"
|
|
39
|
-
},
|
|
40
|
-
CNY: {
|
|
41
|
-
code: "CNY",
|
|
42
|
-
decimals: 2,
|
|
43
|
-
symbol: "¥",
|
|
44
|
-
name: "Chinese Yuan"
|
|
45
|
-
},
|
|
46
|
-
AED: {
|
|
47
|
-
code: "AED",
|
|
48
|
-
decimals: 2,
|
|
49
|
-
symbol: "د.إ",
|
|
50
|
-
name: "UAE Dirham"
|
|
51
|
-
},
|
|
52
|
-
SAR: {
|
|
53
|
-
code: "SAR",
|
|
54
|
-
decimals: 2,
|
|
55
|
-
symbol: "﷼",
|
|
56
|
-
name: "Saudi Riyal"
|
|
57
|
-
},
|
|
58
|
-
SGD: {
|
|
59
|
-
code: "SGD",
|
|
60
|
-
decimals: 2,
|
|
61
|
-
symbol: "S$",
|
|
62
|
-
name: "Singapore Dollar"
|
|
63
|
-
},
|
|
64
|
-
AUD: {
|
|
65
|
-
code: "AUD",
|
|
66
|
-
decimals: 2,
|
|
67
|
-
symbol: "A$",
|
|
68
|
-
name: "Australian Dollar"
|
|
69
|
-
},
|
|
70
|
-
CAD: {
|
|
71
|
-
code: "CAD",
|
|
72
|
-
decimals: 2,
|
|
73
|
-
symbol: "C$",
|
|
74
|
-
name: "Canadian Dollar"
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Money class - immutable money representation
|
|
79
|
-
*/
|
|
80
|
-
var Money = class Money {
|
|
81
|
-
amount;
|
|
82
|
-
currency;
|
|
83
|
-
constructor(amount, currency) {
|
|
84
|
-
if (!Number.isInteger(amount)) throw new Error(`Money amount must be integer (smallest unit). Got: ${amount}`);
|
|
85
|
-
this.amount = amount;
|
|
86
|
-
this.currency = currency.toUpperCase();
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Create money from smallest unit (cents, paisa)
|
|
90
|
-
* @example Money.cents(1999, 'USD') // $19.99
|
|
91
|
-
*/
|
|
92
|
-
static cents(amount, currency = "USD") {
|
|
93
|
-
return new Money(Math.round(amount), currency);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Create money from major unit (dollars, taka)
|
|
97
|
-
* @example Money.of(19.99, 'USD') // $19.99 (stored as 1999 cents)
|
|
98
|
-
*/
|
|
99
|
-
static of(amount, currency = "USD") {
|
|
100
|
-
const config = CURRENCIES[currency.toUpperCase()] ?? { decimals: 2 };
|
|
101
|
-
const multiplier = Math.pow(10, config.decimals);
|
|
102
|
-
return new Money(Math.round(amount * multiplier), currency);
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Create zero money
|
|
106
|
-
*/
|
|
107
|
-
static zero(currency = "USD") {
|
|
108
|
-
return new Money(0, currency);
|
|
109
|
-
}
|
|
110
|
-
static usd(cents) {
|
|
111
|
-
return Money.cents(cents, "USD");
|
|
112
|
-
}
|
|
113
|
-
static eur(cents) {
|
|
114
|
-
return Money.cents(cents, "EUR");
|
|
115
|
-
}
|
|
116
|
-
static gbp(pence) {
|
|
117
|
-
return Money.cents(pence, "GBP");
|
|
118
|
-
}
|
|
119
|
-
static bdt(paisa) {
|
|
120
|
-
return Money.cents(paisa, "BDT");
|
|
121
|
-
}
|
|
122
|
-
static inr(paisa) {
|
|
123
|
-
return Money.cents(paisa, "INR");
|
|
124
|
-
}
|
|
125
|
-
static jpy(yen) {
|
|
126
|
-
return Money.cents(yen, "JPY");
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Add two money values (must be same currency)
|
|
130
|
-
*/
|
|
131
|
-
add(other) {
|
|
132
|
-
this.assertSameCurrency(other);
|
|
133
|
-
return new Money(this.amount + other.amount, this.currency);
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Subtract money (must be same currency)
|
|
137
|
-
*/
|
|
138
|
-
subtract(other) {
|
|
139
|
-
this.assertSameCurrency(other);
|
|
140
|
-
return new Money(this.amount - other.amount, this.currency);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Multiply by a factor (rounds to nearest integer)
|
|
144
|
-
*/
|
|
145
|
-
multiply(factor) {
|
|
146
|
-
return new Money(Math.round(this.amount * factor), this.currency);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Divide by a divisor (rounds to nearest integer)
|
|
150
|
-
*/
|
|
151
|
-
divide(divisor) {
|
|
152
|
-
if (divisor === 0) throw new Error("Cannot divide by zero");
|
|
153
|
-
return new Money(Math.round(this.amount / divisor), this.currency);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Calculate percentage
|
|
157
|
-
* @example money.percentage(10) // 10% of money
|
|
158
|
-
*/
|
|
159
|
-
percentage(percent) {
|
|
160
|
-
return this.multiply(percent / 100);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Allocate money among recipients (handles rounding)
|
|
164
|
-
* @example Money.usd(100).allocate([1, 1, 1]) // [34, 33, 33] cents
|
|
165
|
-
*/
|
|
166
|
-
allocate(ratios) {
|
|
167
|
-
const total = ratios.reduce((a, b) => a + b, 0);
|
|
168
|
-
if (total === 0) throw new Error("Ratios must sum to more than 0");
|
|
169
|
-
let remainder = this.amount;
|
|
170
|
-
const results = [];
|
|
171
|
-
for (let i = 0; i < ratios.length; i++) {
|
|
172
|
-
const share = Math.floor(this.amount * ratios[i] / total);
|
|
173
|
-
results.push(new Money(share, this.currency));
|
|
174
|
-
remainder -= share;
|
|
175
|
-
}
|
|
176
|
-
for (let i = 0; i < remainder; i++) results[i] = new Money(results[i].amount + 1, this.currency);
|
|
177
|
-
return results;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Split equally among n recipients
|
|
181
|
-
*/
|
|
182
|
-
split(parts) {
|
|
183
|
-
return this.allocate(Array(parts).fill(1));
|
|
184
|
-
}
|
|
185
|
-
isZero() {
|
|
186
|
-
return this.amount === 0;
|
|
187
|
-
}
|
|
188
|
-
isPositive() {
|
|
189
|
-
return this.amount > 0;
|
|
190
|
-
}
|
|
191
|
-
isNegative() {
|
|
192
|
-
return this.amount < 0;
|
|
193
|
-
}
|
|
194
|
-
equals(other) {
|
|
195
|
-
return this.amount === other.amount && this.currency === other.currency;
|
|
196
|
-
}
|
|
197
|
-
greaterThan(other) {
|
|
198
|
-
this.assertSameCurrency(other);
|
|
199
|
-
return this.amount > other.amount;
|
|
200
|
-
}
|
|
201
|
-
lessThan(other) {
|
|
202
|
-
this.assertSameCurrency(other);
|
|
203
|
-
return this.amount < other.amount;
|
|
204
|
-
}
|
|
205
|
-
greaterThanOrEqual(other) {
|
|
206
|
-
return this.greaterThan(other) || this.equals(other);
|
|
207
|
-
}
|
|
208
|
-
lessThanOrEqual(other) {
|
|
209
|
-
return this.lessThan(other) || this.equals(other);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Get amount in major unit (dollars, taka)
|
|
213
|
-
*/
|
|
214
|
-
toUnit() {
|
|
215
|
-
const config = CURRENCIES[this.currency] ?? { decimals: 2 };
|
|
216
|
-
return this.amount / Math.pow(10, config.decimals);
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Format for display
|
|
220
|
-
* @example Money.usd(1999).format() // "$19.99"
|
|
221
|
-
*/
|
|
222
|
-
format(locale = "en-US") {
|
|
223
|
-
return new Intl.NumberFormat(locale, {
|
|
224
|
-
style: "currency",
|
|
225
|
-
currency: this.currency
|
|
226
|
-
}).format(this.toUnit());
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Format without currency symbol
|
|
230
|
-
*/
|
|
231
|
-
formatAmount(locale = "en-US") {
|
|
232
|
-
const config = CURRENCIES[this.currency] ?? { decimals: 2 };
|
|
233
|
-
return new Intl.NumberFormat(locale, {
|
|
234
|
-
minimumFractionDigits: config.decimals,
|
|
235
|
-
maximumFractionDigits: config.decimals
|
|
236
|
-
}).format(this.toUnit());
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Convert to JSON-serializable object
|
|
240
|
-
*/
|
|
241
|
-
toJSON() {
|
|
242
|
-
return {
|
|
243
|
-
amount: this.amount,
|
|
244
|
-
currency: this.currency
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Create from JSON
|
|
249
|
-
*/
|
|
250
|
-
static fromJSON(json) {
|
|
251
|
-
return new Money(json.amount, json.currency);
|
|
252
|
-
}
|
|
253
|
-
toString() {
|
|
254
|
-
return `${this.currency} ${this.amount}`;
|
|
255
|
-
}
|
|
256
|
-
assertSameCurrency(other) {
|
|
257
|
-
if (this.currency !== other.currency) throw new Error(`Currency mismatch: ${this.currency} vs ${other.currency}. Convert first.`);
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
/**
|
|
261
|
-
* Helper functions for legacy compatibility
|
|
262
|
-
*/
|
|
263
|
-
function toSmallestUnit(amount, currency = "USD") {
|
|
264
|
-
return Money.of(amount, currency).amount;
|
|
265
|
-
}
|
|
266
|
-
function fromSmallestUnit(amount, currency = "USD") {
|
|
267
|
-
return Money.cents(amount, currency).toUnit();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
//#endregion
|
|
271
|
-
export { fromSmallestUnit as n, toSmallestUnit as r, Money as t };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
//#region src/shared/utils/formatters/money.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Money Utility - Integer-safe currency handling
|
|
4
|
-
* @classytic/revenue
|
|
5
|
-
*
|
|
6
|
-
* Never use floating point for money!
|
|
7
|
-
* All amounts stored as smallest unit (cents, paisa, etc.)
|
|
8
|
-
*
|
|
9
|
-
* Inspired by: Stripe, Dinero.js, tc39/proposal-decimal
|
|
10
|
-
*/
|
|
11
|
-
interface MoneyValue {
|
|
12
|
-
/** Amount in smallest currency unit (cents, paisa, etc.) */
|
|
13
|
-
readonly amount: number;
|
|
14
|
-
/** ISO 4217 currency code */
|
|
15
|
-
readonly currency: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Money class - immutable money representation
|
|
19
|
-
*/
|
|
20
|
-
declare class Money implements MoneyValue {
|
|
21
|
-
readonly amount: number;
|
|
22
|
-
readonly currency: string;
|
|
23
|
-
private constructor();
|
|
24
|
-
/**
|
|
25
|
-
* Create money from smallest unit (cents, paisa)
|
|
26
|
-
* @example Money.cents(1999, 'USD') // $19.99
|
|
27
|
-
*/
|
|
28
|
-
static cents(amount: number, currency?: string): Money;
|
|
29
|
-
/**
|
|
30
|
-
* Create money from major unit (dollars, taka)
|
|
31
|
-
* @example Money.of(19.99, 'USD') // $19.99 (stored as 1999 cents)
|
|
32
|
-
*/
|
|
33
|
-
static of(amount: number, currency?: string): Money;
|
|
34
|
-
/**
|
|
35
|
-
* Create zero money
|
|
36
|
-
*/
|
|
37
|
-
static zero(currency?: string): Money;
|
|
38
|
-
static usd(cents: number): Money;
|
|
39
|
-
static eur(cents: number): Money;
|
|
40
|
-
static gbp(pence: number): Money;
|
|
41
|
-
static bdt(paisa: number): Money;
|
|
42
|
-
static inr(paisa: number): Money;
|
|
43
|
-
static jpy(yen: number): Money;
|
|
44
|
-
/**
|
|
45
|
-
* Add two money values (must be same currency)
|
|
46
|
-
*/
|
|
47
|
-
add(other: Money): Money;
|
|
48
|
-
/**
|
|
49
|
-
* Subtract money (must be same currency)
|
|
50
|
-
*/
|
|
51
|
-
subtract(other: Money): Money;
|
|
52
|
-
/**
|
|
53
|
-
* Multiply by a factor (rounds to nearest integer)
|
|
54
|
-
*/
|
|
55
|
-
multiply(factor: number): Money;
|
|
56
|
-
/**
|
|
57
|
-
* Divide by a divisor (rounds to nearest integer)
|
|
58
|
-
*/
|
|
59
|
-
divide(divisor: number): Money;
|
|
60
|
-
/**
|
|
61
|
-
* Calculate percentage
|
|
62
|
-
* @example money.percentage(10) // 10% of money
|
|
63
|
-
*/
|
|
64
|
-
percentage(percent: number): Money;
|
|
65
|
-
/**
|
|
66
|
-
* Allocate money among recipients (handles rounding)
|
|
67
|
-
* @example Money.usd(100).allocate([1, 1, 1]) // [34, 33, 33] cents
|
|
68
|
-
*/
|
|
69
|
-
allocate(ratios: number[]): Money[];
|
|
70
|
-
/**
|
|
71
|
-
* Split equally among n recipients
|
|
72
|
-
*/
|
|
73
|
-
split(parts: number): Money[];
|
|
74
|
-
isZero(): boolean;
|
|
75
|
-
isPositive(): boolean;
|
|
76
|
-
isNegative(): boolean;
|
|
77
|
-
equals(other: Money): boolean;
|
|
78
|
-
greaterThan(other: Money): boolean;
|
|
79
|
-
lessThan(other: Money): boolean;
|
|
80
|
-
greaterThanOrEqual(other: Money): boolean;
|
|
81
|
-
lessThanOrEqual(other: Money): boolean;
|
|
82
|
-
/**
|
|
83
|
-
* Get amount in major unit (dollars, taka)
|
|
84
|
-
*/
|
|
85
|
-
toUnit(): number;
|
|
86
|
-
/**
|
|
87
|
-
* Format for display
|
|
88
|
-
* @example Money.usd(1999).format() // "$19.99"
|
|
89
|
-
*/
|
|
90
|
-
format(locale?: string): string;
|
|
91
|
-
/**
|
|
92
|
-
* Format without currency symbol
|
|
93
|
-
*/
|
|
94
|
-
formatAmount(locale?: string): string;
|
|
95
|
-
/**
|
|
96
|
-
* Convert to JSON-serializable object
|
|
97
|
-
*/
|
|
98
|
-
toJSON(): MoneyValue;
|
|
99
|
-
/**
|
|
100
|
-
* Create from JSON
|
|
101
|
-
*/
|
|
102
|
-
static fromJSON(json: MoneyValue): Money;
|
|
103
|
-
toString(): string;
|
|
104
|
-
private assertSameCurrency;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Helper functions for legacy compatibility
|
|
108
|
-
*/
|
|
109
|
-
declare function toSmallestUnit(amount: number, currency?: string): number;
|
|
110
|
-
declare function fromSmallestUnit(amount: number, currency?: string): number;
|
|
111
|
-
//#endregion
|
|
112
|
-
export { toSmallestUnit as i, MoneyValue as n, fromSmallestUnit as r, Money as t };
|