From b5297c04732945bfe7e14cc61278199e7a39e169 Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Sat, 18 Jul 2026 14:51:48 -0700 Subject: [PATCH 1/5] Key supported ECUK countries by currency, populate from onyx, alphabetize correctly --- src/CONST/index.ts | 6 ++- src/ONYXKEYS.ts | 4 ++ .../SubStepForms/CountryFullStep.tsx | 7 +-- src/libs/CardUtils.ts | 6 ++- src/libs/ExportOnyxState/common.ts | 1 + .../utils/getAvailableEuCountries.ts | 15 ++++-- .../DynamicWorkspaceSettlementAccountPage.tsx | 3 +- .../WorkspaceExpensifyCardBankAccounts.tsx | 3 +- .../WorkspaceExpensifyCardPageEmptyState.tsx | 5 +- tests/unit/CardUtilsTest.ts | 46 ++++++++++++++++++- tests/unit/getAvailableEuCountriesTest.ts | 20 ++++++++ 11 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 tests/unit/getAvailableEuCountriesTest.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 8b6c6c0e4b65..fdee75a0bdb0 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -5707,7 +5707,11 @@ const CONST = { SE: 'Sweden', }, - EXPENSIFY_UK_EU_SUPPORTED_COUNTRIES: ['BE', 'DK', 'ES', 'FI', 'IE', 'LT', 'LU', 'LV', 'NL', 'PL', 'SE', 'GB', 'GI'], + // Temporary hard-coded fallback for the Expensify Card supported countries keyed by settlement currency + EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY: { + GBP: ['GB', 'GI'], + EUR: ['BE', 'DK', 'ES', 'FI', 'IE', 'LT', 'LU', 'LV', 'NL', 'PL', 'SE'], + }, EU_REGISTRATION_NUMBER_REGEX: { AT: /^FN\d{6}[a-z]?$/i, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 532d700759fd..f81c53dc6fed 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -388,6 +388,9 @@ const ONYXKEYS = { /** The user's payment and P2P cards */ FUND_LIST: 'fundList', + /** Authoritative country codes where the Expensify Card is supported, keyed by settlement currency, sent by the backend */ + CARD_SUPPORTED_COUNTRIES: 'cardSupportedCountries', + /** The user's cash card and imported cards (including the Expensify Card) */ CARD_LIST: 'cardList', @@ -1580,6 +1583,7 @@ type OnyxValuesMapping = { [ONYXKEYS.WALLET_TERMS]: OnyxTypes.WalletTerms; [ONYXKEYS.BANK_ACCOUNT_LIST]: OnyxTypes.BankAccountList; [ONYXKEYS.FUND_LIST]: OnyxTypes.FundList; + [ONYXKEYS.CARD_SUPPORTED_COUNTRIES]: Record; [ONYXKEYS.CARD_LIST]: OnyxTypes.CardList; [ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement; [ONYXKEYS.TRAVEL_INVOICE_STATEMENT]: OnyxTypes.TravelInvoiceStatement; diff --git a/src/components/SubStepForms/CountryFullStep.tsx b/src/components/SubStepForms/CountryFullStep.tsx index 30388d80f817..bcc144197b5f 100644 --- a/src/components/SubStepForms/CountryFullStep.tsx +++ b/src/components/SubStepForms/CountryFullStep.tsx @@ -52,13 +52,14 @@ type CountryFullStepProps = { }; function CountryFullStep({onBackButtonPress, stepNames, onSubmit, policyID, isComingFromExpensifyCard}: CountryFullStepProps) { - const {translate} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [supportedCountriesByCurrency] = useOnyx(ONYXKEYS.CARD_SUPPORTED_COUNTRIES); const [showNoPolicyError, setShowNoPolicyError] = useState(false); const currency = @@ -67,12 +68,12 @@ function CountryFullStep({onBackButtonPress, stepNames, onSubmit, policyID, isCo reimbursementAccount?.achData?.currency ?? CONST.BBA_COUNTRY_CURRENCY_MAP[reimbursementAccount?.achData?.country ?? '']; - const shouldAllowChange = currency === CONST.CURRENCY.EUR && !reimbursementAccount?.achData?.accountNumber; + const shouldAllowChange = (currency === CONST.CURRENCY.EUR || currency === CONST.CURRENCY.GBP) && !reimbursementAccount?.achData?.accountNumber; const defaultCountries = shouldAllowChange ? CONST.ALL_EUROPEAN_UNION_COUNTRIES : CONST.ALL_COUNTRIES; const countryDefaultValue = reimbursementAccountDraft?.[COUNTRY] ?? reimbursementAccount?.achData?.[COUNTRY] ?? ''; const currencyMappedToCountry = mapCurrencyToCountry(currency) || countryDefaultValue; const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID) && isComingFromExpensifyCard; - const countriesSupportedForExpensifyCard = getAvailableEuCountries(); + const countriesSupportedForExpensifyCard = getAvailableEuCountries(supportedCountriesByCurrency, currency, localeCompare); const [userSelectedCountry, setUserSelectedCountry] = useState(''); const selectedCountry = shouldAllowChange ? userSelectedCountry || countryDefaultValue : currencyMappedToCountry; diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 4130d4efb9e7..ed5dc5668cc2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -533,17 +533,19 @@ function getConnectionBankAccountsForReconciliation(connections: OnyxEntry, outputCurrency?: string) { +function getEligibleBankAccountsForUkEuCard(bankAccountsList: OnyxEntry, supportedCountriesByCurrency: OnyxEntry>, outputCurrency?: string) { if (!bankAccountsList || isEmptyObject(bankAccountsList)) { return []; } + const supportedCountriesForCurrency: Record = supportedCountriesByCurrency ?? CONST.EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY; + const supportedCountries = supportedCountriesForCurrency[outputCurrency ?? ''] ?? []; return Object.values(bankAccountsList).filter( (bankAccount) => bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.accountData?.allowDebit && !isBankAccountPartiallySetup(bankAccount?.accountData?.state) && bankAccount?.bankCurrency === outputCurrency && - (CONST.EXPENSIFY_UK_EU_SUPPORTED_COUNTRIES as unknown as string).includes(bankAccount?.bankCountry), + supportedCountries.includes(bankAccount?.bankCountry), ); } diff --git a/src/libs/ExportOnyxState/common.ts b/src/libs/ExportOnyxState/common.ts index 5e63da804d0b..be80fb1db935 100644 --- a/src/libs/ExportOnyxState/common.ts +++ b/src/libs/ExportOnyxState/common.ts @@ -134,6 +134,7 @@ const safeOnyxKeys = new Set([ ONYXKEYS.BETAS, ONYXKEYS.BETA_CONFIGURATION, ONYXKEYS.CACHED_PDF_PATHS, + ONYXKEYS.CARD_SUPPORTED_COUNTRIES, ONYXKEYS.COLLECTION.CONCIERGE_PENDING_FOLLOWUP_LIST, ONYXKEYS.COLLECTION.DEVICE_BIOMETRICS, ONYXKEYS.COLLECTION.DOMAIN_HIGHLIGHT_ITEMS, diff --git a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts b/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts index b59938b49a8a..f632d69c2fdc 100644 --- a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts +++ b/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts @@ -1,8 +1,17 @@ +import type {LocaleContextProps} from '@components/LocaleContextProvider'; + import CONST from '@src/CONST'; -const ukEuSupportedCountrySet = new Set(CONST.EXPENSIFY_UK_EU_SUPPORTED_COUNTRIES); +import type {OnyxEntry} from 'react-native-onyx'; + const europeanCountries = Object.entries(CONST.EUROPEAN_UNION_COUNTRIES_WITH_GB); -export default function getAvailableEuCountries(): Record { - return Object.fromEntries(europeanCountries.filter(([code]) => ukEuSupportedCountrySet.has(code))); +export default function getAvailableEuCountries( + supportedCountriesByCurrency: OnyxEntry>, + currency: string | undefined, + localeCompare: LocaleContextProps['localeCompare'], +): Record { + const supportedCountriesForCurrency: Record = supportedCountriesByCurrency ?? CONST.EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY; + const supportedCountrySet = new Set(supportedCountriesForCurrency[currency ?? ''] ?? []); + return Object.fromEntries(europeanCountries.filter(([code]) => supportedCountrySet.has(code)).sort(([, nameA], [, nameB]) => localeCompare(nameA, nameB))); } diff --git a/src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx b/src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx index 8e411f8fe2f5..5bca13f57d82 100644 --- a/src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx +++ b/src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx @@ -55,6 +55,7 @@ function DynamicWorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccou const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const [bankAccountsList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [supportedCountriesByCurrency] = useOnyx(ONYXKEYS.CARD_SUPPORTED_COUNTRIES); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${defaultFundID}`); const programKey = getCardProgramKey(cardSettings); const settings = getCardSettings(cardSettings, programKey); @@ -71,7 +72,7 @@ function DynamicWorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccou const getEligibleBankAccounts = () => { if (isUkEuCurrencySupported) { - return getEligibleBankAccountsForUkEuCard(bankAccountsList, policy?.outputCurrency); + return getEligibleBankAccountsForUkEuCard(bankAccountsList, supportedCountriesByCurrency, policy?.outputCurrency); } return getEligibleBankAccountsForCard(bankAccountsList); }; diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx index 6fff669f76b8..6e0aea653066 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx @@ -45,6 +45,7 @@ function WorkspaceExpensifyCardBankAccounts({route}: WorkspaceExpensifyCardBankA const {translate} = useLocalize(); const styles = useThemeStyles(); const [bankAccountsList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [supportedCountriesByCurrency] = useOnyx(ONYXKEYS.CARD_SUPPORTED_COUNTRIES); const {login: currentUserLogin = ''} = useCurrentUserPersonalDetails(); const policyID = route?.params?.policyID; @@ -92,7 +93,7 @@ function WorkspaceExpensifyCardBankAccounts({route}: WorkspaceExpensifyCardBankA } const eligibleBankAccounts = isUkEuCurrencySupported - ? getEligibleBankAccountsForUkEuCard(bankAccountsList, policy?.outputCurrency) + ? getEligibleBankAccountsForUkEuCard(bankAccountsList, supportedCountriesByCurrency, policy?.outputCurrency) : getEligibleBankAccountsForCard(bankAccountsList); return eligibleBankAccounts.map((bankAccount) => { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx index 34a325046cb8..9580bb5c2385 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx @@ -55,6 +55,7 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif const theme = useTheme(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [supportedCountriesByCurrency] = useOnyx(ONYXKEYS.CARD_SUPPORTED_COUNTRIES); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const {showConfirmModal, closeModal} = useConfirmModal(); const {windowHeight} = useWindowDimensions(); @@ -80,7 +81,9 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif const {allFeeds} = useExpensifyCardFeedsForFeedSelector(policy?.id); const hasAccessibleFeeds = allFeeds.length > 0; - const eligibleBankAccounts = isUkEuCurrencySupported ? getEligibleBankAccountsForUkEuCard(bankAccountList, policy?.outputCurrency) : getEligibleBankAccountsForCard(bankAccountList); + const eligibleBankAccounts = isUkEuCurrencySupported + ? getEligibleBankAccountsForUkEuCard(bankAccountList, supportedCountriesByCurrency, policy?.outputCurrency) + : getEligibleBankAccountsForCard(bankAccountList); const shouldStartBankAccountSetup = !eligibleBankAccounts.length || isSetupUnfinished; const canStartBankAccountSetup = canEditWorkspaceSettings(policy, currentUserLogin); const shouldDisableCTA = !canWriteExpensifyCard || (!hasAccessibleFeeds && shouldStartBankAccountSetup && !canStartBankAccountSetup); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 99443c289441..3369130a1c2f 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -4416,10 +4416,54 @@ describe('getEligibleBankAccountsForUkEuCard', () => { bankCountry: 'GB', }, }; - const result = getEligibleBankAccountsForUkEuCard(bankAccounts, 'GBP'); + const result = getEligibleBankAccountsForUkEuCard(bankAccounts, {GBP: ['GB', 'GI']}, 'GBP'); expect(result).toHaveLength(1); expect(result.at(0)?.accountData?.state).toBe(CONST.BANK_ACCOUNT.STATE.OPEN); }); + + it('uses only the supported countries for the workspace settlement currency', () => { + const bankAccounts: BankAccountList = { + '1': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'EUR', + bankCountry: 'BE', + }, + '2': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'EUR', + bankCountry: 'GB', + }, + }; + const supportedCountriesByCurrency = {GBP: ['GB', 'GI'], EUR: ['BE', 'DK']}; + const result = getEligibleBankAccountsForUkEuCard(bankAccounts, supportedCountriesByCurrency, 'EUR'); + expect(result).toHaveLength(1); + expect(result.at(0)?.bankCountry).toBe('BE'); + }); + + it('returns no accounts when the settlement currency has no supported-country entry', () => { + const bankAccounts: BankAccountList = { + '1': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'GBP', + bankCountry: 'GB', + }, + }; + const result = getEligibleBankAccountsForUkEuCard(bankAccounts, {EUR: ['BE', 'DK']}, 'GBP'); + expect(result).toHaveLength(0); + }); + + it('falls back to the hard-coded supported countries when the backend list is unavailable', () => { + const bankAccounts: BankAccountList = { + '1': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'GBP', + bankCountry: 'GB', + }, + }; + const result = getEligibleBankAccountsForUkEuCard(bankAccounts, undefined, 'GBP'); + expect(result).toHaveLength(1); + expect(result.at(0)?.bankCountry).toBe('GB'); + }); }); describe('getConnectionBankAccountsForReconciliation', () => { diff --git a/tests/unit/getAvailableEuCountriesTest.ts b/tests/unit/getAvailableEuCountriesTest.ts new file mode 100644 index 000000000000..d145b8958bad --- /dev/null +++ b/tests/unit/getAvailableEuCountriesTest.ts @@ -0,0 +1,20 @@ +import getAvailableEuCountries from '@pages/ReimbursementAccount/utils/getAvailableEuCountries'; + +const localeCompare = (a: string, b: string) => a.localeCompare(b); + +describe('getAvailableEuCountries', () => { + it('sorts supported countries by localized name, not by country code', () => { + // LT/LU/LV are alphabetical by code but Latvia/Lithuania/Luxembourg by name + const result = getAvailableEuCountries({EUR: ['LV', 'LT', 'LU', 'BE']}, 'EUR', localeCompare); + expect(Object.values(result)).toEqual(['Belgium', 'Latvia', 'Lithuania', 'Luxembourg']); + }); + + it('falls back to the hard-coded supported countries (sorted by name) when the backend list is unavailable', () => { + const result = getAvailableEuCountries(undefined, 'EUR', localeCompare); + expect(Object.values(result)).toEqual(['Belgium', 'Denmark', 'Finland', 'Ireland', 'Latvia', 'Lithuania', 'Luxembourg', 'Netherlands', 'Poland', 'Spain', 'Sweden']); + }); + + it('returns no countries for an unsupported settlement currency', () => { + expect(getAvailableEuCountries({EUR: ['BE']}, 'USD', localeCompare)).toEqual({}); + }); +}); From 727fcceef98c4b65efaaad97149c07fc4bfc5828 Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Sat, 18 Jul 2026 15:48:02 -0700 Subject: [PATCH 2/5] Only unlock VBA country picker for GBP as part of ECUK onboarding flow (i.e. remain locked to GB in reimbursement onboarding) --- src/components/SubStepForms/CountryFullStep.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/SubStepForms/CountryFullStep.tsx b/src/components/SubStepForms/CountryFullStep.tsx index bcc144197b5f..ef05a8ee0f92 100644 --- a/src/components/SubStepForms/CountryFullStep.tsx +++ b/src/components/SubStepForms/CountryFullStep.tsx @@ -68,11 +68,12 @@ function CountryFullStep({onBackButtonPress, stepNames, onSubmit, policyID, isCo reimbursementAccount?.achData?.currency ?? CONST.BBA_COUNTRY_CURRENCY_MAP[reimbursementAccount?.achData?.country ?? '']; - const shouldAllowChange = (currency === CONST.CURRENCY.EUR || currency === CONST.CURRENCY.GBP) && !reimbursementAccount?.achData?.accountNumber; + const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID) && isComingFromExpensifyCard; + // GBP maps 1:1 to GB outside the Expensify Card flow, so only unlock the country picker for GBP during card onboarding, where GI is also valid + const shouldAllowChange = (currency === CONST.CURRENCY.EUR || (currency === CONST.CURRENCY.GBP && isUkEuCurrencySupported)) && !reimbursementAccount?.achData?.accountNumber; const defaultCountries = shouldAllowChange ? CONST.ALL_EUROPEAN_UNION_COUNTRIES : CONST.ALL_COUNTRIES; const countryDefaultValue = reimbursementAccountDraft?.[COUNTRY] ?? reimbursementAccount?.achData?.[COUNTRY] ?? ''; const currencyMappedToCountry = mapCurrencyToCountry(currency) || countryDefaultValue; - const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID) && isComingFromExpensifyCard; const countriesSupportedForExpensifyCard = getAvailableEuCountries(supportedCountriesByCurrency, currency, localeCompare); const [userSelectedCountry, setUserSelectedCountry] = useState(''); From 328d8c35b0a2411de576d7509906157a40015382 Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Sat, 18 Jul 2026 15:54:00 -0700 Subject: [PATCH 3/5] Extract helper getSupportedCardCountriesForCurrency --- src/libs/CardUtils.ts | 13 +++++++++++-- .../utils/getAvailableEuCountries.ts | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index ed5dc5668cc2..a1cce79deae2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -533,12 +533,20 @@ function getConnectionBankAccountsForReconciliation(connections: OnyxEntry>, currency?: string): readonly string[] { + const byCurrency: Record = supportedCountriesByCurrency ?? CONST.EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY; + return byCurrency[currency ?? ''] ?? []; +} + function getEligibleBankAccountsForUkEuCard(bankAccountsList: OnyxEntry, supportedCountriesByCurrency: OnyxEntry>, outputCurrency?: string) { if (!bankAccountsList || isEmptyObject(bankAccountsList)) { return []; } - const supportedCountriesForCurrency: Record = supportedCountriesByCurrency ?? CONST.EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY; - const supportedCountries = supportedCountriesForCurrency[outputCurrency ?? ''] ?? []; + const supportedCountries = getSupportedCardCountriesForCurrency(supportedCountriesByCurrency, outputCurrency); return Object.values(bankAccountsList).filter( (bankAccount) => bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && @@ -2102,6 +2110,7 @@ export { getCardFeedWithDomainID, splitCardFeedWithDomainID, getEligibleBankAccountsForUkEuCard, + getSupportedCardCountriesForCurrency, getConnectionBankAccountsForReconciliation, isPersonalCard, COMPANY_CARD_FEED_ICON_NAMES, diff --git a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts b/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts index f632d69c2fdc..3bb897d0932c 100644 --- a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts +++ b/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts @@ -1,5 +1,7 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import {getSupportedCardCountriesForCurrency} from '@libs/CardUtils'; + import CONST from '@src/CONST'; import type {OnyxEntry} from 'react-native-onyx'; @@ -11,7 +13,6 @@ export default function getAvailableEuCountries( currency: string | undefined, localeCompare: LocaleContextProps['localeCompare'], ): Record { - const supportedCountriesForCurrency: Record = supportedCountriesByCurrency ?? CONST.EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY; - const supportedCountrySet = new Set(supportedCountriesForCurrency[currency ?? ''] ?? []); + const supportedCountrySet = new Set(getSupportedCardCountriesForCurrency(supportedCountriesByCurrency, currency)); return Object.fromEntries(europeanCountries.filter(([code]) => supportedCountrySet.has(code)).sort(([, nameA], [, nameB]) => localeCompare(nameA, nameB))); } From 4682702bee03bfef740f3e2dcb86f43c74ac0a89 Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Sat, 18 Jul 2026 17:51:46 -0700 Subject: [PATCH 4/5] Update comment --- src/CONST/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index fdee75a0bdb0..06fabc97e7cf 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -5707,7 +5707,7 @@ const CONST = { SE: 'Sweden', }, - // Temporary hard-coded fallback for the Expensify Card supported countries keyed by settlement currency + // Hard-coded fallback for the Expensify Card supported countries keyed by settlement currency EXPENSIFY_CARD_SUPPORTED_COUNTRIES_BY_CURRENCY: { GBP: ['GB', 'GI'], EUR: ['BE', 'DK', 'ES', 'FI', 'IE', 'LT', 'LU', 'LV', 'NL', 'PL', 'SE'], From 157f44519d3504a6e35549b2dc46e709ca1a48ed Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Sun, 19 Jul 2026 13:58:11 -0700 Subject: [PATCH 5/5] Rename getAvailableEUCountries to getAvailableCardCountryOptions --- src/components/SubStepForms/CountryFullStep.tsx | 4 ++-- ...leEuCountries.ts => getAvailableCardCountryOptions.ts} | 2 +- ...triesTest.ts => getAvailableCardCountryOptionsTest.ts} | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/pages/ReimbursementAccount/utils/{getAvailableEuCountries.ts => getAvailableCardCountryOptions.ts} (93%) rename tests/unit/{getAvailableEuCountriesTest.ts => getAvailableCardCountryOptionsTest.ts} (65%) diff --git a/src/components/SubStepForms/CountryFullStep.tsx b/src/components/SubStepForms/CountryFullStep.tsx index ef05a8ee0f92..3d53fbc51f24 100644 --- a/src/components/SubStepForms/CountryFullStep.tsx +++ b/src/components/SubStepForms/CountryFullStep.tsx @@ -19,7 +19,7 @@ import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import Navigation from '@navigation/Navigation'; -import getAvailableEuCountries from '@pages/ReimbursementAccount/utils/getAvailableEuCountries'; +import getAvailableCardCountryOptions from '@pages/ReimbursementAccount/utils/getAvailableCardCountryOptions'; import {clearErrors, setDraftValues} from '@userActions/FormActions'; import {setIsComingFromGlobalReimbursementsFlow} from '@userActions/Policy/Policy'; @@ -74,7 +74,7 @@ function CountryFullStep({onBackButtonPress, stepNames, onSubmit, policyID, isCo const defaultCountries = shouldAllowChange ? CONST.ALL_EUROPEAN_UNION_COUNTRIES : CONST.ALL_COUNTRIES; const countryDefaultValue = reimbursementAccountDraft?.[COUNTRY] ?? reimbursementAccount?.achData?.[COUNTRY] ?? ''; const currencyMappedToCountry = mapCurrencyToCountry(currency) || countryDefaultValue; - const countriesSupportedForExpensifyCard = getAvailableEuCountries(supportedCountriesByCurrency, currency, localeCompare); + const countriesSupportedForExpensifyCard = getAvailableCardCountryOptions(supportedCountriesByCurrency, currency, localeCompare); const [userSelectedCountry, setUserSelectedCountry] = useState(''); const selectedCountry = shouldAllowChange ? userSelectedCountry || countryDefaultValue : currencyMappedToCountry; diff --git a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts b/src/pages/ReimbursementAccount/utils/getAvailableCardCountryOptions.ts similarity index 93% rename from src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts rename to src/pages/ReimbursementAccount/utils/getAvailableCardCountryOptions.ts index 3bb897d0932c..c7da5c8bd7b8 100644 --- a/src/pages/ReimbursementAccount/utils/getAvailableEuCountries.ts +++ b/src/pages/ReimbursementAccount/utils/getAvailableCardCountryOptions.ts @@ -8,7 +8,7 @@ import type {OnyxEntry} from 'react-native-onyx'; const europeanCountries = Object.entries(CONST.EUROPEAN_UNION_COUNTRIES_WITH_GB); -export default function getAvailableEuCountries( +export default function getAvailableCardCountryOptions( supportedCountriesByCurrency: OnyxEntry>, currency: string | undefined, localeCompare: LocaleContextProps['localeCompare'], diff --git a/tests/unit/getAvailableEuCountriesTest.ts b/tests/unit/getAvailableCardCountryOptionsTest.ts similarity index 65% rename from tests/unit/getAvailableEuCountriesTest.ts rename to tests/unit/getAvailableCardCountryOptionsTest.ts index d145b8958bad..207231203e4e 100644 --- a/tests/unit/getAvailableEuCountriesTest.ts +++ b/tests/unit/getAvailableCardCountryOptionsTest.ts @@ -1,20 +1,20 @@ -import getAvailableEuCountries from '@pages/ReimbursementAccount/utils/getAvailableEuCountries'; +import getAvailableCardCountryOptions from '@pages/ReimbursementAccount/utils/getAvailableCardCountryOptions'; const localeCompare = (a: string, b: string) => a.localeCompare(b); describe('getAvailableEuCountries', () => { it('sorts supported countries by localized name, not by country code', () => { // LT/LU/LV are alphabetical by code but Latvia/Lithuania/Luxembourg by name - const result = getAvailableEuCountries({EUR: ['LV', 'LT', 'LU', 'BE']}, 'EUR', localeCompare); + const result = getAvailableCardCountryOptions({EUR: ['LV', 'LT', 'LU', 'BE']}, 'EUR', localeCompare); expect(Object.values(result)).toEqual(['Belgium', 'Latvia', 'Lithuania', 'Luxembourg']); }); it('falls back to the hard-coded supported countries (sorted by name) when the backend list is unavailable', () => { - const result = getAvailableEuCountries(undefined, 'EUR', localeCompare); + const result = getAvailableCardCountryOptions(undefined, 'EUR', localeCompare); expect(Object.values(result)).toEqual(['Belgium', 'Denmark', 'Finland', 'Ireland', 'Latvia', 'Lithuania', 'Luxembourg', 'Netherlands', 'Poland', 'Spain', 'Sweden']); }); it('returns no countries for an unsupported settlement currency', () => { - expect(getAvailableEuCountries({EUR: ['BE']}, 'USD', localeCompare)).toEqual({}); + expect(getAvailableCardCountryOptions({EUR: ['BE']}, 'USD', localeCompare)).toEqual({}); }); });