From f8482ea1f251a5626d9278c91f30ca2314aed7f3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 02:13:10 +0700 Subject: [PATCH 01/20] fix: incorrect bill splits for some currencies --- src/components/IOUConfirmationList.js | 4 ++-- src/libs/IOUUtils.js | 32 +++++++++++++++++++++++---- src/libs/actions/IOU.js | 4 ++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index eae3f48d84ca..695c88a9467e 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -153,7 +153,7 @@ class IOUConfirmationList extends Component { getParticipantsWithAmount(participants) { return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participants, - this.props.numberFormat(IOUUtils.calculateAmount(participants, this.props.iouAmount) / 100, { + this.props.numberFormat(IOUUtils.calculateAmount(participants, this.props.iouAmount, this.props.iou.selectedCurrencyCode) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), @@ -187,7 +187,7 @@ class IOUConfirmationList extends Component { const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( this.props.currentUserPersonalDetails, - this.props.numberFormat(IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, true) / 100, { + this.props.numberFormat(IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, this.props.iou.selectedCurrencyCode, true) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index b4e8f1ff46ce..1ced04b9d7a4 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -1,31 +1,55 @@ import _ from 'underscore'; import CONST from '../CONST'; +function getCurrencyDecimals(currency) { + const formatted = Intl.NumberFormat('en', {style: 'currency', currency}).format(0); + const decimalPointIndex = formatted.indexOf('.'); + return decimalPointIndex === -1 + ? 0 + : Math.min(formatted.length - decimalPointIndex - 1, 2); +} + +function getCurrencyUnits(currency) { + return 10 ** getCurrencyDecimals(currency); +} + /** * Calculates the amount per user given a list of participants * @param {Array} participants - List of logins for the participants in the chat. It should not include the current user's login. * @param {Number} total - IOU total amount + * @param {String} currency - IOU currency * @param {Boolean} isDefaultUser - Whether we are calculating the amount for the current user + * @param {Boolean} shouldConvertTo2DigitsFormat - Whether to convert the amount to 2-digits format * @returns {Number} */ -function calculateAmount(participants, total, isDefaultUser = false) { +function calculateAmount(participants, total, currency, isDefaultUser = false, shouldConvertTo2DigitsFormat = false) { // Convert to cents before working with iouAmount to avoid // javascript subtraction with decimal problem -- when dealing with decimals, // because they are encoded as IEEE 754 floating point numbers, some of the decimal // numbers cannot be represented with perfect accuracy. - // Cents is temporary and there must be support for other currencies in the future - const iouAmount = Math.round(parseFloat(total * 100)); + // Currencies that do not have minor units (i.e. no decimal place) are also supported. + // https://github.com/Expensify/App/issues/15878 + const currencyUnits = getCurrencyUnits(currency); + const iouAmount = Math.round(parseFloat(total * currencyUnits)); + const totalParticipants = participants.length + 1; const amountPerPerson = Math.round(iouAmount / totalParticipants); if (!isDefaultUser) { + if (shouldConvertTo2DigitsFormat) { + return (amountPerPerson * 100) / currencyUnits; + } return amountPerPerson; } const sumAmount = amountPerPerson * totalParticipants; const difference = iouAmount - sumAmount; - return iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; + const finalAmount = iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; + if (shouldConvertTo2DigitsFormat) { + return (finalAmount * 100) / currencyUnits; + } + return finalAmount; } /** diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 9bad1af1a63f..ba791426fef1 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -311,8 +311,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment ]; // Loop through participants creating individual chats, iouReports and reportActionIDs as needed - const splitAmount = IOUUtils.calculateAmount(participants, amount); - const splits = [{email: currentUserEmail, amount: IOUUtils.calculateAmount(participants, amount, true)}]; + const splitAmount = IOUUtils.calculateAmount(participants, amount, currency, false, true); + const splits = [{email: currentUserEmail, amount: IOUUtils.calculateAmount(participants, amount, currency, true, true)}]; const hasMultipleParticipants = participants.length > 1; _.each(participants, (participant) => { From 36e3c8b1385b4b5a83eb83c34f221e01545b704d Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 02:25:52 +0700 Subject: [PATCH 02/20] fix eslint --- src/components/IOUConfirmationList.js | 31 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index 695c88a9467e..b45a4ff882ae 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -153,10 +153,17 @@ class IOUConfirmationList extends Component { getParticipantsWithAmount(participants) { return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participants, - this.props.numberFormat(IOUUtils.calculateAmount(participants, this.props.iouAmount, this.props.iou.selectedCurrencyCode) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), { - style: 'currency', - currency: this.props.iou.selectedCurrencyCode, - }), + this.props.numberFormat( + IOUUtils.calculateAmount( + participants, + this.props.iouAmount, + this.props.iou.selectedCurrencyCode, + ) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), + { + style: 'currency', + currency: this.props.iou.selectedCurrencyCode, + }, + ), ); } @@ -187,10 +194,18 @@ class IOUConfirmationList extends Component { const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( this.props.currentUserPersonalDetails, - this.props.numberFormat(IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, this.props.iou.selectedCurrencyCode, true) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), { - style: 'currency', - currency: this.props.iou.selectedCurrencyCode, - }), + this.props.numberFormat( + IOUUtils.calculateAmount( + selectedParticipants, + this.props.iouAmount, + this.props.iou.selectedCurrencyCode, + true, + ) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), + { + style: 'currency', + currency: this.props.iou.selectedCurrencyCode, + }, + ), ); sections.push({ From 8bc9e5e9c12eba9ec968757cb88ecdde41663556 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 12:01:59 +0700 Subject: [PATCH 03/20] export function getCurrencyUnits --- src/libs/IOUUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 1ced04b9d7a4..34a73710f560 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -163,4 +163,5 @@ export { updateIOUOwnerAndTotal, getIOUReportActions, isIOUReportPendingCurrencyConversion, + getCurrencyUnits, }; From 1d5fb24e8f5f05e5f309339266170a25449daea1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 14:10:19 +0700 Subject: [PATCH 04/20] refactor: remove duplicated logic --- src/libs/IOUUtils.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 34a73710f560..d089c71efe6b 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -35,20 +35,18 @@ function calculateAmount(participants, total, currency, isDefaultUser = false, s const totalParticipants = participants.length + 1; const amountPerPerson = Math.round(iouAmount / totalParticipants); - if (!isDefaultUser) { - if (shouldConvertTo2DigitsFormat) { - return (amountPerPerson * 100) / currencyUnits; - } - return amountPerPerson; - } + let finalAmount = amountPerPerson; - const sumAmount = amountPerPerson * totalParticipants; - const difference = iouAmount - sumAmount; + if (isDefaultUser) { + const sumAmount = amountPerPerson * totalParticipants; + const difference = iouAmount - sumAmount; + finalAmount = iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; + } - const finalAmount = iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; if (shouldConvertTo2DigitsFormat) { - return (finalAmount * 100) / currencyUnits; + finalAmount = (finalAmount * 100) / currencyUnits; } + return finalAmount; } From 81f909b5109934c5507551f11b605d9e75b8b03a Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 22:52:46 +0700 Subject: [PATCH 05/20] add JSDoc and unit test for getCurrencyDecimals function --- src/libs/IOUUtils.js | 10 ++++++++++ tests/unit/IOUUtilsTest.js | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index d089c71efe6b..5be66ac1ab59 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -1,6 +1,15 @@ import _ from 'underscore'; import CONST from '../CONST'; +/** + * Returns the number of digits after the decimal separator + * for a specific currency following ISO 4217. + * For currencies that have decimal places > 2, floor to 2 instead: + * https://github.com/Expensify/App/issues/15878#issuecomment-1496291464 + * + * @param {String} currency - IOU currency + * @returns {Number} + */ function getCurrencyDecimals(currency) { const formatted = Intl.NumberFormat('en', {style: 'currency', currency}).format(0); const decimalPointIndex = formatted.indexOf('.'); @@ -162,4 +171,5 @@ export { getIOUReportActions, isIOUReportPendingCurrencyConversion, getCurrencyUnits, + getCurrencyDecimals, }; diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 97ee16ca038b..53b91867962c 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -131,3 +131,17 @@ describe('isIOUReportPendingCurrencyConversion', () => { }); }); +describe('getCurrencyDecimals', () => { + test('Currency decimals smaller than or equal 2', () => { + expect(IOUUtils.getCurrencyDecimals('JPY')).toBe(0); + expect(IOUUtils.getCurrencyDecimals('USD')).toBe(2); + }); + + test('Currency decimals larger than 2 should return 2', () => { + // Actual: 3 + expect(IOUUtils.getCurrencyDecimals('LYD')).toBe(2); + + // Actual: 4 + expect(IOUUtils.getCurrencyDecimals('UYW')).toBe(2); + }); +}); From 714c49df3efedec0543c00026d2b8f329142bb06 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 5 Apr 2023 23:39:49 +0700 Subject: [PATCH 06/20] add JSDoc and unit test for getCurrencyUnits function --- src/libs/IOUUtils.js | 7 +++++++ tests/unit/IOUUtilsTest.js | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 5be66ac1ab59..352b25f61353 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -18,6 +18,13 @@ function getCurrencyDecimals(currency) { : Math.min(formatted.length - decimalPointIndex - 1, 2); } +/** + * Returns the currency's minor unit quantity + * i.e. Cent in USD + * + * @param {String} currency - IOU currency + * @returns {Number} + */ function getCurrencyUnits(currency) { return 10 ** getCurrencyDecimals(currency); } diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 53b91867962c..43ef28549ee1 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -145,3 +145,14 @@ describe('getCurrencyDecimals', () => { expect(IOUUtils.getCurrencyDecimals('UYW')).toBe(2); }); }); + +describe('getCurrencyUnits', () => { + test('Currency with decimals smaller than or equal 2', () => { + expect(IOUUtils.getCurrencyUnits('JPY')).toBe(1); + expect(IOUUtils.getCurrencyUnits('USD')).toBe(100); + }); + + test('Currency with decimals larger than 2 should be floor to 2', () => { + expect(IOUUtils.getCurrencyUnits('LYD')).toBe(100); + }); +}); From 075d6422c265ded879b39d64aa0f035be2d5f62c Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 13 Apr 2023 17:17:15 +0700 Subject: [PATCH 07/20] get currency decimals from onyx --- src/libs/IOUUtils.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 352b25f61353..12caf6c42f4b 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -1,9 +1,23 @@ import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import CONST from '../CONST'; +import ONYXKEYS from '../ONYXKEYS'; + +let currencyList = {}; +Onyx.connect({ + key: ONYXKEYS.CURRENCY_LIST, + callback: (val) => { + if (_.isEmpty(val)) { + return; + } + + currencyList = val; + }, +}); /** - * Returns the number of digits after the decimal separator - * for a specific currency following ISO 4217. + * Returns the number of digits after the decimal separator for a specific currency. * For currencies that have decimal places > 2, floor to 2 instead: * https://github.com/Expensify/App/issues/15878#issuecomment-1496291464 * @@ -11,16 +25,13 @@ import CONST from '../CONST'; * @returns {Number} */ function getCurrencyDecimals(currency) { - const formatted = Intl.NumberFormat('en', {style: 'currency', currency}).format(0); - const decimalPointIndex = formatted.indexOf('.'); - return decimalPointIndex === -1 - ? 0 - : Math.min(formatted.length - decimalPointIndex - 1, 2); + const decimals = lodashGet(currencyList, [currency, 'decimals']); + return _.isUndefined(decimals) ? 0 : Math.min(decimals, 2); } /** * Returns the currency's minor unit quantity - * i.e. Cent in USD + * e.g. Cent in USD * * @param {String} currency - IOU currency * @returns {Number} From b4cdbebfbdd2c9728376e24fb6b0074f29b6421f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 14 Apr 2023 17:49:36 +0700 Subject: [PATCH 08/20] default decimal to 2 for missing currencies --- src/libs/IOUUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 12caf6c42f4b..a9948684a6a3 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -26,7 +26,7 @@ Onyx.connect({ */ function getCurrencyDecimals(currency) { const decimals = lodashGet(currencyList, [currency, 'decimals']); - return _.isUndefined(decimals) ? 0 : Math.min(decimals, 2); + return _.isUndefined(decimals) ? 2 : Math.min(decimals, 2); } /** From 1c21bce32c56586a4944277c351a5da236f9fb05 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 14 Apr 2023 17:58:41 +0700 Subject: [PATCH 09/20] refactor for readibility --- src/components/IOUConfirmationList.js | 36 ++++++++++----------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index 182fabdcfb51..f6e1c06302a7 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -151,19 +151,15 @@ class IOUConfirmationList extends Component { * @returns {Array} */ getParticipantsWithAmount(participants) { + const iouAmount = IOUUtils.calculateAmount(participants, this.props.iouAmount, this.props.iou.selectedCurrencyCode); + const currencyUnits = IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode); + return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participants, - this.props.numberFormat( - IOUUtils.calculateAmount( - participants, - this.props.iouAmount, - this.props.iou.selectedCurrencyCode, - ) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), - { - style: 'currency', - currency: this.props.iou.selectedCurrencyCode, - }, - ), + this.props.numberFormat(iouAmount / currencyUnits, { + style: 'currency', + currency: this.props.iou.selectedCurrencyCode, + }), ); } @@ -192,20 +188,14 @@ class IOUConfirmationList extends Component { const formattedUnselectedParticipants = this.getParticipantsWithoutAmount(unselectedParticipants); const formattedParticipants = _.union(formattedSelectedParticipants, formattedUnselectedParticipants); + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, this.props.iou.selectedCurrencyCode, true); + const currencyUnits = IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode); const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( this.props.currentUserPersonalDetails, - this.props.numberFormat( - IOUUtils.calculateAmount( - selectedParticipants, - this.props.iouAmount, - this.props.iou.selectedCurrencyCode, - true, - ) / IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode), - { - style: 'currency', - currency: this.props.iou.selectedCurrencyCode, - }, - ), + this.props.numberFormat(myIOUAmount / currencyUnits, { + style: 'currency', + currency: this.props.iou.selectedCurrencyCode, + }), ); sections.push({ From 048624182db9050f395b70b48b8a7d5abe110b20 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 14 Apr 2023 18:48:50 +0700 Subject: [PATCH 10/20] update mock currencyList.json and init Onyx for IOUUtilsTest --- tests/unit/IOUUtilsTest.js | 16 + tests/unit/currencyList.json | 1069 +++++++++++++++++----------------- 2 files changed, 561 insertions(+), 524 deletions(-) diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 4678ea1b231c..2c4c1b76f94f 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -1,6 +1,10 @@ +import Onyx from 'react-native-onyx'; import CONST from '../../src/CONST'; import * as IOUUtils from '../../src/libs/IOUUtils'; import * as ReportUtils from '../../src/libs/ReportUtils'; +import ONYXKEYS from '../../src/ONYXKEYS'; +import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; +import currencyList from './currencyList.json'; let iouReport; let reportActions; @@ -38,6 +42,16 @@ function cancelMoneyRequest(moneyRequestAction, {isOnline} = {}) { ); } +function initCurrencyList() { + Onyx.init({ + keys: ONYXKEYS, + initialKeyStates: { + [ONYXKEYS.CURRENCY_LIST]: currencyList, + }, + }); + return waitForPromisesToResolve(); +} + beforeEach(() => { reportActions = []; const chatReportID = ReportUtils.generateReportID(); @@ -133,6 +147,7 @@ describe('isIOUReportPendingCurrencyConversion', () => { }); describe('getCurrencyDecimals', () => { + beforeAll(() => initCurrencyList()); test('Currency decimals smaller than or equal 2', () => { expect(IOUUtils.getCurrencyDecimals('JPY')).toBe(0); expect(IOUUtils.getCurrencyDecimals('USD')).toBe(2); @@ -148,6 +163,7 @@ describe('getCurrencyDecimals', () => { }); describe('getCurrencyUnits', () => { + beforeAll(() => initCurrencyList()); test('Currency with decimals smaller than or equal 2', () => { expect(IOUUtils.getCurrencyUnits('JPY')).toBe(1); expect(IOUUtils.getCurrencyUnits('USD')).toBe(100); diff --git a/tests/unit/currencyList.json b/tests/unit/currencyList.json index 740b3caf2b28..1283a854f7a9 100644 --- a/tests/unit/currencyList.json +++ b/tests/unit/currencyList.json @@ -1,855 +1,876 @@ { "AED": { - "symbol": "Dhs", - "name": "UAE Dirham", - "ISO4217": "784" + "symbol": "Dhs", + "name": "UAE Dirham", + "ISO4217": "784" }, "AFN": { - "symbol": "Af", - "name": "Afghan Afghani", - "ISO4217": "971" + "symbol": "Af", + "name": "Afghan Afghani", + "decimals": 0, + "ISO4217": "971" }, "ALL": { - "symbol": "ALL", - "name": "Albanian Lek", - "ISO4217": "008" + "symbol": "ALL", + "name": "Albanian Lek", + "decimals": 0, + "ISO4217": "008" }, "AMD": { - "symbol": "դր", - "name": "Armenian Dram", - "ISO4217": "051" + "symbol": "դր", + "name": "Armenian Dram", + "ISO4217": "051" }, "ANG": { - "symbol": "NAƒ", - "name": "Neth Antilles Guilder", - "ISO4217": "532" + "symbol": "NAƒ", + "name": "Neth Antilles Guilder", + "ISO4217": "532" }, "AOA": { - "symbol": "Kz", - "name": "Angolan Kwanza", - "ISO4217": "973" + "symbol": "Kz", + "name": "Angolan Kwanza", + "ISO4217": "973" }, "ARS": { - "symbol": "AR$", - "name": "Argentine Peso", - "ISO4217": "032" + "symbol": "AR$", + "name": "Argentine Peso", + "ISO4217": "032" }, "AUD": { - "symbol": "A$", - "name": "Australian Dollar", - "ISO4217": "036" + "symbol": "A$", + "name": "Australian Dollar", + "ISO4217": "036" }, "AWG": { - "symbol": "ƒ", - "name": "Aruba Florin", - "ISO4217": "533" + "symbol": "ƒ", + "name": "Aruba Florin", + "ISO4217": "533" }, "AZN": { - "symbol": "man", - "name": "Azerbaijani Manat", - "ISO4217": "944" + "symbol": "man", + "name": "Azerbaijani Manat", + "ISO4217": "944" }, "BAM": { - "symbol": "KM", - "name": "Bosnia And Herzegovina Convertible Mark", - "ISO4217": "977" + "symbol": "KM", + "name": "Bosnia And Herzegovina Convertible Mark", + "ISO4217": "977" }, "BBD": { - "symbol": "Bds$", - "name": "Barbados Dollar", - "ISO4217": "052" + "symbol": "Bds$", + "name": "Barbados Dollar", + "ISO4217": "052" }, "BDT": { - "symbol": "Tk", - "name": "Bangladesh Taka", - "ISO4217": "050" + "symbol": "Tk", + "name": "Bangladesh Taka", + "ISO4217": "050" }, "BGN": { - "symbol": "лв", - "name": "Bulgarian Lev", - "ISO4217": "975" + "symbol": "лв", + "name": "Bulgarian Lev", + "ISO4217": "975" }, "BHD": { - "symbol": "BHD", - "name": "Bahraini Dinar", - "ISO4217": "048" + "symbol": "BHD", + "name": "Bahraini Dinar", + "ISO4217": "048" }, "BIF": { - "symbol": "FBu", - "name": "Burundi Franc", - "decimals": 0, - "ISO4217": "108" + "symbol": "FBu", + "name": "Burundi Franc", + "decimals": 0, + "ISO4217": "108" }, "BMD": { - "symbol": "BD$", - "name": "Bermuda Dollar", - "ISO4217": "060" + "symbol": "BD$", + "name": "Bermuda Dollar", + "ISO4217": "060" }, "BND": { - "symbol": "BN$", - "name": "Brunei Dollar", - "ISO4217": "096" + "symbol": "BN$", + "name": "Brunei Dollar", + "ISO4217": "096" }, "BOB": { - "symbol": "Bs", - "name": "Bolivian Boliviano", - "ISO4217": "068" + "symbol": "Bs", + "name": "Bolivian Boliviano", + "ISO4217": "068" }, "BRL": { - "symbol": "R$", - "name": "Brazilian Real", - "ISO4217": "986" + "symbol": "R$", + "name": "Brazilian Real", + "ISO4217": "986" }, "BSD": { - "symbol": "BS$", - "name": "Bahamian Dollar", - "ISO4217": "044" + "symbol": "BS$", + "name": "Bahamian Dollar", + "ISO4217": "044" }, "BTN": { - "symbol": "Nu.", - "name": "Bhutan Ngultrum", - "ISO4217": "064" + "symbol": "Nu.", + "name": "Bhutan Ngultrum", + "ISO4217": "064" }, "BWP": { - "symbol": "P", - "name": "Botswana Pula", - "ISO4217": "072" + "symbol": "P", + "name": "Botswana Pula", + "ISO4217": "072" }, "BYN": { - "symbol": "BR", - "name": "Belarus Ruble", - "ISO4217": "933" + "symbol": "BR", + "name": "Belarus Ruble", + "ISO4217": "933" }, "BYR": { - "symbol": "BR", - "name": "Belarus Ruble", - "retired": true, - "retirementDate": "2016-07-01", - "ISO4217": "974" + "symbol": "BR", + "name": "Belarus Ruble", + "decimals": 0, + "retired": true, + "retirementDate": "2016-07-01", + "ISO4217": "974" }, "BZD": { - "symbol": "BZ$", - "name": "Belize Dollar", - "ISO4217": "084" + "symbol": "BZ$", + "name": "Belize Dollar", + "ISO4217": "084" }, "CAD": { - "symbol": "C$", - "name": "Canadian Dollar", - "ISO4217": "124" + "symbol": "C$", + "name": "Canadian Dollar", + "ISO4217": "124" }, "CDF": { - "symbol": "CDF", - "name": "Congolese Franc", - "ISO4217": "976" + "symbol": "CDF", + "name": "Congolese Franc", + "ISO4217": "976" }, "CHF": { - "symbol": "CHF", - "name": "Swiss Franc", - "ISO4217": "756" + "symbol": "CHF", + "name": "Swiss Franc", + "ISO4217": "756" }, "CLP": { - "symbol": "Ch$", - "name": "Chilean Peso", - "decimals": 0, - "ISO4217": "152" + "symbol": "Ch$", + "name": "Chilean Peso", + "decimals": 0, + "ISO4217": "152" }, "CNY": { - "symbol": "¥", - "name": "Chinese Yuan", - "ISO4217": "156" + "symbol": "¥", + "name": "Chinese Yuan", + "ISO4217": "156" }, "COP": { - "symbol": "Col$", - "name": "Colombian Peso", - "decimals": 0, - "ISO4217": "170" + "symbol": "Col$", + "name": "Colombian Peso", + "decimals": 0, + "ISO4217": "170" }, "CRC": { - "symbol": "CR₡", - "name": "Costa Rica Colon", - "ISO4217": "188" + "symbol": "CR₡", + "name": "Costa Rica Colon", + "ISO4217": "188" }, "CUC": { - "symbol": "CUC", - "name": "Cuban Convertible Peso", - "ISO4217": "931" + "symbol": "CUC", + "name": "Cuban Convertible Peso", + "ISO4217": "931" }, "CUP": { - "symbol": "$MN", - "name": "Cuban Peso", - "ISO4217": "192" + "symbol": "$MN", + "name": "Cuban Peso", + "ISO4217": "192" }, "CVE": { - "symbol": "Esc", - "name": "Cape Verde Escudo", - "ISO4217": "132" + "symbol": "Esc", + "name": "Cape Verde Escudo", + "ISO4217": "132" }, "CZK": { - "symbol": "Kč", - "name": "Czech Koruna", - "ISO4217": "203" + "symbol": "Kč", + "name": "Czech Koruna", + "ISO4217": "203" }, "DJF": { - "symbol": "Fdj", - "name": "Dijibouti Franc", - "decimals": 0, - "ISO4217": "262" + "symbol": "Fdj", + "name": "Dijibouti Franc", + "decimals": 0, + "ISO4217": "262" }, "DKK": { - "symbol": "Dkr", - "name": "Danish Krone", - "ISO4217": "208" + "symbol": "Dkr", + "name": "Danish Krone", + "ISO4217": "208" }, "DOP": { - "symbol": "RD$", - "name": "Dominican Peso", - "ISO4217": "214" + "symbol": "RD$", + "name": "Dominican Peso", + "ISO4217": "214" }, "DZD": { - "symbol": "DZD", - "name": "Algerian Dinar", - "ISO4217": "012" + "symbol": "DZD", + "name": "Algerian Dinar", + "ISO4217": "012" }, "EEK": { - "symbol": "KR", - "name": "Estonian Kroon", - "ISO4217": "", - "retired": true + "symbol": "KR", + "name": "Estonian Kroon", + "ISO4217": "", + "retired": true }, "EGP": { - "symbol": "EGP", - "name": "Egyptian Pound", - "ISO4217": "818" + "symbol": "EGP", + "name": "Egyptian Pound", + "ISO4217": "818" }, "ERN": { - "symbol": "Nfk", - "name": "Eritrea Nakfa", - "ISO4217": "232" + "symbol": "Nfk", + "name": "Eritrea Nakfa", + "ISO4217": "232" }, "ETB": { - "symbol": "Br", - "name": "Ethiopian Birr", - "ISO4217": "230" + "symbol": "Br", + "name": "Ethiopian Birr", + "ISO4217": "230" }, "EUR": { - "symbol": "€", - "name": "Euro", - "ISO4217": "978" + "symbol": "€", + "name": "Euro", + "ISO4217": "978" }, "FJD": { - "symbol": "FJ$", - "name": "Fiji Dollar", - "ISO4217": "242" + "symbol": "FJ$", + "name": "Fiji Dollar", + "ISO4217": "242" }, "FKP": { - "symbol": "FK£", - "name": "Falkland Islands Pound", - "ISO4217": "238" + "symbol": "FK£", + "name": "Falkland Islands Pound", + "ISO4217": "238" }, "GBP": { - "symbol": "£", - "name": "British Pound", - "ISO4217": "826" + "symbol": "£", + "name": "British Pound", + "ISO4217": "826" }, "GEL": { - "symbol": "ლ", - "name": "Georgian Lari", - "ISO4217": "981" + "symbol": "ლ", + "name": "Georgian Lari", + "ISO4217": "981" }, "GHS": { - "symbol": "₵", - "name": "Ghanaian Cedi", - "ISO4217": "936" + "symbol": "₵", + "name": "Ghanaian Cedi", + "ISO4217": "936" }, "GIP": { - "symbol": "£G", - "name": "Gibraltar Pound", - "ISO4217": "292" + "symbol": "£G", + "name": "Gibraltar Pound", + "ISO4217": "292" }, "GMD": { - "symbol": "D", - "name": "Gambian Dalasi", - "ISO4217": "270" + "symbol": "D", + "name": "Gambian Dalasi", + "ISO4217": "270" }, "GNF": { - "symbol": "FG", - "name": "Guinea Franc", - "decimals": 0, - "ISO4217": "324" + "symbol": "FG", + "name": "Guinea Franc", + "decimals": 0, + "ISO4217": "324" }, "GTQ": { - "symbol": "Q", - "name": "Guatemala Quetzal", - "ISO4217": "320" + "symbol": "Q", + "name": "Guatemala Quetzal", + "ISO4217": "320" }, "GYD": { - "symbol": "GY$", - "name": "Guyana Dollar", - "ISO4217": "328" + "symbol": "GY$", + "name": "Guyana Dollar", + "ISO4217": "328" }, "HKD": { - "symbol": "HK$", - "name": "Hong Kong Dollar", - "ISO4217": "344" + "symbol": "HK$", + "name": "Hong Kong Dollar", + "ISO4217": "344" }, "HNL": { - "symbol": "HNL", - "name": "Honduras Lempira", - "ISO4217": "340" + "symbol": "HNL", + "name": "Honduras Lempira", + "ISO4217": "340" }, "HRK": { - "symbol": "kn", - "name": "Croatian Kuna", - "ISO4217": "191" + "symbol": "kn", + "name": "Croatian Kuna", + "ISO4217": "191" }, "HTG": { - "symbol": "G", - "name": "Haiti Gourde", - "ISO4217": "332" + "symbol": "G", + "name": "Haiti Gourde", + "ISO4217": "332" }, "HUF": { - "symbol": "Ft", - "name": "Hungarian Forint", - "ISO4217": "348" + "symbol": "Ft", + "name": "Hungarian Forint", + "ISO4217": "348" }, "IDR": { - "symbol": "Rp", - "name": "Indonesian Rupiah", - "ISO4217": "360" + "symbol": "Rp", + "name": "Indonesian Rupiah", + "ISO4217": "360" }, "ILS": { - "symbol": "₪", - "name": "Israeli Shekel", - "ISO4217": "376" + "symbol": "₪", + "name": "Israeli Shekel", + "ISO4217": "376" }, "INR": { - "symbol": "₹", - "name": "Indian Rupee", - "ISO4217": "356" + "symbol": "₹", + "name": "Indian Rupee", + "ISO4217": "356" }, "IQD": { - "symbol": "IQD", - "name": "Iraqi Dinar", - "ISO4217": "368" + "symbol": "IQD", + "name": "Iraqi Dinar", + "decimals": 0, + "ISO4217": "368" }, "IRR": { - "symbol": "﷼", - "name": "Iran Rial", - "ISO4217": "364" + "symbol": "﷼", + "name": "Iran Rial", + "decimals": 0, + "ISO4217": "364" }, "ISK": { - "symbol": "kr", - "name": "Iceland Krona", - "decimals": 0, - "ISO4217": "352" + "symbol": "kr", + "name": "Iceland Krona", + "decimals": 0, + "ISO4217": "352" }, "JMD": { - "symbol": "J$", - "name": "Jamaican Dollar", - "ISO4217": "388" + "symbol": "J$", + "name": "Jamaican Dollar", + "ISO4217": "388" }, "JOD": { - "symbol": "JOD", - "name": "Jordanian Dinar", - "ISO4217": "400" + "symbol": "JOD", + "name": "Jordanian Dinar", + "ISO4217": "400" }, "JPY": { - "symbol": "¥", - "name": "Japanese Yen", - "decimals": 0, - "ISO4217": "392" + "symbol": "¥", + "name": "Japanese Yen", + "decimals": 0, + "ISO4217": "392" }, "KES": { - "symbol": "KSh", - "name": "Kenyan Shilling", - "ISO4217": "404" + "symbol": "KSh", + "name": "Kenyan Shilling", + "ISO4217": "404" }, "KGS": { - "symbol": "KGS", - "name": "Kyrgyzstani Som", - "ISO4217": "417" + "symbol": "KGS", + "name": "Kyrgyzstani Som", + "ISO4217": "417" }, "KHR": { - "symbol": "KHR", - "name": "Cambodia Riel", - "ISO4217": "116" + "symbol": "KHR", + "name": "Cambodia Riel", + "ISO4217": "116" }, "KMF": { - "symbol": "CF", - "name": "Comoros Franc", - "ISO4217": "174" + "symbol": "CF", + "name": "Comoros Franc", + "decimals": 0, + "ISO4217": "174" }, "KPW": { - "symbol": "KP₩", - "name": "North Korean Won", - "ISO4217": "408" + "symbol": "KP₩", + "name": "North Korean Won", + "decimals": 0, + "ISO4217": "408" }, "KRW": { - "symbol": "₩", - "name": "Korean Won", - "ISO4217": "410" + "symbol": "₩", + "name": "Korean Won", + "decimals": 0, + "ISO4217": "410" }, "KWD": { - "symbol": "KWD", - "name": "Kuwaiti Dinar", - "ISO4217": "414" + "symbol": "KWD", + "name": "Kuwaiti Dinar", + "ISO4217": "414" }, "KYD": { - "symbol": "CI$", - "name": "Cayman Islands Dollar", - "ISO4217": "136" + "symbol": "CI$", + "name": "Cayman Islands Dollar", + "ISO4217": "136" }, "KZT": { - "symbol": "〒", - "name": "Kazakhstan Tenge", - "ISO4217": "398" + "symbol": "〒", + "name": "Kazakhstan Tenge", + "ISO4217": "398" }, "LAK": { - "symbol": "₭", - "name": "Lao Kip", - "ISO4217": "418" + "symbol": "₭", + "name": "Lao Kip", + "decimals": 0, + "ISO4217": "418" }, "LBP": { - "symbol": "LBP", - "name": "Lebanese Pound", - "ISO4217": "422" + "symbol": "LBP", + "name": "Lebanese Pound", + "decimals": 0, + "ISO4217": "422" }, "LKR": { - "symbol": "SL₨", - "name": "Sri Lanka Rupee", - "ISO4217": "144" + "symbol": "SL₨", + "name": "Sri Lanka Rupee", + "ISO4217": "144" }, "LRD": { - "symbol": "L$", - "name": "Liberian Dollar", - "ISO4217": "430" + "symbol": "L$", + "name": "Liberian Dollar", + "ISO4217": "430" }, "LSL": { - "symbol": "M", - "name": "Lesotho Loti", - "ISO4217": "426" + "symbol": "M", + "name": "Lesotho Loti", + "ISO4217": "426" }, "LTL": { - "symbol": "Lt", - "name": "Lithuanian Lita", - "retirementDate": "2015-08-22", - "retired": true, - "ISO4217": "440" + "symbol": "Lt", + "name": "Lithuanian Lita", + "retirementDate": "2015-08-22", + "retired": true, + "ISO4217": "440" }, "LVL": { - "symbol": "Ls", - "name": "Latvian Lat", - "ISO4217": "428", - "retired": true + "symbol": "Ls", + "name": "Latvian Lat", + "ISO4217": "428", + "retired": true }, "LYD": { - "symbol": "LYD", - "name": "Libyan Dinar", - "ISO4217": "434" + "symbol": "LYD", + "name": "Libyan Dinar", + "ISO4217": "434" }, "MAD": { - "symbol": "MAD", - "name": "Moroccan Dirham", - "ISO4217": "504" + "symbol": "MAD", + "name": "Moroccan Dirham", + "ISO4217": "504" }, "MDL": { - "symbol": "MDL", - "name": "Moldovan Leu", - "ISO4217": "498" + "symbol": "MDL", + "name": "Moldovan Leu", + "ISO4217": "498" }, "MGA": { - "symbol": "MGA", - "name": "Malagasy Ariary", - "ISO4217": "969" + "symbol": "MGA", + "name": "Malagasy Ariary", + "decimals": 0, + "ISO4217": "969" }, "MKD": { - "symbol": "ден", - "name": "Macedonian Denar", - "ISO4217": "807" + "symbol": "ден", + "name": "Macedonian Denar", + "ISO4217": "807" }, "MMK": { - "symbol": "Ks", - "name": "Myanmar Kyat", - "ISO4217": "104" + "symbol": "Ks", + "name": "Myanmar Kyat", + "decimals": 0, + "ISO4217": "104" }, "MNT": { - "symbol": "₮", - "name": "Mongolian Tugrik", - "ISO4217": "496" + "symbol": "₮", + "name": "Mongolian Tugrik", + "ISO4217": "496" }, "MOP": { - "symbol": "MOP$", - "name": "Macau Pataca", - "ISO4217": "446" + "symbol": "MOP$", + "name": "Macau Pataca", + "ISO4217": "446" }, "MRO": { - "symbol": "UM", - "name": "Mauritania Ougulya", - "decimals": 0, - "retired": true, - "retirementDate": "2018-07-11", - "ISO4217": "478" + "symbol": "UM", + "name": "Mauritania Ougulya", + "decimals": 0, + "retired": true, + "retirementDate": "2018-07-11", + "ISO4217": "478" }, "MRU": { - "symbol": "UM", - "name": "Mauritania Ougulya", - "decimals": 0, - "ISO4217": "" + "symbol": "UM", + "name": "Mauritania Ougulya", + "decimals": 0, + "ISO4217": "" }, "MUR": { - "symbol": "Rs", - "name": "Mauritius Rupee", - "ISO4217": "480" + "symbol": "Rs", + "name": "Mauritius Rupee", + "ISO4217": "480" }, "MVR": { - "symbol": "Rf", - "name": "Maldives Rufiyaa", - "ISO4217": "462" + "symbol": "Rf", + "name": "Maldives Rufiyaa", + "ISO4217": "462" }, "MWK": { - "symbol": "MK", - "name": "Malawi Kwacha", - "ISO4217": "454" + "symbol": "MK", + "name": "Malawi Kwacha", + "ISO4217": "454" }, "MXN": { - "symbol": "Mex$", - "name": "Mexican Peso", - "ISO4217": "484" + "symbol": "Mex$", + "name": "Mexican Peso", + "ISO4217": "484" }, "MYR": { - "symbol": "RM", - "name": "Malaysian Ringgit", - "ISO4217": "458" + "symbol": "RM", + "name": "Malaysian Ringgit", + "ISO4217": "458" }, "MZN": { - "symbol": "MTn", - "name": "Mozambican Metical", - "ISO4217": "943" + "symbol": "MTn", + "name": "Mozambican Metical", + "ISO4217": "943" }, "NAD": { - "symbol": "N$", - "name": "Namibian Dollar", - "ISO4217": "516" + "symbol": "N$", + "name": "Namibian Dollar", + "ISO4217": "516" }, "NGN": { - "symbol": "₦", - "name": "Nigerian Naira", - "ISO4217": "566" + "symbol": "₦", + "name": "Nigerian Naira", + "ISO4217": "566" }, "NIO": { - "symbol": "NIO", - "name": "Nicaragua Cordoba", - "ISO4217": "558" + "symbol": "NIO", + "name": "Nicaragua Cordoba", + "ISO4217": "558" }, "NOK": { - "symbol": "Nkr", - "name": "Norwegian Krone", - "ISO4217": "578" + "symbol": "Nkr", + "name": "Norwegian Krone", + "ISO4217": "578" }, "NPR": { - "symbol": "₨", - "name": "Nepalese Rupee", - "ISO4217": "524" + "symbol": "₨", + "name": "Nepalese Rupee", + "ISO4217": "524" }, "NZD": { - "symbol": "NZ$", - "name": "New Zealand Dollar", - "ISO4217": "554" + "symbol": "NZ$", + "name": "New Zealand Dollar", + "ISO4217": "554" }, "OMR": { - "symbol": "OMR", - "name": "Omani Rial", - "ISO4217": "512" + "symbol": "OMR", + "name": "Omani Rial", + "ISO4217": "512" }, "PAB": { - "symbol": "B", - "name": "Panama Balboa", - "ISO4217": "590" + "symbol": "B", + "name": "Panama Balboa", + "ISO4217": "590" }, "PEN": { - "symbol": "S/.", - "name": "Peruvian Nuevo Sol", - "ISO4217": "604" + "symbol": "S/.", + "name": "Peruvian Nuevo Sol", + "ISO4217": "604" }, "PGK": { - "symbol": "K", - "name": "Papua New Guinea Kina", - "ISO4217": "598" + "symbol": "K", + "name": "Papua New Guinea Kina", + "ISO4217": "598" }, "PHP": { - "symbol": "₱", - "name": "Philippine Peso", - "ISO4217": "608" + "symbol": "₱", + "name": "Philippine Peso", + "ISO4217": "608" }, "PKR": { - "symbol": "Rs", - "name": "Pakistani Rupee", - "ISO4217": "586" + "symbol": "Rs", + "name": "Pakistani Rupee", + "ISO4217": "586" }, "PLN": { - "symbol": "zł", - "name": "Polish Zloty", - "ISO4217": "985" + "symbol": "zł", + "name": "Polish Zloty", + "ISO4217": "985" }, "PYG": { - "symbol": "₲", - "name": "Paraguayan Guarani", - "ISO4217": "600" + "symbol": "₲", + "name": "Paraguayan Guarani", + "decimals": 0, + "ISO4217": "600" }, "QAR": { - "symbol": "QAR", - "name": "Qatar Rial", - "ISO4217": "634" + "symbol": "QAR", + "name": "Qatar Rial", + "ISO4217": "634" }, "RON": { - "symbol": "RON", - "name": "Romanian New Leu", - "ISO4217": "946" + "symbol": "RON", + "name": "Romanian New Leu", + "ISO4217": "946" }, "RSD": { - "symbol": "РСД", - "name": "Serbian Dinar", - "ISO4217": "941" + "symbol": "РСД", + "name": "Serbian Dinar", + "decimals": 0, + "ISO4217": "941" }, "RUB": { - "symbol": "₽", - "name": "Russian Rouble", - "ISO4217": "643" + "symbol": "₽", + "name": "Russian Rouble", + "ISO4217": "643" }, "RWF": { - "symbol": "RF", - "name": "Rwanda Franc", - "decimals": 0, - "ISO4217": "646" + "symbol": "RF", + "name": "Rwanda Franc", + "decimals": 0, + "ISO4217": "646" }, "SAR": { - "symbol": "SAR", - "name": "Saudi Arabian Riyal", - "ISO4217": "682" + "symbol": "SAR", + "name": "Saudi Arabian Riyal", + "ISO4217": "682" }, "SBD": { - "symbol": "SI$", - "name": "Solomon Islands Dollar", - "ISO4217": "090" + "symbol": "SI$", + "name": "Solomon Islands Dollar", + "ISO4217": "090" }, "SCR": { - "symbol": "SR", - "name": "Seychelles Rupee", - "ISO4217": "690" + "symbol": "SR", + "name": "Seychelles Rupee", + "ISO4217": "690" }, "SDG": { - "symbol": "SDG", - "name": "Sudanese Pound", - "ISO4217": "938" + "symbol": "SDG", + "name": "Sudanese Pound", + "ISO4217": "938" }, "SEK": { - "symbol": "Skr", - "name": "Swedish Krona", - "ISO4217": "752" + "symbol": "Skr", + "name": "Swedish Krona", + "ISO4217": "752" }, "SGD": { - "symbol": "S$", - "name": "Singapore Dollar", - "ISO4217": "702" + "symbol": "S$", + "name": "Singapore Dollar", + "ISO4217": "702" }, "SHP": { - "symbol": "£S", - "name": "St Helena Pound", - "ISO4217": "654" + "symbol": "£S", + "name": "St Helena Pound", + "ISO4217": "654" }, "SLL": { - "symbol": "Le", - "name": "Sierra Leone Leone", - "ISO4217": "694" + "symbol": "Le", + "name": "Sierra Leone Leone", + "decimals": 0, + "ISO4217": "694" }, "SOS": { - "symbol": "So.", - "name": "Somali Shilling", - "ISO4217": "706" + "symbol": "So.", + "name": "Somali Shilling", + "decimals": 0, + "ISO4217": "706" }, "SRD": { - "symbol": "SRD", - "name": "Surinamese Dollar", - "ISO4217": "968" + "symbol": "SRD", + "name": "Surinamese Dollar", + "ISO4217": "968" }, "STD": { - "symbol": "Db", - "name": "Sao Tome Dobra", - "retired": true, - "retirementDate": "2018-07-11", - "ISO4217": "678" + "symbol": "Db", + "name": "Sao Tome Dobra", + "decimals": 0, + "retired": true, + "retirementDate": "2018-07-11", + "ISO4217": "678" }, "STN": { - "symbol": "Db", - "name": "Sao Tome Dobra", - "ISO4217": "" + "symbol": "Db", + "name": "Sao Tome Dobra", + "ISO4217": "" }, "SVC": { - "symbol": "SVC", - "name": "El Salvador Colon", - "ISO4217": "222" + "symbol": "SVC", + "name": "El Salvador Colon", + "ISO4217": "222" }, "SYP": { - "symbol": "SYP", - "name": "Syrian Pound", - "ISO4217": "760" + "symbol": "SYP", + "name": "Syrian Pound", + "decimals": 0, + "ISO4217": "760" }, "SZL": { - "symbol": "E", - "name": "Swaziland Lilageni", - "ISO4217": "748" + "symbol": "E", + "name": "Swaziland Lilageni", + "ISO4217": "748" }, "THB": { - "symbol": "฿", - "name": "Thai Baht", - "ISO4217": "764" + "symbol": "฿", + "name": "Thai Baht", + "ISO4217": "764" }, "TJS": { - "symbol": "TJS", - "name": "Tajikistani Somoni", - "ISO4217": "972" + "symbol": "TJS", + "name": "Tajikistani Somoni", + "ISO4217": "972" }, "TMT": { - "symbol": "m", - "name": "Turkmenistani Manat", - "ISO4217": "934" + "symbol": "m", + "name": "Turkmenistani Manat", + "ISO4217": "934" }, "TND": { - "symbol": "TND", - "name": "Tunisian Dinar", - "ISO4217": "788" + "symbol": "TND", + "name": "Tunisian Dinar", + "ISO4217": "788" }, "TOP": { - "symbol": "T$", - "name": "Tonga Pa'ang", - "ISO4217": "776" + "symbol": "T$", + "name": "Tonga Pa'ang", + "ISO4217": "776" }, "TRY": { - "symbol": "TL", - "name": "Turkish Lira", - "ISO4217": "949" + "symbol": "TL", + "name": "Turkish Lira", + "ISO4217": "949" }, "TTD": { - "symbol": "TT$", - "name": "Trinidad & Tobago Dollar", - "ISO4217": "780" + "symbol": "TT$", + "name": "Trinidad & Tobago Dollar", + "ISO4217": "780" }, "TWD": { - "symbol": "NT$", - "name": "Taiwan Dollar", - "ISO4217": "901" + "symbol": "NT$", + "name": "Taiwan Dollar", + "ISO4217": "901" }, "TZS": { - "symbol": "TZS", - "name": "Tanzanian Shilling", - "ISO4217": "834" + "symbol": "TZS", + "name": "Tanzanian Shilling", + "ISO4217": "834" }, "UAH": { - "symbol": "₴", - "name": "Ukraine Hryvnia", - "ISO4217": "980" + "symbol": "₴", + "name": "Ukraine Hryvnia", + "ISO4217": "980" }, "UGX": { - "symbol": "USh", - "name": "Ugandan Shilling", - "decimals": 0, - "ISO4217": "800" + "symbol": "USh", + "name": "Ugandan Shilling", + "decimals": 0, + "ISO4217": "800" }, "USD": { - "symbol": "$", - "name": "United States Dollar", - "ISO4217": "840" + "symbol": "$", + "name": "United States Dollar", + "ISO4217": "840" }, "UYU": { - "symbol": "$U", - "name": "Uruguayan New Peso", - "ISO4217": "858" + "symbol": "$U", + "name": "Uruguayan New Peso", + "ISO4217": "858" }, "UZS": { - "symbol": "UZS", - "name": "Uzbekistani Som", - "ISO4217": "860" + "symbol": "UZS", + "name": "Uzbekistani Som", + "ISO4217": "860" }, "VEB": { - "symbol": "Bs.", - "name": "Venezuelan Bolivar", - "retired": true, - "retirementDate": "2008-02-01", - "ISO4217": "" + "symbol": "Bs.", + "name": "Venezuelan Bolivar", + "retired": true, + "retirementDate": "2008-02-01", + "ISO4217": "" }, "VEF": { - "symbol": "Bs.F", - "name": "Venezuelan Bolivar Fuerte", - "retired": true, - "retirementDate": "2018-08-20", - "ISO4217": "937" + "symbol": "Bs.F", + "name": "Venezuelan Bolivar Fuerte", + "retired": true, + "retirementDate": "2018-08-20", + "ISO4217": "937" }, "VES": { - "symbol": "Bs.S", - "name": "Venezuelan Bolivar Soberano", - "ISO4217": "928" + "symbol": "Bs.S", + "name": "Venezuelan Bolivar Soberano", + "ISO4217": "928" }, "VND": { - "symbol": "₫", - "name": "Vietnam Dong", - "decimals": 0, - "ISO4217": "704" + "symbol": "₫", + "name": "Vietnam Dong", + "decimals": 0, + "ISO4217": "704" }, "VUV": { - "symbol": "Vt", - "name": "Vanuatu Vatu", - "ISO4217": "548" + "symbol": "Vt", + "name": "Vanuatu Vatu", + "decimals": 0, + "ISO4217": "548" }, "WST": { - "symbol": "WS$", - "name": "Samoa Tala", - "ISO4217": "882" + "symbol": "WS$", + "name": "Samoa Tala", + "ISO4217": "882" }, "XAF": { - "symbol": "FCFA", - "name": "CFA Franc (BEAC)", - "decimals": 0, - "ISO4217": "950" + "symbol": "FCFA", + "name": "CFA Franc (BEAC)", + "decimals": 0, + "ISO4217": "950" }, "XCD": { - "symbol": "EC$", - "name": "East Caribbean Dollar", - "ISO4217": "951" + "symbol": "EC$", + "name": "East Caribbean Dollar", + "ISO4217": "951" }, "XOF": { - "symbol": "CFA", - "name": "CFA Franc (BCEAO)", - "decimals": 0, - "ISO4217": "952" + "symbol": "CFA", + "name": "CFA Franc (BCEAO)", + "decimals": 0, + "ISO4217": "952" }, "XPF": { - "symbol": "XPF", - "name": "Pacific Franc", - "decimals": 0, - "ISO4217": "953" + "symbol": "XPF", + "name": "Pacific Franc", + "decimals": 0, + "ISO4217": "953" }, "YER": { - "symbol": "YER", - "name": "Yemen Riyal", - "ISO4217": "886" + "symbol": "YER", + "name": "Yemen Riyal", + "decimals": 0, + "ISO4217": "886" }, "ZAR": { - "symbol": "R", - "name": "South African Rand", - "ISO4217": "710" + "symbol": "R", + "name": "South African Rand", + "ISO4217": "710" }, "ZMK": { - "symbol": "ZK", - "name": "Zambian Kwacha", - "retired": true, - "retirementDate": "2013-01-01", - "ISO4217": "894" + "symbol": "ZK", + "name": "Zambian Kwacha", + "decimals": 0, + "retired": true, + "retirementDate": "2013-01-01", + "ISO4217": "894" }, "ZMW": { - "symbol": "ZMW", - "name": "Zambian Kwacha", - "cacheBurst": 1, - "ISO4217": "967" + "symbol": "ZMW", + "name": "Zambian Kwacha", + "cacheBurst": 1, + "ISO4217": "967" } } \ No newline at end of file From 4308726d159fe46ecd3f53ddf3dd22a587c681fc Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 14 Apr 2023 22:56:44 +0700 Subject: [PATCH 11/20] added unit test for calculateAmount --- tests/unit/IOUUtilsTest.js | 55 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 2c4c1b76f94f..5dd3774c9596 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -52,27 +52,27 @@ function initCurrencyList() { return waitForPromisesToResolve(); } -beforeEach(() => { - reportActions = []; - const chatReportID = ReportUtils.generateReportID(); - const amount = 1000; - const currency = 'USD'; - - iouReport = ReportUtils.buildOptimisticIOUReport( - ownerEmail, - managerEmail, - amount, - chatReportID, - currency, - CONST.LOCALES.EN, - ); - - // The starting point of all tests is the IOUReport containing a single non-pending transaction in USD - // All requests in the tests are assumed to be offline, unless isOnline is specified - createIOUReportAction('create', amount, currency, {IOUTransactionID: '', isOnline: true}); -}); - describe('isIOUReportPendingCurrencyConversion', () => { + beforeEach(() => { + reportActions = []; + const chatReportID = ReportUtils.generateReportID(); + const amount = 1000; + const currency = 'USD'; + + iouReport = ReportUtils.buildOptimisticIOUReport( + ownerEmail, + managerEmail, + amount, + chatReportID, + currency, + CONST.LOCALES.EN, + ); + + // The starting point of all tests is the IOUReport containing a single non-pending transaction in USD + // All requests in the tests are assumed to be offline, unless isOnline is specified + createIOUReportAction('create', amount, currency, {IOUTransactionID: '', isOnline: true}); + }); + test('Requesting money offline in a different currency will show the pending conversion message', () => { // Request money offline in AED createIOUReportAction('create', 100, 'AED'); @@ -173,3 +173,18 @@ describe('getCurrencyUnits', () => { expect(IOUUtils.getCurrencyUnits('LYD')).toBe(100); }); }); + +describe('calculateAmount', () => { + beforeAll(() => initCurrencyList()); + test('103 JPY split among 3 participants including the default user should be [35, 34, 34]', () => { + const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com']; + expect(IOUUtils.calculateAmount(participants, 103, 'JPY', true)).toBe(35); + expect(IOUUtils.calculateAmount(participants, 103, 'JPY')).toBe(34); + }); + + test('10 AFN split among 4 participants including the default user should be [1, 3, 3, 3]', () => { + const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com', 'suestorm@expensify.com']; + expect(IOUUtils.calculateAmount(participants, 10, 'AFN', true)).toBe(1); + expect(IOUUtils.calculateAmount(participants, 10, 'AFN')).toBe(3); + }); +}); From 5b5845df957b10e35511c049cb757b39769bc740 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sun, 16 Apr 2023 00:14:44 +0700 Subject: [PATCH 12/20] rename getCurrencyUnits to singular --- src/components/MoneyRequestConfirmationList.js | 8 ++++---- src/libs/IOUUtils.js | 10 +++++----- tests/unit/IOUUtilsTest.js | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 640935771c53..420f47733f11 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -139,11 +139,11 @@ class MoneyRequestConfirmationList extends Component { */ getParticipantsWithAmount(participants) { const iouAmount = IOUUtils.calculateAmount(participants, this.props.iouAmount, this.props.iou.selectedCurrencyCode); - const currencyUnits = IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode); + const currencyUnit = IOUUtils.getCurrencyUnit(this.props.iou.selectedCurrencyCode); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participants, - this.props.numberFormat(iouAmount / currencyUnits, { + this.props.numberFormat(iouAmount / currencyUnit, { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), @@ -176,10 +176,10 @@ class MoneyRequestConfirmationList extends Component { const formattedParticipants = _.union(formattedSelectedParticipants, formattedUnselectedParticipants); const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, this.props.iou.selectedCurrencyCode, true); - const currencyUnits = IOUUtils.getCurrencyUnits(this.props.iou.selectedCurrencyCode); + const currencyUnit = IOUUtils.getCurrencyUnit(this.props.iou.selectedCurrencyCode); const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( this.props.currentUserPersonalDetails, - this.props.numberFormat(myIOUAmount / currencyUnits, { + this.props.numberFormat(myIOUAmount / currencyUnit, { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index a9948684a6a3..0d3be7607bc1 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -36,7 +36,7 @@ function getCurrencyDecimals(currency) { * @param {String} currency - IOU currency * @returns {Number} */ -function getCurrencyUnits(currency) { +function getCurrencyUnit(currency) { return 10 ** getCurrencyDecimals(currency); } @@ -56,8 +56,8 @@ function calculateAmount(participants, total, currency, isDefaultUser = false, s // numbers cannot be represented with perfect accuracy. // Currencies that do not have minor units (i.e. no decimal place) are also supported. // https://github.com/Expensify/App/issues/15878 - const currencyUnits = getCurrencyUnits(currency); - const iouAmount = Math.round(parseFloat(total * currencyUnits)); + const currencyUnit = getCurrencyUnit(currency); + const iouAmount = Math.round(parseFloat(total * currencyUnit)); const totalParticipants = participants.length + 1; const amountPerPerson = Math.round(iouAmount / totalParticipants); @@ -71,7 +71,7 @@ function calculateAmount(participants, total, currency, isDefaultUser = false, s } if (shouldConvertTo2DigitsFormat) { - finalAmount = (finalAmount * 100) / currencyUnits; + finalAmount = (finalAmount * 100) / currencyUnit; } return finalAmount; @@ -188,6 +188,6 @@ export { updateIOUOwnerAndTotal, getIOUReportActions, isIOUReportPendingCurrencyConversion, - getCurrencyUnits, + getCurrencyUnit, getCurrencyDecimals, }; diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 5dd3774c9596..180948c21cc6 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -165,12 +165,12 @@ describe('getCurrencyDecimals', () => { describe('getCurrencyUnits', () => { beforeAll(() => initCurrencyList()); test('Currency with decimals smaller than or equal 2', () => { - expect(IOUUtils.getCurrencyUnits('JPY')).toBe(1); - expect(IOUUtils.getCurrencyUnits('USD')).toBe(100); + expect(IOUUtils.getCurrencyUnit('JPY')).toBe(1); + expect(IOUUtils.getCurrencyUnit('USD')).toBe(100); }); test('Currency with decimals larger than 2 should be floor to 2', () => { - expect(IOUUtils.getCurrencyUnits('LYD')).toBe(100); + expect(IOUUtils.getCurrencyUnit('LYD')).toBe(100); }); }); From d2b28c0741af109878056c15d26dd44e003fedd5 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sun, 16 Apr 2023 00:15:54 +0700 Subject: [PATCH 13/20] default currency param to USD --- src/libs/IOUUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index 0d3be7607bc1..f3ffc21ea107 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -24,7 +24,7 @@ Onyx.connect({ * @param {String} currency - IOU currency * @returns {Number} */ -function getCurrencyDecimals(currency) { +function getCurrencyDecimals(currency = CONST.CURRENCY.USD) { const decimals = lodashGet(currencyList, [currency, 'decimals']); return _.isUndefined(decimals) ? 2 : Math.min(decimals, 2); } @@ -36,7 +36,7 @@ function getCurrencyDecimals(currency) { * @param {String} currency - IOU currency * @returns {Number} */ -function getCurrencyUnit(currency) { +function getCurrencyUnit(currency = CONST.CURRENCY.USD) { return 10 ** getCurrencyDecimals(currency); } From 0f8cf46c02ad3766800efd22bfe14a932be0fa9a Mon Sep 17 00:00:00 2001 From: tienifr Date: Sun, 16 Apr 2023 00:24:37 +0700 Subject: [PATCH 14/20] keep original indents currencyList.json --- tests/unit/currencyList.json | 1093 +++++++++++++++++----------------- 1 file changed, 546 insertions(+), 547 deletions(-) diff --git a/tests/unit/currencyList.json b/tests/unit/currencyList.json index 1283a854f7a9..3995569912b7 100644 --- a/tests/unit/currencyList.json +++ b/tests/unit/currencyList.json @@ -1,876 +1,875 @@ { "AED": { - "symbol": "Dhs", - "name": "UAE Dirham", - "ISO4217": "784" + "symbol": "Dhs", + "name": "UAE Dirham", + "ISO4217": "784" }, "AFN": { - "symbol": "Af", - "name": "Afghan Afghani", - "decimals": 0, - "ISO4217": "971" + "symbol": "Af", + "name": "Afghan Afghani", + "decimals": 0, + "ISO4217": "971" }, "ALL": { - "symbol": "ALL", - "name": "Albanian Lek", - "decimals": 0, - "ISO4217": "008" + "symbol": "ALL", + "name": "Albanian Lek", + "decimals": 0, + "ISO4217": "008" }, "AMD": { - "symbol": "դր", - "name": "Armenian Dram", - "ISO4217": "051" + "symbol": "դր", + "name": "Armenian Dram", + "ISO4217": "051" }, "ANG": { - "symbol": "NAƒ", - "name": "Neth Antilles Guilder", - "ISO4217": "532" + "symbol": "NAƒ", + "name": "Neth Antilles Guilder", + "ISO4217": "532" }, "AOA": { - "symbol": "Kz", - "name": "Angolan Kwanza", - "ISO4217": "973" + "symbol": "Kz", + "name": "Angolan Kwanza", + "ISO4217": "973" }, "ARS": { - "symbol": "AR$", - "name": "Argentine Peso", - "ISO4217": "032" + "symbol": "AR$", + "name": "Argentine Peso", + "ISO4217": "032" }, "AUD": { - "symbol": "A$", - "name": "Australian Dollar", - "ISO4217": "036" + "symbol": "A$", + "name": "Australian Dollar", + "ISO4217": "036" }, "AWG": { - "symbol": "ƒ", - "name": "Aruba Florin", - "ISO4217": "533" + "symbol": "ƒ", + "name": "Aruba Florin", + "ISO4217": "533" }, "AZN": { - "symbol": "man", - "name": "Azerbaijani Manat", - "ISO4217": "944" + "symbol": "man", + "name": "Azerbaijani Manat", + "ISO4217": "944" }, "BAM": { - "symbol": "KM", - "name": "Bosnia And Herzegovina Convertible Mark", - "ISO4217": "977" + "symbol": "KM", + "name": "Bosnia And Herzegovina Convertible Mark", + "ISO4217": "977" }, "BBD": { - "symbol": "Bds$", - "name": "Barbados Dollar", - "ISO4217": "052" + "symbol": "Bds$", + "name": "Barbados Dollar", + "ISO4217": "052" }, "BDT": { - "symbol": "Tk", - "name": "Bangladesh Taka", - "ISO4217": "050" + "symbol": "Tk", + "name": "Bangladesh Taka", + "ISO4217": "050" }, "BGN": { - "symbol": "лв", - "name": "Bulgarian Lev", - "ISO4217": "975" + "symbol": "лв", + "name": "Bulgarian Lev", + "ISO4217": "975" }, "BHD": { - "symbol": "BHD", - "name": "Bahraini Dinar", - "ISO4217": "048" + "symbol": "BHD", + "name": "Bahraini Dinar", + "ISO4217": "048" }, "BIF": { - "symbol": "FBu", - "name": "Burundi Franc", - "decimals": 0, - "ISO4217": "108" + "symbol": "FBu", + "name": "Burundi Franc", + "decimals": 0, + "ISO4217": "108" }, "BMD": { - "symbol": "BD$", - "name": "Bermuda Dollar", - "ISO4217": "060" + "symbol": "BD$", + "name": "Bermuda Dollar", + "ISO4217": "060" }, "BND": { - "symbol": "BN$", - "name": "Brunei Dollar", - "ISO4217": "096" + "symbol": "BN$", + "name": "Brunei Dollar", + "ISO4217": "096" }, "BOB": { - "symbol": "Bs", - "name": "Bolivian Boliviano", - "ISO4217": "068" + "symbol": "Bs", + "name": "Bolivian Boliviano", + "ISO4217": "068" }, "BRL": { - "symbol": "R$", - "name": "Brazilian Real", - "ISO4217": "986" + "symbol": "R$", + "name": "Brazilian Real", + "ISO4217": "986" }, "BSD": { - "symbol": "BS$", - "name": "Bahamian Dollar", - "ISO4217": "044" + "symbol": "BS$", + "name": "Bahamian Dollar", + "ISO4217": "044" }, "BTN": { - "symbol": "Nu.", - "name": "Bhutan Ngultrum", - "ISO4217": "064" + "symbol": "Nu.", + "name": "Bhutan Ngultrum", + "ISO4217": "064" }, "BWP": { - "symbol": "P", - "name": "Botswana Pula", - "ISO4217": "072" + "symbol": "P", + "name": "Botswana Pula", + "ISO4217": "072" }, "BYN": { - "symbol": "BR", - "name": "Belarus Ruble", - "ISO4217": "933" + "symbol": "BR", + "name": "Belarus Ruble", + "ISO4217": "933" }, "BYR": { - "symbol": "BR", - "name": "Belarus Ruble", - "decimals": 0, - "retired": true, - "retirementDate": "2016-07-01", - "ISO4217": "974" + "symbol": "BR", + "name": "Belarus Ruble", + "decimals": 0, + "retired": true, + "retirementDate": "2016-07-01", + "ISO4217": "974" }, "BZD": { - "symbol": "BZ$", - "name": "Belize Dollar", - "ISO4217": "084" + "symbol": "BZ$", + "name": "Belize Dollar", + "ISO4217": "084" }, "CAD": { - "symbol": "C$", - "name": "Canadian Dollar", - "ISO4217": "124" + "symbol": "C$", + "name": "Canadian Dollar", + "ISO4217": "124" }, "CDF": { - "symbol": "CDF", - "name": "Congolese Franc", - "ISO4217": "976" + "symbol": "CDF", + "name": "Congolese Franc", + "ISO4217": "976" }, "CHF": { - "symbol": "CHF", - "name": "Swiss Franc", - "ISO4217": "756" + "symbol": "CHF", + "name": "Swiss Franc", + "ISO4217": "756" }, "CLP": { - "symbol": "Ch$", - "name": "Chilean Peso", - "decimals": 0, - "ISO4217": "152" + "symbol": "Ch$", + "name": "Chilean Peso", + "decimals": 0, + "ISO4217": "152" }, "CNY": { - "symbol": "¥", - "name": "Chinese Yuan", - "ISO4217": "156" + "symbol": "¥", + "name": "Chinese Yuan", + "ISO4217": "156" }, "COP": { - "symbol": "Col$", - "name": "Colombian Peso", - "decimals": 0, - "ISO4217": "170" + "symbol": "Col$", + "name": "Colombian Peso", + "decimals": 0, + "ISO4217": "170" }, "CRC": { - "symbol": "CR₡", - "name": "Costa Rica Colon", - "ISO4217": "188" + "symbol": "CR₡", + "name": "Costa Rica Colon", + "ISO4217": "188" }, "CUC": { - "symbol": "CUC", - "name": "Cuban Convertible Peso", - "ISO4217": "931" + "symbol": "CUC", + "name": "Cuban Convertible Peso", + "ISO4217": "931" }, "CUP": { - "symbol": "$MN", - "name": "Cuban Peso", - "ISO4217": "192" + "symbol": "$MN", + "name": "Cuban Peso", + "ISO4217": "192" }, "CVE": { - "symbol": "Esc", - "name": "Cape Verde Escudo", - "ISO4217": "132" + "symbol": "Esc", + "name": "Cape Verde Escudo", + "ISO4217": "132" }, "CZK": { - "symbol": "Kč", - "name": "Czech Koruna", - "ISO4217": "203" + "symbol": "Kč", + "name": "Czech Koruna", + "ISO4217": "203" }, "DJF": { - "symbol": "Fdj", - "name": "Dijibouti Franc", - "decimals": 0, - "ISO4217": "262" + "symbol": "Fdj", + "name": "Dijibouti Franc", + "decimals": 0, + "ISO4217": "262" }, "DKK": { - "symbol": "Dkr", - "name": "Danish Krone", - "ISO4217": "208" + "symbol": "Dkr", + "name": "Danish Krone", + "ISO4217": "208" }, "DOP": { - "symbol": "RD$", - "name": "Dominican Peso", - "ISO4217": "214" + "symbol": "RD$", + "name": "Dominican Peso", + "ISO4217": "214" }, "DZD": { - "symbol": "DZD", - "name": "Algerian Dinar", - "ISO4217": "012" + "symbol": "DZD", + "name": "Algerian Dinar", + "ISO4217": "012" }, "EEK": { - "symbol": "KR", - "name": "Estonian Kroon", - "ISO4217": "", - "retired": true + "symbol": "KR", + "name": "Estonian Kroon", + "ISO4217": "", + "retired": true }, "EGP": { - "symbol": "EGP", - "name": "Egyptian Pound", - "ISO4217": "818" + "symbol": "EGP", + "name": "Egyptian Pound", + "ISO4217": "818" }, "ERN": { - "symbol": "Nfk", - "name": "Eritrea Nakfa", - "ISO4217": "232" + "symbol": "Nfk", + "name": "Eritrea Nakfa", + "ISO4217": "232" }, "ETB": { - "symbol": "Br", - "name": "Ethiopian Birr", - "ISO4217": "230" + "symbol": "Br", + "name": "Ethiopian Birr", + "ISO4217": "230" }, "EUR": { - "symbol": "€", - "name": "Euro", - "ISO4217": "978" + "symbol": "€", + "name": "Euro", + "ISO4217": "978" }, "FJD": { - "symbol": "FJ$", - "name": "Fiji Dollar", - "ISO4217": "242" + "symbol": "FJ$", + "name": "Fiji Dollar", + "ISO4217": "242" }, "FKP": { - "symbol": "FK£", - "name": "Falkland Islands Pound", - "ISO4217": "238" + "symbol": "FK£", + "name": "Falkland Islands Pound", + "ISO4217": "238" }, "GBP": { - "symbol": "£", - "name": "British Pound", - "ISO4217": "826" + "symbol": "£", + "name": "British Pound", + "ISO4217": "826" }, "GEL": { - "symbol": "ლ", - "name": "Georgian Lari", - "ISO4217": "981" + "symbol": "ლ", + "name": "Georgian Lari", + "ISO4217": "981" }, "GHS": { - "symbol": "₵", - "name": "Ghanaian Cedi", - "ISO4217": "936" + "symbol": "₵", + "name": "Ghanaian Cedi", + "ISO4217": "936" }, "GIP": { - "symbol": "£G", - "name": "Gibraltar Pound", - "ISO4217": "292" + "symbol": "£G", + "name": "Gibraltar Pound", + "ISO4217": "292" }, "GMD": { - "symbol": "D", - "name": "Gambian Dalasi", - "ISO4217": "270" + "symbol": "D", + "name": "Gambian Dalasi", + "ISO4217": "270" }, "GNF": { - "symbol": "FG", - "name": "Guinea Franc", - "decimals": 0, - "ISO4217": "324" + "symbol": "FG", + "name": "Guinea Franc", + "decimals": 0, + "ISO4217": "324" }, "GTQ": { - "symbol": "Q", - "name": "Guatemala Quetzal", - "ISO4217": "320" + "symbol": "Q", + "name": "Guatemala Quetzal", + "ISO4217": "320" }, "GYD": { - "symbol": "GY$", - "name": "Guyana Dollar", - "ISO4217": "328" + "symbol": "GY$", + "name": "Guyana Dollar", + "ISO4217": "328" }, "HKD": { - "symbol": "HK$", - "name": "Hong Kong Dollar", - "ISO4217": "344" + "symbol": "HK$", + "name": "Hong Kong Dollar", + "ISO4217": "344" }, "HNL": { - "symbol": "HNL", - "name": "Honduras Lempira", - "ISO4217": "340" + "symbol": "HNL", + "name": "Honduras Lempira", + "ISO4217": "340" }, "HRK": { - "symbol": "kn", - "name": "Croatian Kuna", - "ISO4217": "191" + "symbol": "kn", + "name": "Croatian Kuna", + "ISO4217": "191" }, "HTG": { - "symbol": "G", - "name": "Haiti Gourde", - "ISO4217": "332" + "symbol": "G", + "name": "Haiti Gourde", + "ISO4217": "332" }, "HUF": { - "symbol": "Ft", - "name": "Hungarian Forint", - "ISO4217": "348" + "symbol": "Ft", + "name": "Hungarian Forint", + "ISO4217": "348" }, "IDR": { - "symbol": "Rp", - "name": "Indonesian Rupiah", - "ISO4217": "360" + "symbol": "Rp", + "name": "Indonesian Rupiah", + "ISO4217": "360" }, "ILS": { - "symbol": "₪", - "name": "Israeli Shekel", - "ISO4217": "376" + "symbol": "₪", + "name": "Israeli Shekel", + "ISO4217": "376" }, "INR": { - "symbol": "₹", - "name": "Indian Rupee", - "ISO4217": "356" + "symbol": "₹", + "name": "Indian Rupee", + "ISO4217": "356" }, "IQD": { - "symbol": "IQD", - "name": "Iraqi Dinar", - "decimals": 0, - "ISO4217": "368" + "symbol": "IQD", + "name": "Iraqi Dinar", + "decimals": 0, + "ISO4217": "368" }, "IRR": { - "symbol": "﷼", - "name": "Iran Rial", - "decimals": 0, - "ISO4217": "364" + "symbol": "﷼", + "name": "Iran Rial", + "decimals": 0, + "ISO4217": "364" }, "ISK": { - "symbol": "kr", - "name": "Iceland Krona", - "decimals": 0, - "ISO4217": "352" + "symbol": "kr", + "name": "Iceland Krona", + "decimals": 0, + "ISO4217": "352" }, "JMD": { - "symbol": "J$", - "name": "Jamaican Dollar", - "ISO4217": "388" + "symbol": "J$", + "name": "Jamaican Dollar", + "ISO4217": "388" }, "JOD": { - "symbol": "JOD", - "name": "Jordanian Dinar", - "ISO4217": "400" + "symbol": "JOD", + "name": "Jordanian Dinar", + "ISO4217": "400" }, "JPY": { - "symbol": "¥", - "name": "Japanese Yen", - "decimals": 0, - "ISO4217": "392" + "symbol": "¥", + "name": "Japanese Yen", + "decimals": 0, + "ISO4217": "392" }, "KES": { - "symbol": "KSh", - "name": "Kenyan Shilling", - "ISO4217": "404" + "symbol": "KSh", + "name": "Kenyan Shilling", + "ISO4217": "404" }, "KGS": { - "symbol": "KGS", - "name": "Kyrgyzstani Som", - "ISO4217": "417" + "symbol": "KGS", + "name": "Kyrgyzstani Som", + "ISO4217": "417" }, "KHR": { - "symbol": "KHR", - "name": "Cambodia Riel", - "ISO4217": "116" + "symbol": "KHR", + "name": "Cambodia Riel", + "ISO4217": "116" }, "KMF": { - "symbol": "CF", - "name": "Comoros Franc", - "decimals": 0, - "ISO4217": "174" + "symbol": "CF", + "name": "Comoros Franc", + "decimals": 0, + "ISO4217": "174" }, "KPW": { - "symbol": "KP₩", - "name": "North Korean Won", - "decimals": 0, - "ISO4217": "408" + "symbol": "KP₩", + "name": "North Korean Won", + "decimals": 0, + "ISO4217": "408" }, "KRW": { - "symbol": "₩", - "name": "Korean Won", - "decimals": 0, - "ISO4217": "410" + "symbol": "₩", + "name": "Korean Won", + "decimals": 0, + "ISO4217": "410" }, "KWD": { - "symbol": "KWD", - "name": "Kuwaiti Dinar", - "ISO4217": "414" + "symbol": "KWD", + "name": "Kuwaiti Dinar", + "ISO4217": "414" }, "KYD": { - "symbol": "CI$", - "name": "Cayman Islands Dollar", - "ISO4217": "136" + "symbol": "CI$", + "name": "Cayman Islands Dollar", + "ISO4217": "136" }, "KZT": { - "symbol": "〒", - "name": "Kazakhstan Tenge", - "ISO4217": "398" + "symbol": "〒", + "name": "Kazakhstan Tenge", + "ISO4217": "398" }, "LAK": { - "symbol": "₭", - "name": "Lao Kip", - "decimals": 0, - "ISO4217": "418" + "symbol": "₭", + "name": "Lao Kip", + "decimals": 0, + "ISO4217": "418" }, "LBP": { - "symbol": "LBP", - "name": "Lebanese Pound", - "decimals": 0, - "ISO4217": "422" + "symbol": "LBP", + "name": "Lebanese Pound", + "decimals": 0, + "ISO4217": "422" }, "LKR": { - "symbol": "SL₨", - "name": "Sri Lanka Rupee", - "ISO4217": "144" + "symbol": "SL₨", + "name": "Sri Lanka Rupee", + "ISO4217": "144" }, "LRD": { - "symbol": "L$", - "name": "Liberian Dollar", - "ISO4217": "430" + "symbol": "L$", + "name": "Liberian Dollar", + "ISO4217": "430" }, "LSL": { - "symbol": "M", - "name": "Lesotho Loti", - "ISO4217": "426" + "symbol": "M", + "name": "Lesotho Loti", + "ISO4217": "426" }, "LTL": { - "symbol": "Lt", - "name": "Lithuanian Lita", - "retirementDate": "2015-08-22", - "retired": true, - "ISO4217": "440" + "symbol": "Lt", + "name": "Lithuanian Lita", + "retirementDate": "2015-08-22", + "retired": true, + "ISO4217": "440" }, "LVL": { - "symbol": "Ls", - "name": "Latvian Lat", - "ISO4217": "428", - "retired": true + "symbol": "Ls", + "name": "Latvian Lat", + "ISO4217": "428", + "retired": true }, "LYD": { - "symbol": "LYD", - "name": "Libyan Dinar", - "ISO4217": "434" + "symbol": "LYD", + "name": "Libyan Dinar", + "ISO4217": "434" }, "MAD": { - "symbol": "MAD", - "name": "Moroccan Dirham", - "ISO4217": "504" + "symbol": "MAD", + "name": "Moroccan Dirham", + "ISO4217": "504" }, "MDL": { - "symbol": "MDL", - "name": "Moldovan Leu", - "ISO4217": "498" + "symbol": "MDL", + "name": "Moldovan Leu", + "ISO4217": "498" }, "MGA": { - "symbol": "MGA", - "name": "Malagasy Ariary", - "decimals": 0, - "ISO4217": "969" + "symbol": "MGA", + "name": "Malagasy Ariary", + "decimals": 0, + "ISO4217": "969" }, "MKD": { - "symbol": "ден", - "name": "Macedonian Denar", - "ISO4217": "807" + "symbol": "ден", + "name": "Macedonian Denar", + "ISO4217": "807" }, "MMK": { - "symbol": "Ks", - "name": "Myanmar Kyat", - "decimals": 0, - "ISO4217": "104" + "symbol": "Ks", + "name": "Myanmar Kyat", + "decimals": 0, + "ISO4217": "104" }, "MNT": { - "symbol": "₮", - "name": "Mongolian Tugrik", - "ISO4217": "496" + "symbol": "₮", + "name": "Mongolian Tugrik", + "ISO4217": "496" }, "MOP": { - "symbol": "MOP$", - "name": "Macau Pataca", - "ISO4217": "446" + "symbol": "MOP$", + "name": "Macau Pataca", + "ISO4217": "446" }, "MRO": { - "symbol": "UM", - "name": "Mauritania Ougulya", - "decimals": 0, - "retired": true, - "retirementDate": "2018-07-11", - "ISO4217": "478" + "symbol": "UM", + "name": "Mauritania Ougulya", + "decimals": 0, + "retired": true, + "retirementDate": "2018-07-11", + "ISO4217": "478" }, "MRU": { - "symbol": "UM", - "name": "Mauritania Ougulya", - "decimals": 0, - "ISO4217": "" + "symbol": "UM", + "name": "Mauritania Ougulya", + "decimals": 0, + "ISO4217": "" }, "MUR": { - "symbol": "Rs", - "name": "Mauritius Rupee", - "ISO4217": "480" + "symbol": "Rs", + "name": "Mauritius Rupee", + "ISO4217": "480" }, "MVR": { - "symbol": "Rf", - "name": "Maldives Rufiyaa", - "ISO4217": "462" + "symbol": "Rf", + "name": "Maldives Rufiyaa", + "ISO4217": "462" }, "MWK": { - "symbol": "MK", - "name": "Malawi Kwacha", - "ISO4217": "454" + "symbol": "MK", + "name": "Malawi Kwacha", + "ISO4217": "454" }, "MXN": { - "symbol": "Mex$", - "name": "Mexican Peso", - "ISO4217": "484" + "symbol": "Mex$", + "name": "Mexican Peso", + "ISO4217": "484" }, "MYR": { - "symbol": "RM", - "name": "Malaysian Ringgit", - "ISO4217": "458" + "symbol": "RM", + "name": "Malaysian Ringgit", + "ISO4217": "458" }, "MZN": { - "symbol": "MTn", - "name": "Mozambican Metical", - "ISO4217": "943" + "symbol": "MTn", + "name": "Mozambican Metical", + "ISO4217": "943" }, "NAD": { - "symbol": "N$", - "name": "Namibian Dollar", - "ISO4217": "516" + "symbol": "N$", + "name": "Namibian Dollar", + "ISO4217": "516" }, "NGN": { - "symbol": "₦", - "name": "Nigerian Naira", - "ISO4217": "566" + "symbol": "₦", + "name": "Nigerian Naira", + "ISO4217": "566" }, "NIO": { - "symbol": "NIO", - "name": "Nicaragua Cordoba", - "ISO4217": "558" + "symbol": "NIO", + "name": "Nicaragua Cordoba", + "ISO4217": "558" }, "NOK": { - "symbol": "Nkr", - "name": "Norwegian Krone", - "ISO4217": "578" + "symbol": "Nkr", + "name": "Norwegian Krone", + "ISO4217": "578" }, "NPR": { - "symbol": "₨", - "name": "Nepalese Rupee", - "ISO4217": "524" + "symbol": "₨", + "name": "Nepalese Rupee", + "ISO4217": "524" }, "NZD": { - "symbol": "NZ$", - "name": "New Zealand Dollar", - "ISO4217": "554" + "symbol": "NZ$", + "name": "New Zealand Dollar", + "ISO4217": "554" }, "OMR": { - "symbol": "OMR", - "name": "Omani Rial", - "ISO4217": "512" + "symbol": "OMR", + "name": "Omani Rial", + "ISO4217": "512" }, "PAB": { - "symbol": "B", - "name": "Panama Balboa", - "ISO4217": "590" + "symbol": "B", + "name": "Panama Balboa", + "ISO4217": "590" }, "PEN": { - "symbol": "S/.", - "name": "Peruvian Nuevo Sol", - "ISO4217": "604" + "symbol": "S/.", + "name": "Peruvian Nuevo Sol", + "ISO4217": "604" }, "PGK": { - "symbol": "K", - "name": "Papua New Guinea Kina", - "ISO4217": "598" + "symbol": "K", + "name": "Papua New Guinea Kina", + "ISO4217": "598" }, "PHP": { - "symbol": "₱", - "name": "Philippine Peso", - "ISO4217": "608" + "symbol": "₱", + "name": "Philippine Peso", + "ISO4217": "608" }, "PKR": { - "symbol": "Rs", - "name": "Pakistani Rupee", - "ISO4217": "586" + "symbol": "Rs", + "name": "Pakistani Rupee", + "ISO4217": "586" }, "PLN": { - "symbol": "zł", - "name": "Polish Zloty", - "ISO4217": "985" + "symbol": "zł", + "name": "Polish Zloty", + "ISO4217": "985" }, "PYG": { - "symbol": "₲", - "name": "Paraguayan Guarani", - "decimals": 0, - "ISO4217": "600" + "symbol": "₲", + "name": "Paraguayan Guarani", + "decimals": 0, + "ISO4217": "600" }, "QAR": { - "symbol": "QAR", - "name": "Qatar Rial", - "ISO4217": "634" + "symbol": "QAR", + "name": "Qatar Rial", + "ISO4217": "634" }, "RON": { - "symbol": "RON", - "name": "Romanian New Leu", - "ISO4217": "946" + "symbol": "RON", + "name": "Romanian New Leu", + "ISO4217": "946" }, "RSD": { - "symbol": "РСД", - "name": "Serbian Dinar", - "decimals": 0, - "ISO4217": "941" + "symbol": "РСД", + "name": "Serbian Dinar", + "decimals": 0, + "ISO4217": "941" }, "RUB": { - "symbol": "₽", - "name": "Russian Rouble", - "ISO4217": "643" + "symbol": "₽", + "name": "Russian Rouble", + "ISO4217": "643" }, "RWF": { - "symbol": "RF", - "name": "Rwanda Franc", - "decimals": 0, - "ISO4217": "646" + "symbol": "RF", + "name": "Rwanda Franc", + "decimals": 0, + "ISO4217": "646" }, "SAR": { - "symbol": "SAR", - "name": "Saudi Arabian Riyal", - "ISO4217": "682" + "symbol": "SAR", + "name": "Saudi Arabian Riyal", + "ISO4217": "682" }, "SBD": { - "symbol": "SI$", - "name": "Solomon Islands Dollar", - "ISO4217": "090" + "symbol": "SI$", + "name": "Solomon Islands Dollar", + "ISO4217": "090" }, "SCR": { - "symbol": "SR", - "name": "Seychelles Rupee", - "ISO4217": "690" + "symbol": "SR", + "name": "Seychelles Rupee", + "ISO4217": "690" }, "SDG": { - "symbol": "SDG", - "name": "Sudanese Pound", - "ISO4217": "938" + "symbol": "SDG", + "name": "Sudanese Pound", + "ISO4217": "938" }, "SEK": { - "symbol": "Skr", - "name": "Swedish Krona", - "ISO4217": "752" + "symbol": "Skr", + "name": "Swedish Krona", + "ISO4217": "752" }, "SGD": { - "symbol": "S$", - "name": "Singapore Dollar", - "ISO4217": "702" + "symbol": "S$", + "name": "Singapore Dollar", + "ISO4217": "702" }, "SHP": { - "symbol": "£S", - "name": "St Helena Pound", - "ISO4217": "654" + "symbol": "£S", + "name": "St Helena Pound", + "ISO4217": "654" }, "SLL": { - "symbol": "Le", - "name": "Sierra Leone Leone", - "decimals": 0, - "ISO4217": "694" + "symbol": "Le", + "name": "Sierra Leone Leone", + "decimals": 0, + "ISO4217": "694" }, "SOS": { - "symbol": "So.", - "name": "Somali Shilling", - "decimals": 0, - "ISO4217": "706" + "symbol": "So.", + "name": "Somali Shilling", + "decimals": 0, + "ISO4217": "706" }, "SRD": { - "symbol": "SRD", - "name": "Surinamese Dollar", - "ISO4217": "968" + "symbol": "SRD", + "name": "Surinamese Dollar", + "ISO4217": "968" }, "STD": { - "symbol": "Db", - "name": "Sao Tome Dobra", - "decimals": 0, - "retired": true, - "retirementDate": "2018-07-11", - "ISO4217": "678" + "symbol": "Db", + "name": "Sao Tome Dobra", + "decimals": 0, + "retired": true, + "retirementDate": "2018-07-11", + "ISO4217": "678" }, "STN": { - "symbol": "Db", - "name": "Sao Tome Dobra", - "ISO4217": "" + "symbol": "Db", + "name": "Sao Tome Dobra", + "ISO4217": "" }, "SVC": { - "symbol": "SVC", - "name": "El Salvador Colon", - "ISO4217": "222" + "symbol": "SVC", + "name": "El Salvador Colon", + "ISO4217": "222" }, "SYP": { - "symbol": "SYP", - "name": "Syrian Pound", - "decimals": 0, - "ISO4217": "760" + "symbol": "SYP", + "name": "Syrian Pound", + "decimals": 0, + "ISO4217": "760" }, "SZL": { - "symbol": "E", - "name": "Swaziland Lilageni", - "ISO4217": "748" + "symbol": "E", + "name": "Swaziland Lilageni", + "ISO4217": "748" }, "THB": { - "symbol": "฿", - "name": "Thai Baht", - "ISO4217": "764" + "symbol": "฿", + "name": "Thai Baht", + "ISO4217": "764" }, "TJS": { - "symbol": "TJS", - "name": "Tajikistani Somoni", - "ISO4217": "972" + "symbol": "TJS", + "name": "Tajikistani Somoni", + "ISO4217": "972" }, "TMT": { - "symbol": "m", - "name": "Turkmenistani Manat", - "ISO4217": "934" + "symbol": "m", + "name": "Turkmenistani Manat", + "ISO4217": "934" }, "TND": { - "symbol": "TND", - "name": "Tunisian Dinar", - "ISO4217": "788" + "symbol": "TND", + "name": "Tunisian Dinar", + "ISO4217": "788" }, "TOP": { - "symbol": "T$", - "name": "Tonga Pa'ang", - "ISO4217": "776" + "symbol": "T$", + "name": "Tonga Pa'ang", + "ISO4217": "776" }, "TRY": { - "symbol": "TL", - "name": "Turkish Lira", - "ISO4217": "949" + "symbol": "TL", + "name": "Turkish Lira", + "ISO4217": "949" }, "TTD": { - "symbol": "TT$", - "name": "Trinidad & Tobago Dollar", - "ISO4217": "780" + "symbol": "TT$", + "name": "Trinidad & Tobago Dollar", + "ISO4217": "780" }, "TWD": { - "symbol": "NT$", - "name": "Taiwan Dollar", - "ISO4217": "901" + "symbol": "NT$", + "name": "Taiwan Dollar", + "ISO4217": "901" }, "TZS": { - "symbol": "TZS", - "name": "Tanzanian Shilling", - "ISO4217": "834" + "symbol": "TZS", + "name": "Tanzanian Shilling", + "ISO4217": "834" }, "UAH": { - "symbol": "₴", - "name": "Ukraine Hryvnia", - "ISO4217": "980" + "symbol": "₴", + "name": "Ukraine Hryvnia", + "ISO4217": "980" }, "UGX": { - "symbol": "USh", - "name": "Ugandan Shilling", - "decimals": 0, - "ISO4217": "800" + "symbol": "USh", + "name": "Ugandan Shilling", + "decimals": 0, + "ISO4217": "800" }, "USD": { - "symbol": "$", - "name": "United States Dollar", - "ISO4217": "840" + "symbol": "$", + "name": "United States Dollar", + "ISO4217": "840" }, "UYU": { - "symbol": "$U", - "name": "Uruguayan New Peso", - "ISO4217": "858" + "symbol": "$U", + "name": "Uruguayan New Peso", + "ISO4217": "858" }, "UZS": { - "symbol": "UZS", - "name": "Uzbekistani Som", - "ISO4217": "860" + "symbol": "UZS", + "name": "Uzbekistani Som", + "ISO4217": "860" }, "VEB": { - "symbol": "Bs.", - "name": "Venezuelan Bolivar", - "retired": true, - "retirementDate": "2008-02-01", - "ISO4217": "" + "symbol": "Bs.", + "name": "Venezuelan Bolivar", + "retired": true, + "retirementDate": "2008-02-01", + "ISO4217": "" }, "VEF": { - "symbol": "Bs.F", - "name": "Venezuelan Bolivar Fuerte", - "retired": true, - "retirementDate": "2018-08-20", - "ISO4217": "937" + "symbol": "Bs.F", + "name": "Venezuelan Bolivar Fuerte", + "retired": true, + "retirementDate": "2018-08-20", + "ISO4217": "937" }, "VES": { - "symbol": "Bs.S", - "name": "Venezuelan Bolivar Soberano", - "ISO4217": "928" + "symbol": "Bs.S", + "name": "Venezuelan Bolivar Soberano", + "ISO4217": "928" }, "VND": { - "symbol": "₫", - "name": "Vietnam Dong", - "decimals": 0, - "ISO4217": "704" + "symbol": "₫", + "name": "Vietnam Dong", + "decimals": 0, + "ISO4217": "704" }, "VUV": { - "symbol": "Vt", - "name": "Vanuatu Vatu", - "decimals": 0, - "ISO4217": "548" + "symbol": "Vt", + "name": "Vanuatu Vatu", + "decimals": 0, + "ISO4217": "548" }, "WST": { - "symbol": "WS$", - "name": "Samoa Tala", - "ISO4217": "882" + "symbol": "WS$", + "name": "Samoa Tala", + "ISO4217": "882" }, "XAF": { - "symbol": "FCFA", - "name": "CFA Franc (BEAC)", - "decimals": 0, - "ISO4217": "950" + "symbol": "FCFA", + "name": "CFA Franc (BEAC)", + "decimals": 0, + "ISO4217": "950" }, "XCD": { - "symbol": "EC$", - "name": "East Caribbean Dollar", - "ISO4217": "951" + "symbol": "EC$", + "name": "East Caribbean Dollar", + "ISO4217": "951" }, "XOF": { - "symbol": "CFA", - "name": "CFA Franc (BCEAO)", - "decimals": 0, - "ISO4217": "952" + "symbol": "CFA", + "name": "CFA Franc (BCEAO)", + "decimals": 0, + "ISO4217": "952" }, "XPF": { - "symbol": "XPF", - "name": "Pacific Franc", - "decimals": 0, - "ISO4217": "953" + "symbol": "XPF", + "name": "Pacific Franc", + "decimals": 0, + "ISO4217": "953" }, "YER": { - "symbol": "YER", - "name": "Yemen Riyal", - "decimals": 0, - "ISO4217": "886" + "symbol": "YER", + "name": "Yemen Riyal", + "decimals": 0, + "ISO4217": "886" }, "ZAR": { - "symbol": "R", - "name": "South African Rand", - "ISO4217": "710" + "symbol": "R", + "name": "South African Rand", + "ISO4217": "710" }, "ZMK": { - "symbol": "ZK", - "name": "Zambian Kwacha", - "decimals": 0, - "retired": true, - "retirementDate": "2013-01-01", - "ISO4217": "894" + "symbol": "ZK", + "name": "Zambian Kwacha", + "decimals": 0, + "retired": true, + "retirementDate": "2013-01-01", + "ISO4217": "894" }, "ZMW": { - "symbol": "ZMW", - "name": "Zambian Kwacha", - "cacheBurst": 1, - "ISO4217": "967" + "symbol": "ZMW", + "name": "Zambian Kwacha", + "cacheBurst": 1, + "ISO4217": "967" } -} - \ No newline at end of file + } \ No newline at end of file From 1a931f171b728b8c690db3a5abe1288a75722022 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sun, 16 Apr 2023 00:35:09 +0700 Subject: [PATCH 15/20] add edge case unit test for calculateAmount --- tests/unit/IOUUtilsTest.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 180948c21cc6..58ba38839cf1 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -187,4 +187,10 @@ describe('calculateAmount', () => { expect(IOUUtils.calculateAmount(participants, 10, 'AFN', true)).toBe(1); expect(IOUUtils.calculateAmount(participants, 10, 'AFN')).toBe(3); }); + + test('0.02 USD split among 4 participants including the default user should be [-1, 1, 1, 1]', () => { + const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com', 'suestorm@expensify.com']; + expect(IOUUtils.calculateAmount(participants, 0.02, 'USD', true)).toBe(-1); + expect(IOUUtils.calculateAmount(participants, 0.02, 'USD')).toBe(1); + }); }); From 1d733173550a6963beaec4406d035af76f8f5aa8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 17 Apr 2023 09:40:30 +0700 Subject: [PATCH 16/20] add unit test for calculateAmount with currency having two decimals --- tests/unit/IOUUtilsTest.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 58ba38839cf1..03ab29412aca 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -162,7 +162,7 @@ describe('getCurrencyDecimals', () => { }); }); -describe('getCurrencyUnits', () => { +describe('getCurrencyUnit', () => { beforeAll(() => initCurrencyList()); test('Currency with decimals smaller than or equal 2', () => { expect(IOUUtils.getCurrencyUnit('JPY')).toBe(1); @@ -188,6 +188,12 @@ describe('calculateAmount', () => { expect(IOUUtils.calculateAmount(participants, 10, 'AFN')).toBe(3); }); + test('10 BHD split among 3 participants including the default user should be [334, 333, 333]', () => { + const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com']; + expect(IOUUtils.calculateAmount(participants, 10, 'BHD', true)).toBe(334); + expect(IOUUtils.calculateAmount(participants, 10, 'BHD')).toBe(333); + }); + test('0.02 USD split among 4 participants including the default user should be [-1, 1, 1, 1]', () => { const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com', 'suestorm@expensify.com']; expect(IOUUtils.calculateAmount(participants, 0.02, 'USD', true)).toBe(-1); From a7359eab4bfa5083540c97cdda1ec4d1f8a6f72f Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 18 Apr 2023 16:28:47 +0700 Subject: [PATCH 17/20] remove shouldConvertTwo2DigitsFormat --- src/components/MoneyRequestConfirmationList.js | 6 ++---- src/libs/IOUUtils.js | 9 ++------- src/libs/actions/IOU.js | 4 ++-- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 420f47733f11..d2ce71a9d39b 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -139,11 +139,10 @@ class MoneyRequestConfirmationList extends Component { */ getParticipantsWithAmount(participants) { const iouAmount = IOUUtils.calculateAmount(participants, this.props.iouAmount, this.props.iou.selectedCurrencyCode); - const currencyUnit = IOUUtils.getCurrencyUnit(this.props.iou.selectedCurrencyCode); return OptionsListUtils.getIOUConfirmationOptionsFromParticipants( participants, - this.props.numberFormat(iouAmount / currencyUnit, { + this.props.numberFormat(iouAmount / 100, { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), @@ -176,10 +175,9 @@ class MoneyRequestConfirmationList extends Component { const formattedParticipants = _.union(formattedSelectedParticipants, formattedUnselectedParticipants); const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants, this.props.iouAmount, this.props.iou.selectedCurrencyCode, true); - const currencyUnit = IOUUtils.getCurrencyUnit(this.props.iou.selectedCurrencyCode); const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( this.props.currentUserPersonalDetails, - this.props.numberFormat(myIOUAmount / currencyUnit, { + this.props.numberFormat(myIOUAmount / 100, { style: 'currency', currency: this.props.iou.selectedCurrencyCode, }), diff --git a/src/libs/IOUUtils.js b/src/libs/IOUUtils.js index f3ffc21ea107..1151f9c77225 100644 --- a/src/libs/IOUUtils.js +++ b/src/libs/IOUUtils.js @@ -46,10 +46,9 @@ function getCurrencyUnit(currency = CONST.CURRENCY.USD) { * @param {Number} total - IOU total amount * @param {String} currency - IOU currency * @param {Boolean} isDefaultUser - Whether we are calculating the amount for the current user - * @param {Boolean} shouldConvertTo2DigitsFormat - Whether to convert the amount to 2-digits format * @returns {Number} */ -function calculateAmount(participants, total, currency, isDefaultUser = false, shouldConvertTo2DigitsFormat = false) { +function calculateAmount(participants, total, currency, isDefaultUser = false) { // Convert to cents before working with iouAmount to avoid // javascript subtraction with decimal problem -- when dealing with decimals, // because they are encoded as IEEE 754 floating point numbers, some of the decimal @@ -70,11 +69,7 @@ function calculateAmount(participants, total, currency, isDefaultUser = false, s finalAmount = iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; } - if (shouldConvertTo2DigitsFormat) { - finalAmount = (finalAmount * 100) / currencyUnit; - } - - return finalAmount; + return (finalAmount * 100) / currencyUnit; } /** diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 4126a12e8592..ed8c90cb8193 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -311,8 +311,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment ]; // Loop through participants creating individual chats, iouReports and reportActionIDs as needed - const splitAmount = IOUUtils.calculateAmount(participants, amount, currency, false, true); - const splits = [{email: currentUserEmail, amount: IOUUtils.calculateAmount(participants, amount, currency, true, true)}]; + const splitAmount = IOUUtils.calculateAmount(participants, amount, currency, false); + const splits = [{email: currentUserEmail, amount: IOUUtils.calculateAmount(participants, amount, currency, true)}]; const hasMultipleParticipants = participants.length > 1; _.each(participants, (participant) => { From 8d19eab4e2e2338cc4ffc46587045d83699d91bc Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 18 Apr 2023 16:34:42 +0700 Subject: [PATCH 18/20] update unit test for calculateAmount --- tests/unit/IOUUtilsTest.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 03ab29412aca..4ae4f3c5bc4b 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -178,14 +178,14 @@ describe('calculateAmount', () => { beforeAll(() => initCurrencyList()); test('103 JPY split among 3 participants including the default user should be [35, 34, 34]', () => { const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com']; - expect(IOUUtils.calculateAmount(participants, 103, 'JPY', true)).toBe(35); - expect(IOUUtils.calculateAmount(participants, 103, 'JPY')).toBe(34); + expect(IOUUtils.calculateAmount(participants, 103, 'JPY', true)).toBe(3500); + expect(IOUUtils.calculateAmount(participants, 103, 'JPY')).toBe(3400); }); test('10 AFN split among 4 participants including the default user should be [1, 3, 3, 3]', () => { const participants = ['tonystark@expensify.com', 'reedrichards@expensify.com', 'suestorm@expensify.com']; - expect(IOUUtils.calculateAmount(participants, 10, 'AFN', true)).toBe(1); - expect(IOUUtils.calculateAmount(participants, 10, 'AFN')).toBe(3); + expect(IOUUtils.calculateAmount(participants, 10, 'AFN', true)).toBe(100); + expect(IOUUtils.calculateAmount(participants, 10, 'AFN')).toBe(300); }); test('10 BHD split among 3 participants including the default user should be [334, 333, 333]', () => { From 36e33e8f41b04f030908bb1d8e1c3673476579e0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 18 Apr 2023 17:18:49 +0700 Subject: [PATCH 19/20] fix json indent --- tests/unit/currencyList.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/currencyList.json b/tests/unit/currencyList.json index 3995569912b7..3a4230d2eabf 100644 --- a/tests/unit/currencyList.json +++ b/tests/unit/currencyList.json @@ -872,4 +872,4 @@ "cacheBurst": 1, "ISO4217": "967" } - } \ No newline at end of file +} \ No newline at end of file From 1837007d231e9aa063834630288a49353bb288db Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 18 Apr 2023 17:34:34 +0700 Subject: [PATCH 20/20] add line break json --- tests/unit/currencyList.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/currencyList.json b/tests/unit/currencyList.json index 3a4230d2eabf..c6eda7bdd766 100644 --- a/tests/unit/currencyList.json +++ b/tests/unit/currencyList.json @@ -872,4 +872,4 @@ "cacheBurst": 1, "ISO4217": "967" } -} \ No newline at end of file +}