moneyutils-in 0.1.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/README.md +80 -0
- package/dist/commerce/commission.d.ts +7 -0
- package/dist/commerce/fee.d.ts +7 -0
- package/dist/commerce/order-total.d.ts +34 -0
- package/dist/currency/conversion.d.ts +14 -0
- package/dist/currency/format-rupee.d.ts +5 -0
- package/dist/discount/discount.d.ts +13 -0
- package/dist/gst/gst.d.ts +12 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +308 -0
- package/dist/index.mjs +271 -0
- package/dist/internal/round-money.d.ts +1 -0
- package/dist/internal/validate-amount.d.ts +1 -0
- package/dist/percentage/percentage.d.ts +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# moneyutils-in
|
|
2
|
+
|
|
3
|
+
A lightweight, TypeScript-first utility library for handling INR formatting, GST, discounts, commissions, fees, and common money calculations for Indian e-commerce, booking, and payment applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install moneyutils-in
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
formatINR,
|
|
16
|
+
addGST,
|
|
17
|
+
calculatePercentageDiscount,
|
|
18
|
+
calculateCommission,
|
|
19
|
+
} from "moneyutils-in";
|
|
20
|
+
|
|
21
|
+
formatINR(125000);
|
|
22
|
+
// ₹1,25,000.00
|
|
23
|
+
|
|
24
|
+
addGST(1000, 18);
|
|
25
|
+
// Total: ₹1,180
|
|
26
|
+
|
|
27
|
+
calculatePercentageDiscount(2000, 15);
|
|
28
|
+
// Final amount: ₹1,700
|
|
29
|
+
|
|
30
|
+
calculateCommission(5000, 12);
|
|
31
|
+
// Commission: ₹600
|
|
32
|
+
// Net amount: ₹4,400
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- INR formatting with number grouping
|
|
38
|
+
- Rupee ↔ paise conversion
|
|
39
|
+
- GST calculations
|
|
40
|
+
- CGST, SGST and IGST splitting
|
|
41
|
+
- GST-inclusive price extraction
|
|
42
|
+
- Percentage and fixed discounts
|
|
43
|
+
- Percentage calculations
|
|
44
|
+
- Platform and convenience fees
|
|
45
|
+
- Marketplace commissions
|
|
46
|
+
- Cart/order total calculations
|
|
47
|
+
- TypeScript support
|
|
48
|
+
- ESM and CommonJS support
|
|
49
|
+
- Zero runtime dependencies
|
|
50
|
+
|
|
51
|
+
## Example: Order Calculation
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { calculateOrderTotal } from "moneyutils-in";
|
|
55
|
+
|
|
56
|
+
const total = calculateOrderTotal({
|
|
57
|
+
items: [
|
|
58
|
+
{ price: 999, quantity: 2 },
|
|
59
|
+
{ price: 2499, quantity: 1 },
|
|
60
|
+
],
|
|
61
|
+
discount: {
|
|
62
|
+
type: "percentage",
|
|
63
|
+
value: 10,
|
|
64
|
+
},
|
|
65
|
+
fees: [{ name: "Delivery", amount: 50 }],
|
|
66
|
+
tax: {
|
|
67
|
+
rate: 18,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.log(total.grandTotal);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Disclaimer
|
|
75
|
+
|
|
76
|
+
GST and tax utilities perform calculations based on the values provided by the developer. They do not determine the legally applicable tax rate or tax treatment for a transaction.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface OrderItem {
|
|
2
|
+
price: number;
|
|
3
|
+
quantity: number;
|
|
4
|
+
}
|
|
5
|
+
export interface OrderFee {
|
|
6
|
+
name: string;
|
|
7
|
+
amount: number;
|
|
8
|
+
}
|
|
9
|
+
export type OrderDiscount = {
|
|
10
|
+
type: "percentage";
|
|
11
|
+
value: number;
|
|
12
|
+
} | {
|
|
13
|
+
type: "fixed";
|
|
14
|
+
value: number;
|
|
15
|
+
};
|
|
16
|
+
export interface OrderTax {
|
|
17
|
+
rate: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CalculateOrderTotalInput {
|
|
20
|
+
items: OrderItem[];
|
|
21
|
+
discount?: OrderDiscount;
|
|
22
|
+
fees?: OrderFee[];
|
|
23
|
+
tax?: OrderTax;
|
|
24
|
+
}
|
|
25
|
+
export interface OrderTotalResult {
|
|
26
|
+
subtotal: number;
|
|
27
|
+
discountAmount: number;
|
|
28
|
+
subtotalAfterDiscount: number;
|
|
29
|
+
feeTotal: number;
|
|
30
|
+
taxableAmount: number;
|
|
31
|
+
taxAmount: number;
|
|
32
|
+
grandTotal: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function calculateOrderTotal(input: CalculateOrderTotalInput): OrderTotalResult;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts rupees to paise.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* rupeesToPaise(499.99) // 49999
|
|
6
|
+
*/
|
|
7
|
+
export declare function rupeesToPaise(rupees: number): number;
|
|
8
|
+
/**
|
|
9
|
+
* Converts paise to rupees.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* paiseToRupees(49999) // 499.99
|
|
13
|
+
*/
|
|
14
|
+
export declare function paiseToRupees(paise: number): number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DiscountResult {
|
|
2
|
+
originalAmount: number;
|
|
3
|
+
discountAmount: number;
|
|
4
|
+
finalAmount: number;
|
|
5
|
+
percentage: number;
|
|
6
|
+
}
|
|
7
|
+
export interface FixedDiscountResult {
|
|
8
|
+
originalAmount: number;
|
|
9
|
+
discountAmount: number;
|
|
10
|
+
finalAmount: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function calculatePercentageDiscount(amount: number, percentage: number): DiscountResult;
|
|
13
|
+
export declare function calculateFixedDiscount(amount: number, discount: number): FixedDiscountResult;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type GSTTransactionType = "INTRA_STATE" | "INTER_STATE";
|
|
2
|
+
export interface GSTResult {
|
|
3
|
+
taxableAmount: number;
|
|
4
|
+
gstRate: number;
|
|
5
|
+
cgst: number;
|
|
6
|
+
sgst: number;
|
|
7
|
+
igst: number;
|
|
8
|
+
totalTax: number;
|
|
9
|
+
totalAmount: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function addGST(amount: number, gstRate: number, transactionType?: GSTTransactionType): GSTResult;
|
|
12
|
+
export declare function removeGST(inclusiveAmount: number, gstRate: number, transactionType?: GSTTransactionType): GSTResult;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { formatINR, type FormatINROptions } from "./currency/format-rupee.js";
|
|
2
|
+
export { rupeesToPaise, paiseToRupees } from "./currency/conversion.js";
|
|
3
|
+
export { calculatePercentageDiscount, calculateFixedDiscount, type DiscountResult, type FixedDiscountResult } from "./discount/discount.js";
|
|
4
|
+
export { addGST, removeGST, type GSTResult, type GSTTransactionType } from "./gst/gst.js";
|
|
5
|
+
export { calculateOrderTotal, type OrderItem, type OrderFee, type OrderDiscount, type OrderTax, type CalculateOrderTotalInput, type OrderTotalResult } from "./commerce/order-total.js";
|
|
6
|
+
export { calculatePercentage } from "./percentage/percentage.js";
|
|
7
|
+
export { calculateCommission, type CommissionResult } from "./commerce/commission.js";
|
|
8
|
+
export { calculatePercentageFee, type PercentageFeeResult } from "./commerce/fee.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
addGST: () => addGST,
|
|
24
|
+
calculateCommission: () => calculateCommission,
|
|
25
|
+
calculateFixedDiscount: () => calculateFixedDiscount,
|
|
26
|
+
calculateOrderTotal: () => calculateOrderTotal,
|
|
27
|
+
calculatePercentage: () => calculatePercentage,
|
|
28
|
+
calculatePercentageDiscount: () => calculatePercentageDiscount,
|
|
29
|
+
calculatePercentageFee: () => calculatePercentageFee,
|
|
30
|
+
formatINR: () => formatINR,
|
|
31
|
+
paiseToRupees: () => paiseToRupees,
|
|
32
|
+
removeGST: () => removeGST,
|
|
33
|
+
rupeesToPaise: () => rupeesToPaise
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/currency/format-rupee.ts
|
|
38
|
+
function formatINR(amount, options = {}) {
|
|
39
|
+
const {
|
|
40
|
+
decimals = 2,
|
|
41
|
+
symbol = true
|
|
42
|
+
} = options;
|
|
43
|
+
if (!Number.isFinite(amount)) {
|
|
44
|
+
throw new TypeError("Amount must be a finite number");
|
|
45
|
+
}
|
|
46
|
+
const formatted = new Intl.NumberFormat("en-IN", {
|
|
47
|
+
minimumFractionDigits: decimals,
|
|
48
|
+
maximumFractionDigits: decimals
|
|
49
|
+
}).format(amount);
|
|
50
|
+
return symbol ? `\u20B9${formatted}` : formatted;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/currency/conversion.ts
|
|
54
|
+
function rupeesToPaise(rupees) {
|
|
55
|
+
if (!Number.isFinite(rupees)) {
|
|
56
|
+
throw new TypeError("Amount must be a finite number");
|
|
57
|
+
}
|
|
58
|
+
return Math.round(rupees * 100);
|
|
59
|
+
}
|
|
60
|
+
function paiseToRupees(paise) {
|
|
61
|
+
if (!Number.isFinite(paise)) {
|
|
62
|
+
throw new TypeError("Amount must be a finite number");
|
|
63
|
+
}
|
|
64
|
+
if (!Number.isInteger(paise)) {
|
|
65
|
+
throw new TypeError("Paise must be an integer");
|
|
66
|
+
}
|
|
67
|
+
return paise / 100;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/internal/round-money.ts
|
|
71
|
+
function roundMoney(value) {
|
|
72
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/internal/validate-amount.ts
|
|
76
|
+
function validateAmount(amount, name = "Amount") {
|
|
77
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
78
|
+
throw new TypeError(
|
|
79
|
+
`${name} must be a non-negative finite number`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/discount/discount.ts
|
|
85
|
+
function calculatePercentageDiscount(amount, percentage) {
|
|
86
|
+
validateAmount(amount);
|
|
87
|
+
if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) {
|
|
88
|
+
throw new RangeError(
|
|
89
|
+
"Percentage must be between 0 and 100"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
const discountAmount = roundMoney(
|
|
93
|
+
amount * percentage / 100
|
|
94
|
+
);
|
|
95
|
+
const finalAmount = roundMoney(
|
|
96
|
+
amount - discountAmount
|
|
97
|
+
);
|
|
98
|
+
return {
|
|
99
|
+
originalAmount: amount,
|
|
100
|
+
discountAmount,
|
|
101
|
+
finalAmount,
|
|
102
|
+
percentage
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function calculateFixedDiscount(amount, discount) {
|
|
106
|
+
validateAmount(amount);
|
|
107
|
+
validateAmount(discount, "Discount");
|
|
108
|
+
const discountAmount = Math.min(
|
|
109
|
+
discount,
|
|
110
|
+
amount
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
originalAmount: amount,
|
|
114
|
+
discountAmount,
|
|
115
|
+
finalAmount: roundMoney(
|
|
116
|
+
amount - discountAmount
|
|
117
|
+
)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/gst/gst.ts
|
|
122
|
+
function validateGST(amount, gstRate) {
|
|
123
|
+
validateAmount(amount);
|
|
124
|
+
if (!Number.isFinite(gstRate) || gstRate < 0 || gstRate > 100) {
|
|
125
|
+
throw new RangeError(
|
|
126
|
+
"GST rate must be between 0 and 100"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function addGST(amount, gstRate, transactionType = "INTRA_STATE") {
|
|
131
|
+
validateGST(amount, gstRate);
|
|
132
|
+
const totalTax = roundMoney(amount * gstRate / 100);
|
|
133
|
+
const cgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax / 2) : 0;
|
|
134
|
+
const sgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax - cgst) : 0;
|
|
135
|
+
const igst = transactionType === "INTER_STATE" ? totalTax : 0;
|
|
136
|
+
return {
|
|
137
|
+
taxableAmount: amount,
|
|
138
|
+
gstRate,
|
|
139
|
+
cgst,
|
|
140
|
+
sgst,
|
|
141
|
+
igst,
|
|
142
|
+
totalTax,
|
|
143
|
+
totalAmount: roundMoney(amount + totalTax)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function removeGST(inclusiveAmount, gstRate, transactionType = "INTRA_STATE") {
|
|
147
|
+
validateGST(inclusiveAmount, gstRate);
|
|
148
|
+
const taxableAmount = roundMoney(inclusiveAmount / (1 + gstRate / 100));
|
|
149
|
+
const totalTax = roundMoney(inclusiveAmount - taxableAmount);
|
|
150
|
+
const cgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax / 2) : 0;
|
|
151
|
+
const sgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax - cgst) : 0;
|
|
152
|
+
const igst = transactionType === "INTER_STATE" ? totalTax : 0;
|
|
153
|
+
return {
|
|
154
|
+
taxableAmount,
|
|
155
|
+
gstRate,
|
|
156
|
+
cgst,
|
|
157
|
+
sgst,
|
|
158
|
+
igst,
|
|
159
|
+
totalTax,
|
|
160
|
+
totalAmount: inclusiveAmount
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/commerce/order-total.ts
|
|
165
|
+
function calculateOrderTotal(input) {
|
|
166
|
+
const {
|
|
167
|
+
items,
|
|
168
|
+
discount,
|
|
169
|
+
fees = [],
|
|
170
|
+
tax
|
|
171
|
+
} = input;
|
|
172
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
173
|
+
throw new TypeError("Items must contain at least one item");
|
|
174
|
+
}
|
|
175
|
+
let subtotal = 0;
|
|
176
|
+
for (const item of items) {
|
|
177
|
+
validateAmount(item.price, "Item price");
|
|
178
|
+
if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
|
|
179
|
+
throw new TypeError(
|
|
180
|
+
"Item quantity must be a positive integer"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
subtotal += item.price * item.quantity;
|
|
184
|
+
}
|
|
185
|
+
subtotal = roundMoney(subtotal);
|
|
186
|
+
let discountAmount = 0;
|
|
187
|
+
if (discount) {
|
|
188
|
+
validateAmount(discount.value, "Discount");
|
|
189
|
+
if (discount.type === "percentage") {
|
|
190
|
+
if (discount.value > 100) {
|
|
191
|
+
throw new RangeError(
|
|
192
|
+
"Percentage discount cannot exceed 100"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
discountAmount = roundMoney(
|
|
196
|
+
subtotal * discount.value / 100
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
discountAmount = Math.min(
|
|
200
|
+
discount.value,
|
|
201
|
+
subtotal
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const subtotalAfterDiscount = roundMoney(
|
|
206
|
+
subtotal - discountAmount
|
|
207
|
+
);
|
|
208
|
+
let feeTotal = 0;
|
|
209
|
+
for (const fee of fees) {
|
|
210
|
+
validateAmount(fee.amount, `Fee "${fee.name}"`);
|
|
211
|
+
feeTotal += fee.amount;
|
|
212
|
+
}
|
|
213
|
+
feeTotal = roundMoney(feeTotal);
|
|
214
|
+
const taxableAmount = roundMoney(
|
|
215
|
+
subtotalAfterDiscount + feeTotal
|
|
216
|
+
);
|
|
217
|
+
let taxAmount = 0;
|
|
218
|
+
if (tax) {
|
|
219
|
+
validateAmount(tax.rate, "Tax rate");
|
|
220
|
+
if (tax.rate > 100) {
|
|
221
|
+
throw new RangeError(
|
|
222
|
+
"Tax rate cannot exceed 100"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
taxAmount = roundMoney(
|
|
226
|
+
taxableAmount * tax.rate / 100
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const grandTotal = roundMoney(
|
|
230
|
+
taxableAmount + taxAmount
|
|
231
|
+
);
|
|
232
|
+
return {
|
|
233
|
+
subtotal,
|
|
234
|
+
discountAmount,
|
|
235
|
+
subtotalAfterDiscount,
|
|
236
|
+
feeTotal,
|
|
237
|
+
taxableAmount,
|
|
238
|
+
taxAmount,
|
|
239
|
+
grandTotal
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/percentage/percentage.ts
|
|
244
|
+
function calculatePercentage(amount, percentage) {
|
|
245
|
+
validateAmount(amount);
|
|
246
|
+
validateAmount(percentage, "Percentage");
|
|
247
|
+
return roundMoney(amount * percentage / 100);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/commerce/commission.ts
|
|
251
|
+
function calculateCommission(amount, commissionRate) {
|
|
252
|
+
validateAmount(amount);
|
|
253
|
+
validateAmount(commissionRate, "Commission rate");
|
|
254
|
+
if (commissionRate > 100) {
|
|
255
|
+
throw new RangeError(
|
|
256
|
+
"Commission rate cannot exceed 100"
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const commissionAmount = calculatePercentage(
|
|
260
|
+
amount,
|
|
261
|
+
commissionRate
|
|
262
|
+
);
|
|
263
|
+
return {
|
|
264
|
+
amount,
|
|
265
|
+
commissionRate,
|
|
266
|
+
commissionAmount,
|
|
267
|
+
netAmount: roundMoney(
|
|
268
|
+
amount - commissionAmount
|
|
269
|
+
)
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/commerce/fee.ts
|
|
274
|
+
function calculatePercentageFee(amount, feeRate) {
|
|
275
|
+
validateAmount(amount);
|
|
276
|
+
validateAmount(feeRate, "Fee rate");
|
|
277
|
+
if (feeRate > 100) {
|
|
278
|
+
throw new RangeError(
|
|
279
|
+
"Fee rate cannot exceed 100"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const feeAmount = calculatePercentage(
|
|
283
|
+
amount,
|
|
284
|
+
feeRate
|
|
285
|
+
);
|
|
286
|
+
return {
|
|
287
|
+
baseAmount: amount,
|
|
288
|
+
feeRate,
|
|
289
|
+
feeAmount,
|
|
290
|
+
totalAmount: roundMoney(
|
|
291
|
+
amount + feeAmount
|
|
292
|
+
)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
296
|
+
0 && (module.exports = {
|
|
297
|
+
addGST,
|
|
298
|
+
calculateCommission,
|
|
299
|
+
calculateFixedDiscount,
|
|
300
|
+
calculateOrderTotal,
|
|
301
|
+
calculatePercentage,
|
|
302
|
+
calculatePercentageDiscount,
|
|
303
|
+
calculatePercentageFee,
|
|
304
|
+
formatINR,
|
|
305
|
+
paiseToRupees,
|
|
306
|
+
removeGST,
|
|
307
|
+
rupeesToPaise
|
|
308
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// src/currency/format-rupee.ts
|
|
2
|
+
function formatINR(amount, options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
decimals = 2,
|
|
5
|
+
symbol = true
|
|
6
|
+
} = options;
|
|
7
|
+
if (!Number.isFinite(amount)) {
|
|
8
|
+
throw new TypeError("Amount must be a finite number");
|
|
9
|
+
}
|
|
10
|
+
const formatted = new Intl.NumberFormat("en-IN", {
|
|
11
|
+
minimumFractionDigits: decimals,
|
|
12
|
+
maximumFractionDigits: decimals
|
|
13
|
+
}).format(amount);
|
|
14
|
+
return symbol ? `\u20B9${formatted}` : formatted;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/currency/conversion.ts
|
|
18
|
+
function rupeesToPaise(rupees) {
|
|
19
|
+
if (!Number.isFinite(rupees)) {
|
|
20
|
+
throw new TypeError("Amount must be a finite number");
|
|
21
|
+
}
|
|
22
|
+
return Math.round(rupees * 100);
|
|
23
|
+
}
|
|
24
|
+
function paiseToRupees(paise) {
|
|
25
|
+
if (!Number.isFinite(paise)) {
|
|
26
|
+
throw new TypeError("Amount must be a finite number");
|
|
27
|
+
}
|
|
28
|
+
if (!Number.isInteger(paise)) {
|
|
29
|
+
throw new TypeError("Paise must be an integer");
|
|
30
|
+
}
|
|
31
|
+
return paise / 100;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/internal/round-money.ts
|
|
35
|
+
function roundMoney(value) {
|
|
36
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/internal/validate-amount.ts
|
|
40
|
+
function validateAmount(amount, name = "Amount") {
|
|
41
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
42
|
+
throw new TypeError(
|
|
43
|
+
`${name} must be a non-negative finite number`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/discount/discount.ts
|
|
49
|
+
function calculatePercentageDiscount(amount, percentage) {
|
|
50
|
+
validateAmount(amount);
|
|
51
|
+
if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) {
|
|
52
|
+
throw new RangeError(
|
|
53
|
+
"Percentage must be between 0 and 100"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const discountAmount = roundMoney(
|
|
57
|
+
amount * percentage / 100
|
|
58
|
+
);
|
|
59
|
+
const finalAmount = roundMoney(
|
|
60
|
+
amount - discountAmount
|
|
61
|
+
);
|
|
62
|
+
return {
|
|
63
|
+
originalAmount: amount,
|
|
64
|
+
discountAmount,
|
|
65
|
+
finalAmount,
|
|
66
|
+
percentage
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function calculateFixedDiscount(amount, discount) {
|
|
70
|
+
validateAmount(amount);
|
|
71
|
+
validateAmount(discount, "Discount");
|
|
72
|
+
const discountAmount = Math.min(
|
|
73
|
+
discount,
|
|
74
|
+
amount
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
originalAmount: amount,
|
|
78
|
+
discountAmount,
|
|
79
|
+
finalAmount: roundMoney(
|
|
80
|
+
amount - discountAmount
|
|
81
|
+
)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/gst/gst.ts
|
|
86
|
+
function validateGST(amount, gstRate) {
|
|
87
|
+
validateAmount(amount);
|
|
88
|
+
if (!Number.isFinite(gstRate) || gstRate < 0 || gstRate > 100) {
|
|
89
|
+
throw new RangeError(
|
|
90
|
+
"GST rate must be between 0 and 100"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function addGST(amount, gstRate, transactionType = "INTRA_STATE") {
|
|
95
|
+
validateGST(amount, gstRate);
|
|
96
|
+
const totalTax = roundMoney(amount * gstRate / 100);
|
|
97
|
+
const cgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax / 2) : 0;
|
|
98
|
+
const sgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax - cgst) : 0;
|
|
99
|
+
const igst = transactionType === "INTER_STATE" ? totalTax : 0;
|
|
100
|
+
return {
|
|
101
|
+
taxableAmount: amount,
|
|
102
|
+
gstRate,
|
|
103
|
+
cgst,
|
|
104
|
+
sgst,
|
|
105
|
+
igst,
|
|
106
|
+
totalTax,
|
|
107
|
+
totalAmount: roundMoney(amount + totalTax)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function removeGST(inclusiveAmount, gstRate, transactionType = "INTRA_STATE") {
|
|
111
|
+
validateGST(inclusiveAmount, gstRate);
|
|
112
|
+
const taxableAmount = roundMoney(inclusiveAmount / (1 + gstRate / 100));
|
|
113
|
+
const totalTax = roundMoney(inclusiveAmount - taxableAmount);
|
|
114
|
+
const cgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax / 2) : 0;
|
|
115
|
+
const sgst = transactionType === "INTRA_STATE" ? roundMoney(totalTax - cgst) : 0;
|
|
116
|
+
const igst = transactionType === "INTER_STATE" ? totalTax : 0;
|
|
117
|
+
return {
|
|
118
|
+
taxableAmount,
|
|
119
|
+
gstRate,
|
|
120
|
+
cgst,
|
|
121
|
+
sgst,
|
|
122
|
+
igst,
|
|
123
|
+
totalTax,
|
|
124
|
+
totalAmount: inclusiveAmount
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/commerce/order-total.ts
|
|
129
|
+
function calculateOrderTotal(input) {
|
|
130
|
+
const {
|
|
131
|
+
items,
|
|
132
|
+
discount,
|
|
133
|
+
fees = [],
|
|
134
|
+
tax
|
|
135
|
+
} = input;
|
|
136
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
137
|
+
throw new TypeError("Items must contain at least one item");
|
|
138
|
+
}
|
|
139
|
+
let subtotal = 0;
|
|
140
|
+
for (const item of items) {
|
|
141
|
+
validateAmount(item.price, "Item price");
|
|
142
|
+
if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
"Item quantity must be a positive integer"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
subtotal += item.price * item.quantity;
|
|
148
|
+
}
|
|
149
|
+
subtotal = roundMoney(subtotal);
|
|
150
|
+
let discountAmount = 0;
|
|
151
|
+
if (discount) {
|
|
152
|
+
validateAmount(discount.value, "Discount");
|
|
153
|
+
if (discount.type === "percentage") {
|
|
154
|
+
if (discount.value > 100) {
|
|
155
|
+
throw new RangeError(
|
|
156
|
+
"Percentage discount cannot exceed 100"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
discountAmount = roundMoney(
|
|
160
|
+
subtotal * discount.value / 100
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
discountAmount = Math.min(
|
|
164
|
+
discount.value,
|
|
165
|
+
subtotal
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const subtotalAfterDiscount = roundMoney(
|
|
170
|
+
subtotal - discountAmount
|
|
171
|
+
);
|
|
172
|
+
let feeTotal = 0;
|
|
173
|
+
for (const fee of fees) {
|
|
174
|
+
validateAmount(fee.amount, `Fee "${fee.name}"`);
|
|
175
|
+
feeTotal += fee.amount;
|
|
176
|
+
}
|
|
177
|
+
feeTotal = roundMoney(feeTotal);
|
|
178
|
+
const taxableAmount = roundMoney(
|
|
179
|
+
subtotalAfterDiscount + feeTotal
|
|
180
|
+
);
|
|
181
|
+
let taxAmount = 0;
|
|
182
|
+
if (tax) {
|
|
183
|
+
validateAmount(tax.rate, "Tax rate");
|
|
184
|
+
if (tax.rate > 100) {
|
|
185
|
+
throw new RangeError(
|
|
186
|
+
"Tax rate cannot exceed 100"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
taxAmount = roundMoney(
|
|
190
|
+
taxableAmount * tax.rate / 100
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const grandTotal = roundMoney(
|
|
194
|
+
taxableAmount + taxAmount
|
|
195
|
+
);
|
|
196
|
+
return {
|
|
197
|
+
subtotal,
|
|
198
|
+
discountAmount,
|
|
199
|
+
subtotalAfterDiscount,
|
|
200
|
+
feeTotal,
|
|
201
|
+
taxableAmount,
|
|
202
|
+
taxAmount,
|
|
203
|
+
grandTotal
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/percentage/percentage.ts
|
|
208
|
+
function calculatePercentage(amount, percentage) {
|
|
209
|
+
validateAmount(amount);
|
|
210
|
+
validateAmount(percentage, "Percentage");
|
|
211
|
+
return roundMoney(amount * percentage / 100);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/commerce/commission.ts
|
|
215
|
+
function calculateCommission(amount, commissionRate) {
|
|
216
|
+
validateAmount(amount);
|
|
217
|
+
validateAmount(commissionRate, "Commission rate");
|
|
218
|
+
if (commissionRate > 100) {
|
|
219
|
+
throw new RangeError(
|
|
220
|
+
"Commission rate cannot exceed 100"
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const commissionAmount = calculatePercentage(
|
|
224
|
+
amount,
|
|
225
|
+
commissionRate
|
|
226
|
+
);
|
|
227
|
+
return {
|
|
228
|
+
amount,
|
|
229
|
+
commissionRate,
|
|
230
|
+
commissionAmount,
|
|
231
|
+
netAmount: roundMoney(
|
|
232
|
+
amount - commissionAmount
|
|
233
|
+
)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/commerce/fee.ts
|
|
238
|
+
function calculatePercentageFee(amount, feeRate) {
|
|
239
|
+
validateAmount(amount);
|
|
240
|
+
validateAmount(feeRate, "Fee rate");
|
|
241
|
+
if (feeRate > 100) {
|
|
242
|
+
throw new RangeError(
|
|
243
|
+
"Fee rate cannot exceed 100"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const feeAmount = calculatePercentage(
|
|
247
|
+
amount,
|
|
248
|
+
feeRate
|
|
249
|
+
);
|
|
250
|
+
return {
|
|
251
|
+
baseAmount: amount,
|
|
252
|
+
feeRate,
|
|
253
|
+
feeAmount,
|
|
254
|
+
totalAmount: roundMoney(
|
|
255
|
+
amount + feeAmount
|
|
256
|
+
)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
addGST,
|
|
261
|
+
calculateCommission,
|
|
262
|
+
calculateFixedDiscount,
|
|
263
|
+
calculateOrderTotal,
|
|
264
|
+
calculatePercentage,
|
|
265
|
+
calculatePercentageDiscount,
|
|
266
|
+
calculatePercentageFee,
|
|
267
|
+
formatINR,
|
|
268
|
+
paiseToRupees,
|
|
269
|
+
removeGST,
|
|
270
|
+
rupeesToPaise
|
|
271
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function roundMoney(value: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateAmount(amount: number, name?: string): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function calculatePercentage(amount: number, percentage: number): number;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moneyutils-in",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript-first money utilities for INR formatting, GST, discounts, taxes, payments, and calculations for Indian e-commerce and booking applications.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"test:run": "vitest run",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"prepublishOnly": "npm run typecheck && npm run test:run && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/Dheemanthshenoy/moneyutils-in.git"
|
|
29
|
+
},
|
|
30
|
+
"author": "Dheemanth Shenoy",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/Dheemanthshenoy/moneyutils-in/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/Dheemanthshenoy/moneyutils-in#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"money",
|
|
38
|
+
"inr",
|
|
39
|
+
"india",
|
|
40
|
+
"currency",
|
|
41
|
+
"gst",
|
|
42
|
+
"discount",
|
|
43
|
+
"tax",
|
|
44
|
+
"ecommerce",
|
|
45
|
+
"booking",
|
|
46
|
+
"payments",
|
|
47
|
+
"typescript"
|
|
48
|
+
],
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^26.6.1",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"typescript": "^7.0.2",
|
|
53
|
+
"vitest": "^5.0.1"
|
|
54
|
+
}
|
|
55
|
+
}
|