From 0cbe359aa40ca292e1a8b8dc6f10f2ec934fbe99 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 10:43:33 +0200 Subject: [PATCH 1/9] feat: add Wise global reimbursements intro page --- .../earth-with-coins.svg | 1 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../Icon/chunks/illustrations.chunk.ts | 2 + src/languages/de.ts | 1 + src/languages/el.ts | 1 + src/languages/en.ts | 5 ++ src/languages/es.ts | 1 + src/languages/fr.ts | 5 ++ src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + src/libs/BankAccountUtils.ts | 9 ++ .../ModalStackNavigators/index.tsx | 1 + .../RELATIONS/SETTINGS_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 4 + src/libs/Navigation/types.ts | 3 + .../WiseGlobalReimbursementsIntroPage.tsx | 86 +++++++++++++++++++ src/styles/variables.ts | 1 + tests/unit/BankAccountUtilsTest.ts | 54 ++++++++++++ 23 files changed, 186 insertions(+) create mode 100644 assets/images/product-illustrations/earth-with-coins.svg create mode 100644 src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx diff --git a/assets/images/product-illustrations/earth-with-coins.svg b/assets/images/product-illustrations/earth-with-coins.svg new file mode 100644 index 000000000000..eb9c97faaf63 --- /dev/null +++ b/assets/images/product-illustrations/earth-with-coins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b726c267f0e9..5782eb53197f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2657,6 +2657,10 @@ const ROUTES = { route: 'settings/wallet/:bankAccountID/share-bank-account', getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/share-bank-account` as const, }, + SETTINGS_WALLET_WISE_GLOBAL_REIMBURSEMENTS_INTRO: { + route: 'settings/wallet/:bankAccountID/global-reimbursements', + getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/global-reimbursements` as const, + }, SETTINGS_WALLET_EDIT_BANK_ACCOUNT_NICKNAME: { route: 'settings/wallet/:bankAccountID/edit-nickname', getRoute: (bankAccountID: number | undefined) => `settings/wallet/${bankAccountID}/edit-nickname` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 1d1217bbf699..7a6c575b61df 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -208,6 +208,7 @@ const SCREENS = { DYNAMIC_ENABLE_GLOBAL_REIMBURSEMENTS_SIGN: 'Dynamic_Settings_Wallet_Enable_Global_Reimbursements_Sign', SHARE_BANK_ACCOUNT: 'Settings_Wallet_Share_Bank_Account', EDIT_BANK_ACCOUNT_NICKNAME: 'Settings_Wallet_Edit_Bank_Account_Nickname', + WISE_GLOBAL_REIMBURSEMENTS_INTRO: 'Settings_Wallet_Wise_Global_Reimbursements_Intro', TRAVEL_CVV: 'Settings_Wallet_Travel_CVV', TRAVEL_CVV_VERIFY_ACCOUNT: 'Settings_Wallet_Travel_CVV_VerifyAccount', PERSONAL_CARD_DETAILS: 'Settings_Wallet_Personal_Card_Details', diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 2bafe8d3dac4..8ccb44d73fe1 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -62,6 +62,7 @@ import BigVault from '@assets/images/product-illustrations/big-vault.svg'; import BrokenCompanyCardBankConnection from '@assets/images/product-illustrations/broken-humpty-dumpty.svg'; import BrokenMagnifyingGlass from '@assets/images/product-illustrations/broken-magnifying-glass.svg'; import DjBoothReferenceHands from '@assets/images/product-illustrations/dj-booth-reference-hands.svg'; +import EarthWithCoins from '@assets/images/product-illustrations/earth-with-coins.svg'; import EarthWithControls from '@assets/images/product-illustrations/earth-with-controls.svg'; import EmptyStateDomainExists from '@assets/images/product-illustrations/empty-state-domain-exists.svg'; import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg'; @@ -300,6 +301,7 @@ const Illustrations = { HoldExpense, ReceiptFairy, EmptyStateTravel, + EarthWithCoins, EarthWithControls, EmptyStateDomainExists, Fireworks, diff --git a/src/languages/de.ts b/src/languages/de.ts index 5dd7a081fe84..c6b2a1b5988f 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -2828,6 +2828,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Dieses Konto erfordert Ihre Aufmerksamkeit', unlock: 'Entsperren', }, + wiseGlobalReimbursementsIntro: {title: 'Globale Rückerstattungen einrichten?', subtitle: 'Zahlen Sie Mitglieder weltweit in ihrer lokalen Währung.', getStarted: "Los geht's"}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Karte zu ${walletName} hinzufügen`, diff --git a/src/languages/el.ts b/src/languages/el.ts index 3a0a2f2c3a30..e774a8dfd669 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -2884,6 +2884,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Αυτός ο λογαριασμός χρειάζεται προσοχή', unlock: 'Ξεκλείδωμα', }, + wiseGlobalReimbursementsIntro: {title: 'Να ρυθμιστούν οι παγκόσμιες αποζημιώσεις;', subtitle: 'Πληρώστε μέλη παγκοσμίως στο τοπικό τους νόμισμα.', getStarted: 'Ξεκινήστε'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Προσθήκη κάρτας στο ${walletName}`, diff --git a/src/languages/en.ts b/src/languages/en.ts index bb6975f9f669..25c5adc40acb 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2900,6 +2900,11 @@ const translations = { editNickname: 'Edit nickname', nickname: 'Nickname', editNicknameInstruction: 'Give the bank account a nickname that sets it apart from others.', + wiseGlobalReimbursementsIntro: { + title: 'Set up global reimbursements?', + subtitle: 'Pay members worldwide in their local currency.', + getStarted: 'Get started', + }, deleteCard: 'Delete card', deleteCardConfirmation: 'All unsubmitted card transactions, including those on open reports, will be removed. Are you sure you want to delete this card? You cannot undo this action.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 1255ad655989..c58e926a2e6e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2738,6 +2738,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Esta cuenta requiere atención', unlock: 'Desbloquear', }, + wiseGlobalReimbursementsIntro: {title: '¿Configurar reembolsos globales?', subtitle: 'Paga a miembros de todo el mundo en su moneda local.', getStarted: 'Empieza'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Añadir tarjeta a ${walletName}`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index c629e2bd4b0e..3808f762e49f 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -2833,6 +2833,11 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Ce compte nécessite une attention particulière', unlock: 'Déverrouiller', }, + wiseGlobalReimbursementsIntro: { + title: 'Configurer les remboursements globaux ?', + subtitle: 'Payez des membres partout dans le monde dans leur devise locale.', + getStarted: 'Commencer', + }, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Ajouter la carte à ${walletName}`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 9db5fb5ac92f..dcaa042442c1 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -2817,6 +2817,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Questo conto richiede attenzione', unlock: 'Sblocca', }, + wiseGlobalReimbursementsIntro: {title: 'Configurare i rimborsi globali?', subtitle: 'Paga i membri in tutto il mondo nella loro valuta locale.', getStarted: 'Inizia'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Aggiungi carta a ${walletName}`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index d412979fa9bc..a628acf7c99f 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -2796,6 +2796,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'このアカウントには対応が必要です', unlock: 'ロック解除', }, + wiseGlobalReimbursementsIntro: {title: 'グローバル払い戻しを設定しますか?', subtitle: '世界中のメンバーに現地通貨で支払えます。', getStarted: '使い始める'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `${walletName} にカードを追加`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 60718020161a..eca1e5ede3dc 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -2821,6 +2821,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Deze account vereist aandacht', unlock: 'Ontgrendelen', }, + wiseGlobalReimbursementsIntro: {title: 'Wereldwijde terugbetalingen instellen?', subtitle: 'Betaal leden wereldwijd in hun eigen lokale valuta.', getStarted: 'Aan de slag'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Kaart toevoegen aan ${walletName}`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9792f3bfe001..dbe07ea48848 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -2855,6 +2855,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'To konto wymaga uwagi', unlock: 'Odblokuj', }, + wiseGlobalReimbursementsIntro: {title: 'Skonfigurować globalne zwroty kosztów?', subtitle: 'Płać członkom na całym świecie w ich lokalnej walucie.', getStarted: 'Zacznij'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Dodaj kartę do ${walletName}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index ca2cb4c94ba2..b0a66637ef8a 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -2812,6 +2812,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: 'Esta conta precisa de atenção', unlock: 'Desbloquear', }, + wiseGlobalReimbursementsIntro: {title: 'Configurar reembolsos globais?', subtitle: 'Pague membros no mundo todo na moeda local deles.', getStarted: 'Começar'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `Adicionar cartão à ${walletName}`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 44a092b96b81..b175355be0eb 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -2722,6 +2722,7 @@ const translations: TranslationDeepObject = { accountRequiresAttention: '此账户需要处理', unlock: '解锁', }, + wiseGlobalReimbursementsIntro: {title: '设置全球报销?', subtitle: '按当地货币向全球成员付款。', getStarted: '开始使用'}, }, addCardToDigitalWallet: { title: ({walletName}: {walletName: string}) => `将卡添加到 ${walletName}`, diff --git a/src/libs/BankAccountUtils.ts b/src/libs/BankAccountUtils.ts index f34ad0e810ce..669d7d5eeb0c 100644 --- a/src/libs/BankAccountUtils.ts +++ b/src/libs/BankAccountUtils.ts @@ -136,6 +136,14 @@ function isFilterableBankAccount(bankAccount: OnyxEntry): return bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && !isBankAccountPartiallySetup(bankAccount?.accountData?.state); } +/** + * A verified (OPEN) USD business bank account can start the Wise global reimbursements flow. + * TODO: Also exclude accounts that already have Wise global reimbursements enabled, once the backend field shape is confirmed. + */ +function isEligibleForWiseGlobalReimbursements(accountData: AccountData | undefined): boolean { + return accountData?.additionalData?.currency === CONST.CURRENCY.USD && accountData.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && accountData.state === CONST.BANK_ACCOUNT.STATE.OPEN; +} + function doesPolicyHavePartiallySetupBankAccount(bankAccountList: OnyxEntry, policyID: string) { if (!bankAccountList) { return false; @@ -300,6 +308,7 @@ function showUnlockAlreadyRequestedModal(showConfirmModal: ReturnType('../../../../pages/settings/Wallet/EnableGlobalReimbursements/EnableGlobalReimbursementsSignPage').default, [SCREENS.SETTINGS.WALLET.SHARE_BANK_ACCOUNT]: () => require('../../../../pages/settings/Wallet/ShareBankAccount/ShareBankAccount').default, [SCREENS.SETTINGS.WALLET.EDIT_BANK_ACCOUNT_NICKNAME]: () => require('../../../../pages/settings/Wallet/EditBankAccountNicknamePage').default, + [SCREENS.SETTINGS.WALLET.WISE_GLOBAL_REIMBURSEMENTS_INTRO]: () => require('../../../../pages/settings/Wallet/WiseGlobalReimbursementsIntroPage').default, [SCREENS.SETTINGS.WALLET.TRAVEL_CVV]: () => require('../../../../pages/settings/Wallet/TravelCVVPage/TravelCVVPage').default, [SCREENS.SETTINGS.WALLET.TRAVEL_CVV_VERIFY_ACCOUNT]: () => require('../../../../pages/settings/Wallet/TravelCVVPage/TravelCVVVerifyAccountPage').default, [SCREENS.SETTINGS.WALLET.PERSONAL_CARD_FIX_CONNECTION]: () => require('@pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index cd258c3841e1..551cf527dfd4 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -52,6 +52,7 @@ const SETTINGS_TO_RHP: Partial['config'] = { path: ROUTES.SETTINGS_WALLET_SHARE_BANK_ACCOUNT.route, exact: true, }, + [SCREENS.SETTINGS.WALLET.WISE_GLOBAL_REIMBURSEMENTS_INTRO]: { + path: ROUTES.SETTINGS_WALLET_WISE_GLOBAL_REIMBURSEMENTS_INTRO.route, + exact: true, + }, [SCREENS.SETTINGS.WALLET.EDIT_BANK_ACCOUNT_NICKNAME]: { path: ROUTES.SETTINGS_WALLET_EDIT_BANK_ACCOUNT_NICKNAME.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ce5bb62a6455..6856ad9d3aa9 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -254,6 +254,9 @@ type SettingsNavigatorParamList = { [SCREENS.SETTINGS.WALLET.SHARE_BANK_ACCOUNT]: { bankAccountID: string; }; + [SCREENS.SETTINGS.WALLET.WISE_GLOBAL_REIMBURSEMENTS_INTRO]: { + bankAccountID: string; + }; [SCREENS.SETTINGS.WALLET.EDIT_BANK_ACCOUNT_NICKNAME]: { bankAccountID: string; }; diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx new file mode 100644 index 000000000000..42eb9874a36b --- /dev/null +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -0,0 +1,86 @@ +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ConfirmationPage from '@components/ConfirmationPage'; +import Header from '@components/Header'; +import ScreenWrapper from '@components/ScreenWrapper'; + +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import {isEligibleForWiseGlobalReimbursements} from '@libs/BankAccountUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; + +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; + +import variables from '@styles/variables'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; + +import React, {useEffect} from 'react'; + +type WiseGlobalReimbursementsIntroPageProps = PlatformStackScreenProps; + +function WiseGlobalReimbursementsIntroPage({route}: WiseGlobalReimbursementsIntroPageProps) { + const bankAccountID = Number(route.params.bankAccountID); + const [bankAccountList, bankAccountListMetadata] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const bankAccount = bankAccountList?.[bankAccountID]; + + const {translate} = useLocalize(); + const {isBetaEnabledOrUnknown} = usePermissions(); + const isWiseReimbursementsBetaEnabled = isBetaEnabledOrUnknown(CONST.BETAS.WISE_REIMBURSEMENTS); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const illustrations = useMemoizedLazyIllustrations(['EarthWithCoins']); + + const goBackToWallet = () => Navigation.goBack(ROUTES.SETTINGS_WALLET); + + // Close the page instead of showing "not found" when the user lacks the beta. Wait for betas to load first + useEffect(() => { + if (isWiseReimbursementsBetaEnabled !== false) { + return; + } + Navigation.goBack(ROUTES.SETTINGS_WALLET); + }, [isWiseReimbursementsBetaEnabled]); + + if (isLoadingOnyxValue(bankAccountListMetadata) || !isWiseReimbursementsBetaEnabled) { + return null; + } + + return ( + + +
+ + +
+ {}} + /> +
+
+ ); +} + +export default WiseGlobalReimbursementsIntroPage; diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 45764d769358..387877185d85 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -376,6 +376,7 @@ export default { productMarketingWindowVisualAspectRatio: 7 / 4, productMarketingWindowIllustrationSize: 128, productMarketingWindowOffset: 20, + wiseGlobalReimbursementsIntroIllustrationSize: 250, productMarketingWindowOffsetNarrow: 12, productMarketingWindowHorizontalOffsetNarrow: 16, holdEducationModalWidth: 400, diff --git a/tests/unit/BankAccountUtilsTest.ts b/tests/unit/BankAccountUtilsTest.ts index 23036f167f60..d65f2d80ea6c 100644 --- a/tests/unit/BankAccountUtilsTest.ts +++ b/tests/unit/BankAccountUtilsTest.ts @@ -9,6 +9,7 @@ import { hasPartiallySetupBankAccount, hasPersonalBankAccountMissingInfo, isBankAccountPartiallySetup, + isEligibleForWiseGlobalReimbursements, isPersonalBankAccountMissingInfo, isUserAddressVerificationRequired, isUserDOBVerificationRequired, @@ -222,6 +223,59 @@ describe('BankAccountUtils', () => { }); }); + describe('isEligibleForWiseGlobalReimbursements', () => { + const eligibleAccountData: AccountData = { + type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, + state: CONST.BANK_ACCOUNT.STATE.OPEN, + additionalData: {currency: CONST.CURRENCY.USD}, + }; + + it('returns true for a verified USD business account', () => { + // Given a verified USD business bank account + // When checking whether it can start the Wise global reimbursements flow + // Then it is eligible, since that is the only kind of account Wise onboarding supports + expect(isEligibleForWiseGlobalReimbursements(eligibleAccountData)).toBe(true); + }); + + it('returns false when the account data is missing', () => { + // Given no account data, e.g. an unknown bankAccountID in the URL + // When checking eligibility + // Then it is not eligible, so the intro page shows not-found instead of crashing + expect(isEligibleForWiseGlobalReimbursements(undefined)).toBe(false); + }); + + it('returns false for a personal account', () => { + // Given a personal account that is otherwise eligible + // When checking eligibility + // Then it is not eligible, because global reimbursements are paid from business accounts only + expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, type: CONST.BANK_ACCOUNT.TYPE.PERSONAL})).toBe(false); + }); + + it('returns false for a non-USD account', () => { + // Given a business account in a currency other than USD + // When checking eligibility + // Then it is not eligible, because Release 1 only supports U.S. accounts + expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, additionalData: {currency: CONST.CURRENCY.EUR}})).toBe(false); + }); + + it('returns false when the currency is missing', () => { + // Given a business account without a currency + // When checking eligibility + // Then it is not eligible, matching the existing Corpay condition that requires an explicit USD currency + expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, additionalData: {}})).toBe(false); + }); + + it.each([CONST.BANK_ACCOUNT.STATE.SETUP, CONST.BANK_ACCOUNT.STATE.PENDING, CONST.BANK_ACCOUNT.STATE.VERIFYING, CONST.BANK_ACCOUNT.STATE.LOCKED])( + 'returns false for an account in the %s state', + (state) => { + // Given a USD business account that is not verified yet or is locked + // When checking eligibility + // Then it is not eligible, because only OPEN accounts can send reimbursements + expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, state})).toBe(false); + }, + ); + }); + describe('getLastFourDigits', () => { it.each([ {input: '123456789012', expected: '9012', description: 'long account number'}, From 3e5ef1620a610c547c75dbcd9e5b3a132bfb09d6 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 12:02:33 +0200 Subject: [PATCH 2/9] fix: show loading on Wise intro page and regroup its size variable --- .../settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 3 ++- src/styles/variables.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 42eb9874a36b..258dcb4c1ec2 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -1,5 +1,6 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ConfirmationPage from '@components/ConfirmationPage'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Header from '@components/Header'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -51,7 +52,7 @@ function WiseGlobalReimbursementsIntroPage({route}: WiseGlobalReimbursementsIntr }, [isWiseReimbursementsBetaEnabled]); if (isLoadingOnyxValue(bankAccountListMetadata) || !isWiseReimbursementsBetaEnabled) { - return null; + return ; } return ( diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 387877185d85..4c4566d71bd1 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -376,9 +376,9 @@ export default { productMarketingWindowVisualAspectRatio: 7 / 4, productMarketingWindowIllustrationSize: 128, productMarketingWindowOffset: 20, - wiseGlobalReimbursementsIntroIllustrationSize: 250, productMarketingWindowOffsetNarrow: 12, productMarketingWindowHorizontalOffsetNarrow: 16, + wiseGlobalReimbursementsIntroIllustrationSize: 250, holdEducationModalWidth: 400, changePolicyEducationModalWidth: 400, wideConfirmModalWidth: 400, From 1e65374188f6d5906fca02611bffd7dfb0696071 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 15:29:41 +0200 Subject: [PATCH 3/9] fix: use goBackToWallet in Wise intro page beta redirect effect --- .../settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 258dcb4c1ec2..207790e1c445 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -48,8 +48,8 @@ function WiseGlobalReimbursementsIntroPage({route}: WiseGlobalReimbursementsIntr if (isWiseReimbursementsBetaEnabled !== false) { return; } - Navigation.goBack(ROUTES.SETTINGS_WALLET); - }, [isWiseReimbursementsBetaEnabled]); + goBackToWallet(); + }, [isWiseReimbursementsBetaEnabled, goBackToWallet]); if (isLoadingOnyxValue(bankAccountListMetadata) || !isWiseReimbursementsBetaEnabled) { return ; From 580b5a3f77ad0829e1c6d25f8a05a60d581d7003 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 15:55:06 +0200 Subject: [PATCH 4/9] fix: wait for payment methods fetch before checking Wise intro eligibility --- .../settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 207790e1c445..3f94f8395b13 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -32,6 +32,7 @@ type WiseGlobalReimbursementsIntroPageProps = PlatformStackScreenProps; } From bc62b61564f7e14d40b05ac458c3b42fd6a9d6d1 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 15:57:04 +0200 Subject: [PATCH 5/9] fix: set shouldUseGoBackButton on Wise intro page loading indicator --- src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 3f94f8395b13..3868c2e37d8f 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -56,7 +56,7 @@ function WiseGlobalReimbursementsIntroPage({route}: WiseGlobalReimbursementsIntr const isWaitingForBankAccount = !bankAccount && !!isLoadingPaymentMethods; if (isLoadingOnyxValue(bankAccountListMetadata) || isWaitingForBankAccount || !isWiseReimbursementsBetaEnabled) { - return ; + return ; } return ( From 7b03eb73bdddbfcb0a90bc1ec88d1672997615fb Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 15:59:44 +0200 Subject: [PATCH 6/9] chore: remove TODO comments from Wise global reimbursements code --- src/libs/BankAccountUtils.ts | 1 - src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/BankAccountUtils.ts b/src/libs/BankAccountUtils.ts index 669d7d5eeb0c..9e5cd8ffb4b4 100644 --- a/src/libs/BankAccountUtils.ts +++ b/src/libs/BankAccountUtils.ts @@ -138,7 +138,6 @@ function isFilterableBankAccount(bankAccount: OnyxEntry): /** * A verified (OPEN) USD business bank account can start the Wise global reimbursements flow. - * TODO: Also exclude accounts that already have Wise global reimbursements enabled, once the backend field shape is confirmed. */ function isEligibleForWiseGlobalReimbursements(accountData: AccountData | undefined): boolean { return accountData?.additionalData?.currency === CONST.CURRENCY.USD && accountData.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && accountData.state === CONST.BANK_ACCOUNT.STATE.OPEN; diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 3868c2e37d8f..0932ce428b20 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -80,7 +80,6 @@ function WiseGlobalReimbursementsIntroPage({route}: WiseGlobalReimbursementsIntr onSecondaryButtonPress={goBackToWallet} shouldShowButton buttonText={translate('walletPage.wiseGlobalReimbursementsIntro.getStarted')} - // TODO: Navigate to the next Wise onboarding step once it exists onButtonPress={() => {}} /> From ebb70ed1a0ee0461d74e29b65faedb787f5735c3 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 16:04:36 +0200 Subject: [PATCH 7/9] fix: add heading role and hide decorative illustration in ConfirmationPage --- src/components/ConfirmationPage.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index 0d1bdafffcd7..204fbb6f62d6 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -127,6 +127,7 @@ function ConfirmationPage({ ); @@ -150,11 +151,17 @@ function ConfirmationPage({ ); })()} - {heading} + + {heading} + {!!descriptionComponent && descriptionComponent} {!!description && {description}} {cta ? {cta} : null} From 9bf3043e2283e3d352cbe8ee63ca2c06216c14d8 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 19 Oct 2026 16:39:31 +0200 Subject: [PATCH 8/9] fix: show loader instead of not-found on cold Wise intro deep link --- src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx index 0932ce428b20..a98061f9a6c5 100644 --- a/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx +++ b/src/pages/settings/Wallet/WiseGlobalReimbursementsIntroPage.tsx @@ -32,7 +32,7 @@ type WiseGlobalReimbursementsIntroPageProps = PlatformStackScreenProps Date: Mon, 19 Oct 2026 16:44:30 +0200 Subject: [PATCH 9/9] fix: hide Wise intro for accounts already set up with Corpay --- src/libs/BankAccountUtils.ts | 9 +++++++-- tests/unit/BankAccountUtilsTest.ts | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libs/BankAccountUtils.ts b/src/libs/BankAccountUtils.ts index 9e5cd8ffb4b4..0e9860c0f036 100644 --- a/src/libs/BankAccountUtils.ts +++ b/src/libs/BankAccountUtils.ts @@ -137,10 +137,15 @@ function isFilterableBankAccount(bankAccount: OnyxEntry): } /** - * A verified (OPEN) USD business bank account can start the Wise global reimbursements flow. + * A verified (OPEN) USD business bank account that is not already set up for global reimbursements can start the Wise flow. */ function isEligibleForWiseGlobalReimbursements(accountData: AccountData | undefined): boolean { - return accountData?.additionalData?.currency === CONST.CURRENCY.USD && accountData.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && accountData.state === CONST.BANK_ACCOUNT.STATE.OPEN; + return ( + accountData?.additionalData?.currency === CONST.CURRENCY.USD && + accountData.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && + !accountData.additionalData.verifications?.corpay && + accountData.state === CONST.BANK_ACCOUNT.STATE.OPEN + ); } function doesPolicyHavePartiallySetupBankAccount(bankAccountList: OnyxEntry, policyID: string) { diff --git a/tests/unit/BankAccountUtilsTest.ts b/tests/unit/BankAccountUtilsTest.ts index d65f2d80ea6c..c9e16023cec4 100644 --- a/tests/unit/BankAccountUtilsTest.ts +++ b/tests/unit/BankAccountUtilsTest.ts @@ -265,6 +265,13 @@ describe('BankAccountUtils', () => { expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, additionalData: {}})).toBe(false); }); + it('returns false when the account is already set up with Corpay', () => { + // Given a verified USD business account that already has a Corpay verification + // When checking eligibility + // Then it is not eligible, because global reimbursements are already enabled for it, matching the Wallet menu condition + expect(isEligibleForWiseGlobalReimbursements({...eligibleAccountData, additionalData: {currency: CONST.CURRENCY.USD, verifications: {corpay: {}}}})).toBe(false); + }); + it.each([CONST.BANK_ACCOUNT.STATE.SETUP, CONST.BANK_ACCOUNT.STATE.PENDING, CONST.BANK_ACCOUNT.STATE.VERIFYING, CONST.BANK_ACCOUNT.STATE.LOCKED])( 'returns false for an account in the %s state', (state) => {