Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[ATLAS-123]: add denomination conversion apis in currency module #63

Merged
merged 15 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/funny-dingos-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@razorpay/i18nify-js': patch
---

[feat]: add major/minor conversion apis in currency module
145 changes: 49 additions & 96 deletions packages/i18nify-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Welcome to the command center for your i18n experience! This module serves as th

#### Functions

**setState(newState: Partial`<I18nState>`):** Customize and update your i18n state with ease! Whether you're changing locales or tweaking directions, this function is your ticket to tailor your i18n experience precisely how you want it! 🎨
**setState(newState: Partial `<I18nState>`):** Customize and update your i18n state with ease! Whether you're changing locales or tweaking directions, this function is your ticket to tailor your i18n experience precisely how you want it! 🎨

```
import { setState } from "@razorpay/i18nify-js/core";
Expand Down Expand Up @@ -68,6 +68,28 @@ resetState();

This module's your go-to guru for everything currency/number-related. 🤑 It's all about formatting, validations, and handy tricks to make dealing with money/numbers a breeze. Here are the cool APIs and utilities this Currency Module gives you to play with! 🚀💸

#### convertToMajorUnit(amount, options)

💵🔄 This function is your go-to tool for scaling currency values from lower to major units. Just input the amount in a minor unit (like cents or pence) along with the currency code, and voilà! You get the amount in a major unit (like dollars or pounds). And if you stumble upon an unsupported currency code, it'll promptly let you know by throwing an error.

##### Examples

```javascript
console.log(convertToMajorUnit(10000, 'USD')); // Outputs the amount in dollars for 10000 cents (e.g., 100.00)
console.log(convertToMajorUnit(5000, 'GBP')); // Converts 5000 pence to pounds (e.g., 50.00)
```

#### convertToMinorUnit(amount, options)

💵🔄 This function is your go-to tool for scaling currency values from higher to minor units. Just input the amount in a major unit (like dollars or pounds) along with the currency code, and voilà! You get the amount in a minor unit (like cents or pence). And if you stumble upon an unsupported currency code, it'll promptly let you know by throwing an error.

##### Examples

```javascript
console.log(convertToMinorUnit(100, 'USD')); // Outputs the amount in cents for 10000 dollars (e.g., 10000)
console.log(convertToMinorUnit(50, 'GBP')); // Converts 50 pounds to pence (e.g., 5000)
```

#### formatNumber(amount, options)

🎩✨ This little wizard helps you jazz up numerical values in all sorts of fancy ways. And guess what? It uses the Internationalization API (Intl) to sprinkle that magic dust and give you snazzy, locale-specific number formats—especially for currencies! 🌟💸
Expand Down Expand Up @@ -105,101 +127,32 @@ console.log(

```
console.log(getCurrencyList()); /* {
AED: { symbol: 'د.إ', name: 'United Arab Emirates Dirham' },
ALL: { symbol: 'Lek', name: 'Albanian Lek' },
AMD: { symbol: '֏', name: 'Armenian Dram' },
ARS: { symbol: 'ARS', name: 'Argentine Peso' },
AUD: { symbol: 'A$', name: 'Australian Dollar' },
AWG: { symbol: 'Afl.', name: 'Aruban Florin' },
BBD: { symbol: '$', name: 'Barbadian Dollar' },
BDT: { symbol: '৳', name: 'Bangladeshi Taka' },
BMD: { symbol: '$', name: 'Bermudian Dollar' },
BND: { symbol: 'BND', name: 'Brunei Dollar' },
BOB: { symbol: 'Bs', name: 'Bolivian Boliviano' },
BSD: { symbol: 'B$', name: 'Bahamian Dollar' },
BWP: { symbol: 'P', name: 'Botswanan Pula' },
BZD: { symbol: 'BZ$', name: 'Belize Dollar' },
CAD: { symbol: 'C$', name: 'Canadian Dollar' },
CHF: { symbol: 'CHf', name: 'Swiss Franc' },
CNY: { symbol: '¥', name: 'Chinese Yuan' },
COP: { symbol: 'COL$', name: 'Colombian Peso' },
CRC: { symbol: '₡', name: 'Costa Rican Colón' },
CUP: { symbol: '$MN', name: 'Cuban Peso' },
CZK: { symbol: 'Kč', name: 'Czech Koruna' },
DKK: { symbol: 'DKK', name: 'Danish Krone' },
DOP: { symbol: 'RD$', name: 'Dominican Peso' },
DZD: { symbol: 'د.ج', name: 'Algerian Dinar' },
EGP: { symbol: 'E£', name: 'Egyptian Pound' },
ETB: { symbol: 'ብር', name: 'Ethiopian Birr' },
EUR: { symbol: '€', name: 'Euro' },
FJD: { symbol: 'FJ$', name: 'Fijian Dollar' },
GBP: { symbol: '£', name: 'British Pound' },
GHS: { symbol: 'GH₵', name: 'Ghanaian Cedi' },
GIP: { symbol: 'GIP', name: 'Gibraltar Pound' },
GMD: { symbol: 'D', name: 'Gambian Dalasi' },
GTQ: { symbol: 'Q', name: 'Guatemalan Quetzal' },
GYD: { symbol: 'G$', name: 'Guyanese Dollar' },
HKD: { symbol: 'HK$', name: 'Hong Kong Dollar' },
HNL: { symbol: 'HNL', name: 'Honduran Lempira' },
HRK: { symbol: 'kn', name: 'Croatian Kuna' },
HTG: { symbol: 'G', name: 'Haitian Gourde' },
HUF: { symbol: 'Ft', name: 'Hungarian Forint' },
IDR: { symbol: 'Rp', name: 'Indonesian Rupiah' },
ILS: { symbol: '₪', name: 'Israeli New Shekel' },
INR: { symbol: '₹', name: 'Indian Rupee' },
JMD: { symbol: 'J$', name: 'Jamaican Dollar' },
KES: { symbol: 'Ksh', name: 'Kenyan Shilling' },
KGS: { symbol: 'Лв', name: 'Kyrgystani Som' },
KHR: { symbol: '៛', name: 'Cambodian Riel' },
KYD: { symbol: 'CI$', name: 'Cayman Islands Dollar' },
KZT: { symbol: '₸', name: 'Kazakhstani Tenge' },
LAK: { symbol: '₭', name: 'Laotian Kip' },
LKR: { symbol: 'රු', name: 'Sri Lankan Rupee' },
LRD: { symbol: 'L$', name: 'Liberian Dollar' },
LSL: { symbol: 'LSL', name: 'Lesotho Loti' },
MAD: { symbol: 'د.م.', name: 'Moroccan Dirham' },
MDL: { symbol: 'MDL', name: 'Moldovan Leu' },
MKD: { symbol: 'ден', name: 'Macedonian Denar' },
MMK: { symbol: 'MMK', name: 'Myanmar Kyat' },
MNT: { symbol: '₮', name: 'Mongolian Tugrik' },
MOP: { symbol: 'MOP$', name: 'Macanese Pataca' },
MUR: { symbol: '₨', name: 'Mauritian Rupee' },
MVR: { symbol: 'Rf', name: 'Maldivian Rufiyaa' },
MWK: { symbol: 'MK', name: 'Malawian Kwacha' },
MXN: { symbol: 'Mex$', name: 'Mexican Peso' },
MYR: { symbol: 'RM', name: 'Malaysian Ringgit' },
NAD: { symbol: 'N$', name: 'Namibian Dollar' },
NGN: { symbol: '₦', name: 'Nigerian Naira' },
NIO: { symbol: 'NIO', name: 'Nicaraguan Córdoba' },
NOK: { symbol: 'NOK', name: 'Norwegian Krone' },
NPR: { symbol: 'रू', name: 'Nepalese Rupee' },
NZD: { symbol: 'NZ$', name: 'New Zealand Dollar' },
PEN: { symbol: 'S/', name: 'Peruvian Nuevo Sol' },
PGK: { symbol: 'PGK', name: 'Papua New Guinean Kina' },
PHP: { symbol: '₱', name: 'Philippine Peso' },
PKR: { symbol: '₨', name: 'Pakistani Rupee' },
QAR: { symbol: 'QR', name: 'Qatari Riyal' },
RUB: { symbol: '₽', name: 'Russian Ruble' },
SAR: { symbol: 'SR', name: 'Saudi Riyal' },
SCR: { symbol: 'SRe', name: 'Seychellois Rupee' },
SEK: { symbol: 'SEK', name: 'Swedish Krona' },
SGD: { symbol: 'S$', name: 'Singapore Dollar' },
SLL: { symbol: 'Le', name: 'Sierra Leonean Leone' },
SOS: { symbol: 'Sh.so.', name: 'Somali Shilling' },
SSP: { symbol: 'SS£', name: 'South Sudanese Pound' },
SVC: { symbol: '₡', name: 'Salvadoran Colón' },
SZL: { symbol: 'E', name: 'Swazi Lilangeni' },
THB: { symbol: '฿', name: 'Thai Baht' },
TTD: { symbol: 'TT$', name: 'Trinidad and Tobago Dollar' },
TZS: { symbol: 'Sh', name: 'Tanzanian Shilling' },
USD: { symbol: '$', name: 'United States Dollar' },
UYU: { symbol: '$U', name: 'Uruguayan Peso' },
UZS: { symbol: "so'm", name: 'Uzbekistani Som' },
YER: { symbol: '﷼', name: 'Yemeni Rial' },
ZAR: { symbol: 'R', name: 'South African Rand' },
KWD: { symbol: 'د.ك', name: 'Kuwaiti Dinar' },
BHD: { symbol: 'د.ب.', name: 'Bahraini Dinar' },
OMR: { symbol: 'ر.ع.', name: 'Omani Rial' },
AED: {
symbol: 'د.إ',
name: 'United Arab Emirates Dirham',
lowerUnitName: 'Fils',
},
ALL: {
symbol: 'Lek',
name: 'Albanian Lek',
lowerUnitName: 'Qindarka',
},
AMD: {
symbol: '֏',
name: 'Armenian Dram',
lowerUnitName: 'Luma',
},
ARS: {
symbol: 'ARS',
name: 'Argentine Peso',
lowerUnitName: 'Centavo',
},
AUD: {
symbol: 'A$',
name: 'Australian Dollar',
lowerUnitName: 'Cent',
},
... rest of the country
} */
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { CURRENCIES } from '../../currency/data/currencies';
import type { CurrencyCodeType, I18nifyNumberFormatOptions } from '../../currency/types';
import state from '../state';
import { getLocale } from './getLocale';

export const getIntlInstanceWithOptions = (
options: {
currency?: keyof typeof CURRENCIES;
currency?: CurrencyCodeType;
locale?: string;
intlOptions?: Intl.NumberFormatOptions;
intlOptions?: I18nifyNumberFormatOptions;
} = {},
) => {
/** retrieve locale from below areas in order of preference
Expand All @@ -30,5 +30,8 @@ export const getIntlInstanceWithOptions = (

if (!locale) throw new Error('Pass valid locale !');

return new Intl.NumberFormat(locale || undefined, intlOptions);
return new Intl.NumberFormat(
locale || undefined,
intlOptions as Intl.NumberFormatOptions,
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import convertToMajorUnit from '../convertToMajorUnit';
import { CurrencyCodeType } from '../types';

describe('currency - convertToMajorUnit', () => {
const testCases: {
amount: number;
currency: CurrencyCodeType;
expectedResult: number;
}[] = [
{ amount: 100, currency: 'USD', expectedResult: 1 },
{ amount: 100, currency: 'GBP', expectedResult: 1 },
];

testCases.forEach(({ amount, currency, expectedResult }) => {
it(`should correctly convert ${amount} of minor unit ${currency} to ${expectedResult}`, () => {
const result = convertToMajorUnit(amount, { currency: currency });
expect(result).toBe(expectedResult);
});
});

it('should throw an error for unsupported currency codes', () => {
const unsupportedCurrencyCode = 'XXX';
expect(() => {
// @ts-expect-error intented invalid currencyCode for testing
convertToMajorUnit(100, { currency: unsupportedCurrencyCode });
}).toThrow('Unsupported currency XXX');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import convertToMinorUnit from '../convertToMinorUnit';
import { CurrencyCodeType } from '../types';

describe('currency - convertToMinorUnit', () => {
const testCases: {
amount: number;
currency: CurrencyCodeType;
expectedResult: number;
}[] = [
{ amount: 1, currency: 'USD', expectedResult: 100 },
{ amount: 1, currency: 'GBP', expectedResult: 100 },
];

testCases.forEach(({ amount, currency, expectedResult }) => {
it(`should correctly convert ${amount} of minor unit ${currency} to ${expectedResult}`, () => {
const result = convertToMinorUnit(amount, { currency: currency });
expect(result).toBe(expectedResult);
});
});

it('should throw an error for unsupported currency codes', () => {
const unsupportedCurrencyCode = 'XXX';
expect(() => {
// @ts-expect-error intented invalid currencyCode for testing
convertToMinorUnit(100, { currency: unsupportedCurrencyCode });
}).toThrow('Unsupported currency XXX');
});
});
34 changes: 34 additions & 0 deletions packages/i18nify-js/src/modules/currency/convertToMajorUnit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { withErrorBoundary } from '../../common/errorBoundary';
import { CURRENCIES } from './data/currencies';
import { CurrencyCodeType, CurrencyType } from './types';

/**
* Converts an amount from a minor currency unit to a major currency unit.
*
* The function takes an amount in the minor unit (e.g., cents, pence) and a currency code,
* then converts the amount to the major unit (e.g., dollars, pounds) using the conversion rate
* defined in the CURRENCIES object. If the currency code is not supported, it throws an error.
*
* @param {number} amount - The amount in the minor currency unit.
* @param {object} options - The options object
* @returns {number} - The amount converted to the major currency unit.
* @throws Will throw an error if the currency code is not supported.
*/
const convertToMajorUnit = (
amount: number,
options: {
currency: CurrencyCodeType;
},
): number => {
const currencyInfo = CURRENCIES[options.currency] as CurrencyType;

if (!currencyInfo)
throw new Error(`Unsupported currency ${options.currency}`);

const minorUnitMultiplier = currencyInfo.minorUnitMultiplier || 100;

const higherCurrencyValue = amount / minorUnitMultiplier;
return higherCurrencyValue;
};

export default withErrorBoundary<typeof convertToMajorUnit>(convertToMajorUnit);
34 changes: 34 additions & 0 deletions packages/i18nify-js/src/modules/currency/convertToMinorUnit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { withErrorBoundary } from '../../common/errorBoundary';
import { CURRENCIES } from './data/currencies';
import { CurrencyCodeType, CurrencyType } from './types';

/**
* Converts an amount from a major currency unit to a minor currency unit.
*
* The function takes an amount in the major unit (e.g., dollars, pounds) and a currency code,
* then converts the amount to the minor unit (e.g., cents, pence) using the conversion rate
* defined in the CURRENCIES object. If the currency code is not supported, it throws an error.
*
* @param {number} amount - The amount in the major currency unit.
* @param {object} options - The options object
* @returns {number} - The amount converted to the minor currency unit.
* @throws Will throw an error if the currency code is not supported.
*/
const convertToMinorUnit = (
amount: number,
options: {
currency: CurrencyCodeType;
},
): number => {
const currencyInfo = CURRENCIES[options.currency] as CurrencyType;

if (!currencyInfo)
throw new Error(`Unsupported currency ${options.currency}`);

const minorUnitMultiplier = currencyInfo.minorUnitMultiplier || 100;

const lowerCurrencyValue = amount * minorUnitMultiplier;
return lowerCurrencyValue;
};

export default withErrorBoundary<typeof convertToMinorUnit>(convertToMinorUnit);
Loading
Loading