From bd31bf3e94909abc4fab2256ef07e0588e727148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 21 Sep 2023 10:09:32 +0200 Subject: [PATCH 1/4] migrate PaymentUtils to TS --- .../Icon/{BankIcons.js => BankIcons.ts} | 23 ++--- src/libs/PaymentUtils.js | 95 ------------------- src/libs/PaymentUtils.ts | 88 +++++++++++++++++ src/types/onyx/Fund.ts | 1 + 4 files changed, 101 insertions(+), 106 deletions(-) rename src/components/Icon/{BankIcons.js => BankIcons.ts} (90%) delete mode 100644 src/libs/PaymentUtils.js create mode 100644 src/libs/PaymentUtils.ts diff --git a/src/components/Icon/BankIcons.js b/src/components/Icon/BankIcons.ts similarity index 90% rename from src/components/Icon/BankIcons.js rename to src/components/Icon/BankIcons.ts index ff7c1fb95271..602c45157ae2 100644 --- a/src/components/Icon/BankIcons.js +++ b/src/components/Icon/BankIcons.ts @@ -1,4 +1,5 @@ -import _ from 'underscore'; +import {SvgProps} from 'react-native-svg'; +import {FC} from 'react'; import * as Expensicons from './Expensicons'; import AmericanExpress from '../../../assets/images/bankicons/american-express.svg'; import BankOfAmerica from '../../../assets/images/bankicons/bank-of-america.svg'; @@ -21,14 +22,16 @@ import USBank from '../../../assets/images/bankicons/us-bank.svg'; import USAA from '../../../assets/images/bankicons/usaa.svg'; import variables from '../../styles/variables'; +type BankIcon = { + icon: FC; + iconSize: number; +}; + /** * Returns matching asset icon for bankName - * @param {String} bankName - * @param {Boolean} isCard - * @returns {Object} */ -function getAssetIcon(bankName, isCard) { +function getAssetIcon(bankName: string, isCard: boolean): FC { if (bankName.includes('americanexpress')) { return AmericanExpress; } @@ -106,14 +109,12 @@ function getAssetIcon(bankName, isCard) { /** * Returns Bank Icon Object that matches to existing bank icons or default icons - * @param {String} bankName - * @param {Boolean} [isCard = false] - * @returns {Object} Object includes props icon, iconSize only if applicable */ -export default function getBankIcon(bankName, isCard) { - const bankIcon = { +export default function getBankIcon(bankName: string, isCard = false) { + const bankIcon: BankIcon = { icon: isCard ? Expensicons.CreditCard : GenericBank, + iconSize: 0, }; if (bankName) { @@ -121,7 +122,7 @@ export default function getBankIcon(bankName, isCard) { } // For default Credit Card icon the icon size should not be set. - if (!_.contains([Expensicons.CreditCard], bankIcon.icon)) { + if (![Expensicons.CreditCard].includes(bankIcon.icon)) { bankIcon.iconSize = variables.iconSizeExtraLarge; } diff --git a/src/libs/PaymentUtils.js b/src/libs/PaymentUtils.js deleted file mode 100644 index 67d7c74869fc..000000000000 --- a/src/libs/PaymentUtils.js +++ /dev/null @@ -1,95 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import BankAccount from './models/BankAccount'; -import getBankIcon from '../components/Icon/BankIcons'; -import CONST from '../CONST'; -import * as Localize from './Localize'; - -/** - * Check to see if user has either a debit card or personal bank account added - * - * @param {Array} [fundList] - * @param {Array} [bankAccountList] - * @returns {Boolean} - */ -function hasExpensifyPaymentMethod(fundList = [], bankAccountList = []) { - const validBankAccount = _.some(bankAccountList, (bankAccountJSON) => { - const bankAccount = new BankAccount(bankAccountJSON); - return bankAccount.isDefaultCredit(); - }); - - // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them - const validDebitCard = _.some(fundList, (card) => lodashGet(card, 'accountData.additionalData.isP2PDebitCard', false)); - - return validBankAccount || validDebitCard; -} - -/** - * @param {String} [accountType] - one of {'bankAccount', 'debitCard'} - * @param {Object} account - * @returns {String} - */ -function getPaymentMethodDescription(accountType, account) { - if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber.slice(-4)}`; - } - if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - return `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${account.cardNumber.slice(-4)}`; - } - return ''; -} - -/** - * Get the PaymentMethods list - * @param {Array} bankAccountList - * @param {Array} fundList - * @returns {Array} - */ -function formatPaymentMethods(bankAccountList, fundList) { - const combinedPaymentMethods = []; - - _.each(bankAccountList, (bankAccount) => { - // Add all bank accounts besides the wallet - if (bankAccount.type === CONST.BANK_ACCOUNT_TYPES.WALLET) { - return; - } - - const {icon, iconSize} = getBankIcon(lodashGet(bankAccount, 'accountData.additionalData.bankName', '')); - combinedPaymentMethods.push({ - ...bankAccount, - description: getPaymentMethodDescription(bankAccount.accountType, bankAccount.accountData), - icon, - iconSize, - errors: bankAccount.errors, - pendingAction: bankAccount.pendingAction, - }); - }); - - _.each(fundList, (card) => { - const {icon, iconSize} = getBankIcon(lodashGet(card, 'accountData.bank', ''), true); - combinedPaymentMethods.push({ - ...card, - description: getPaymentMethodDescription(card.accountType, card.accountData), - icon, - iconSize, - errors: card.errors, - pendingAction: card.pendingAction, - }); - }); - - return combinedPaymentMethods; -} - -/** - * @param {Number} currentBalance, in cents - * @param {String} methodType - * @returns {Number} the fee, in cents - */ -function calculateWalletTransferBalanceFee(currentBalance, methodType) { - const transferMethodTypeFeeStructure = - methodType === CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT ? CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT : CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.ACH; - const calculateFee = Math.ceil(currentBalance * (transferMethodTypeFeeStructure.RATE / 100)); - return Math.max(calculateFee, transferMethodTypeFeeStructure.MINIMUM_FEE); -} - -export {hasExpensifyPaymentMethod, getPaymentMethodDescription, formatPaymentMethods, calculateWalletTransferBalanceFee}; diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts new file mode 100644 index 000000000000..456b7c6494f5 --- /dev/null +++ b/src/libs/PaymentUtils.ts @@ -0,0 +1,88 @@ +import {SvgProps} from 'react-native-svg'; +import {FC} from 'react'; +// eslint-disable-next-line import/no-named-default +import {default as BankAccountModel} from './models/BankAccount'; +import getBankIcon from '../components/Icon/BankIcons'; +import CONST from '../CONST'; +import * as Localize from './Localize'; +import Fund from '../types/onyx/Fund'; +import BankAccount from '../types/onyx/BankAccount'; + +type AccountType = 'debitCard' | 'bankAccount'; + +type PaymentMethod = { + description: string; + icon: FC; + iconSize: number; +} & BankAccount & + Fund; + +/** + * Check to see if user has either a debit card or personal bank account added + */ +function hasExpensifyPaymentMethod(fundList: Record, bankAccountList: Record): boolean { + const validBankAccount = Object.values(bankAccountList).some((bankAccountJSON) => { + const bankAccount = new BankAccountModel(bankAccountJSON); + return bankAccount.isDefaultCredit(); + }); + + // Hide any billing cards that are not P2P debit cards for now because you cannot make them your default method, or delete them + const validDebitCard = Object.values(fundList).some((card) => card?.accountData?.additionalData?.isP2PDebitCard ?? false); + + return validBankAccount || validDebitCard; +} + +function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] & Fund['accountData']): string { + if (account) { + if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && account?.accountNumber) { + return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account?.accountNumber?.slice(-4)}`; + } + if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { + return `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${account?.cardNumber?.slice(-4)}`; + } + } + return ''; +} + +/** + * Get the PaymentMethods list + */ +function formatPaymentMethods(bankAccountList: Record, fundList: Record) { + const combinedPaymentMethods: PaymentMethod[] = []; + + Object.values(bankAccountList).forEach((bankAccount) => { + // Add all bank accounts besides the wallet + if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT_TYPES.WALLET) { + return; + } + + const {icon, iconSize} = getBankIcon(bankAccount?.accountData?.additionalData?.bankName ?? '', false); + combinedPaymentMethods.push({ + ...bankAccount, + description: getPaymentMethodDescription(bankAccount?.accountType as AccountType, bankAccount.accountData), + icon, + iconSize, + }); + }); + + Object.values(fundList).forEach((card) => { + const {icon, iconSize} = getBankIcon(card?.accountData?.bank ?? '', true); + combinedPaymentMethods.push({ + ...card, + description: getPaymentMethodDescription(card?.accountType as AccountType, card.accountData), + icon, + iconSize, + }); + }); + + return combinedPaymentMethods; +} + +function calculateWalletTransferBalanceFee(currentBalance: number, methodType: string): number { + const transferMethodTypeFeeStructure = + methodType === CONST.WALLET.TRANSFER_METHOD_TYPE.INSTANT ? CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.INSTANT : CONST.WALLET.TRANSFER_METHOD_TYPE_FEE.ACH; + const calculateFee = Math.ceil(currentBalance * (transferMethodTypeFeeStructure.RATE / 100)); + return Math.max(calculateFee, transferMethodTypeFeeStructure.MINIMUM_FEE); +} + +export {hasExpensifyPaymentMethod, getPaymentMethodDescription, formatPaymentMethods, calculateWalletTransferBalanceFee}; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 7c7c0445081a..70838f72d508 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -18,6 +18,7 @@ type AccountData = { created?: string; currency?: string; fundID?: number; + bank?: string; }; type Fund = { From 03cfe9c4318d3ceb6224099faf68aa095c5603cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Sep 2023 10:32:22 +0200 Subject: [PATCH 2/4] review changes --- src/components/Icon/BankIcons.ts | 5 ++--- src/libs/PaymentUtils.ts | 29 +++++++++++++---------------- src/types/onyx/BankAccount.ts | 4 +++- src/types/onyx/Fund.ts | 4 +++- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 602c45157ae2..9e60d1af873b 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -1,5 +1,4 @@ import {SvgProps} from 'react-native-svg'; -import {FC} from 'react'; import * as Expensicons from './Expensicons'; import AmericanExpress from '../../../assets/images/bankicons/american-express.svg'; import BankOfAmerica from '../../../assets/images/bankicons/bank-of-america.svg'; @@ -23,7 +22,7 @@ import USAA from '../../../assets/images/bankicons/usaa.svg'; import variables from '../../styles/variables'; type BankIcon = { - icon: FC; + icon: React.FC; iconSize: number; }; @@ -31,7 +30,7 @@ type BankIcon = { * Returns matching asset icon for bankName */ -function getAssetIcon(bankName: string, isCard: boolean): FC { +function getAssetIcon(bankName: string, isCard: boolean): React.FC { if (bankName.includes('americanexpress')) { return AmericanExpress; } diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts index 456b7c6494f5..8bf50e30c38f 100644 --- a/src/libs/PaymentUtils.ts +++ b/src/libs/PaymentUtils.ts @@ -1,21 +1,18 @@ import {SvgProps} from 'react-native-svg'; -import {FC} from 'react'; -// eslint-disable-next-line import/no-named-default -import {default as BankAccountModel} from './models/BankAccount'; +import BankAccountModel from './models/BankAccount'; import getBankIcon from '../components/Icon/BankIcons'; import CONST from '../CONST'; import * as Localize from './Localize'; import Fund from '../types/onyx/Fund'; import BankAccount from '../types/onyx/BankAccount'; -type AccountType = 'debitCard' | 'bankAccount'; +type AccountType = BankAccount['accountType'] | Fund['accountType']; -type PaymentMethod = { +type PaymentMethod = (BankAccount | Fund) & { description: string; - icon: FC; + icon: React.FC; iconSize: number; -} & BankAccount & - Fund; +}; /** * Check to see if user has either a debit card or personal bank account added @@ -32,13 +29,13 @@ function hasExpensifyPaymentMethod(fundList: Record, bankAccountLi return validBankAccount || validDebitCard; } -function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] & Fund['accountData']): string { +function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData']): string { if (account) { - if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && account?.accountNumber) { - return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account?.accountNumber?.slice(-4)}`; + if (accountType === CONST.PAYMENT_METHODS.BANK_ACCOUNT && 'accountNumber' in account) { + return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`; } - if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - return `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${account?.cardNumber?.slice(-4)}`; + if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD && 'cardNumber' in account) { + return `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${account.cardNumber?.slice(-4)}`; } } return ''; @@ -47,7 +44,7 @@ function getPaymentMethodDescription(accountType: AccountType, account: BankAcco /** * Get the PaymentMethods list */ -function formatPaymentMethods(bankAccountList: Record, fundList: Record) { +function formatPaymentMethods(bankAccountList: Record, fundList: Record): PaymentMethod[] { const combinedPaymentMethods: PaymentMethod[] = []; Object.values(bankAccountList).forEach((bankAccount) => { @@ -59,7 +56,7 @@ function formatPaymentMethods(bankAccountList: Record, fund const {icon, iconSize} = getBankIcon(bankAccount?.accountData?.additionalData?.bankName ?? '', false); combinedPaymentMethods.push({ ...bankAccount, - description: getPaymentMethodDescription(bankAccount?.accountType as AccountType, bankAccount.accountData), + description: getPaymentMethodDescription(bankAccount?.accountType, bankAccount.accountData), icon, iconSize, }); @@ -69,7 +66,7 @@ function formatPaymentMethods(bankAccountList: Record, fund const {icon, iconSize} = getBankIcon(card?.accountData?.bank ?? '', true); combinedPaymentMethods.push({ ...card, - description: getPaymentMethodDescription(card?.accountType as AccountType, card.accountData), + description: getPaymentMethodDescription(card?.accountType, card.accountData), icon, iconSize, }); diff --git a/src/types/onyx/BankAccount.ts b/src/types/onyx/BankAccount.ts index ccaaa7ebab78..5d309023e94a 100644 --- a/src/types/onyx/BankAccount.ts +++ b/src/types/onyx/BankAccount.ts @@ -1,3 +1,5 @@ +import CONST from '../../CONST'; + type AdditionalData = { isP2PDebitCard?: boolean; beneficialOwners?: string[]; @@ -49,7 +51,7 @@ type AccountData = { type BankAccount = { /** The bank account type */ - accountType?: string; + accountType?: typeof CONST.PAYMENT_METHODS.BANK_ACCOUNT; /** string like 'Account ending in XXXX' */ description?: string; diff --git a/src/types/onyx/Fund.ts b/src/types/onyx/Fund.ts index 70838f72d508..2da0edf78045 100644 --- a/src/types/onyx/Fund.ts +++ b/src/types/onyx/Fund.ts @@ -1,3 +1,5 @@ +import CONST from '../../CONST'; + type AdditionalData = { isBillingCard?: boolean; isP2PDebitCard?: boolean; @@ -23,7 +25,7 @@ type AccountData = { type Fund = { accountData?: AccountData; - accountType?: string; + accountType?: typeof CONST.PAYMENT_METHODS.DEBIT_CARD; description?: string; key?: string; methodID?: number; From 06376b2e097dec444563f2e228eaa43bbc0c63bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 22 Sep 2023 12:32:00 +0200 Subject: [PATCH 3/4] fix debit card icons --- src/components/Icon/BankIcons.ts | 3 +-- src/libs/PaymentUtils.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 9e60d1af873b..44f2fd67d06d 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -23,7 +23,7 @@ import variables from '../../styles/variables'; type BankIcon = { icon: React.FC; - iconSize: number; + iconSize?: number; }; /** @@ -113,7 +113,6 @@ function getAssetIcon(bankName: string, isCard: boolean): React.FC { export default function getBankIcon(bankName: string, isCard = false) { const bankIcon: BankIcon = { icon: isCard ? Expensicons.CreditCard : GenericBank, - iconSize: 0, }; if (bankName) { diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts index 8bf50e30c38f..64260569639e 100644 --- a/src/libs/PaymentUtils.ts +++ b/src/libs/PaymentUtils.ts @@ -11,7 +11,7 @@ type AccountType = BankAccount['accountType'] | Fund['accountType']; type PaymentMethod = (BankAccount | Fund) & { description: string; icon: React.FC; - iconSize: number; + iconSize?: number; }; /** From 7ca844f519339975a2d9ccb136b59870721ffefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Mon, 2 Oct 2023 17:08:55 +0200 Subject: [PATCH 4/4] review changes --- src/components/Icon/BankIcons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 44f2fd67d06d..3118eec56a6d 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -110,7 +110,7 @@ function getAssetIcon(bankName: string, isCard: boolean): React.FC { * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon(bankName: string, isCard = false) { +export default function getBankIcon(bankName: string, isCard = false): BankIcon { const bankIcon: BankIcon = { icon: isCard ? Expensicons.CreditCard : GenericBank, };