fix: KYC flow doesn't trigger after adding bank account when paying another user#92154
Conversation
|
@Eskalifer1 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adjusts the personal bank account clearing logic so that, when entering the international deposit account flow, routing-related data is preserved while transient UI/error state is reset.
Changes:
- Added a
shouldPreserveRoutingDataparameter toclearPersonalBankAccountthat, when true, only clears loading/error/success flags instead of wiping the entire Onyx key. - Updated
AccountFlowEntryPointto callclearPersonalBankAccount(true)to retain routing data on mount.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/libs/actions/BankAccounts.ts | Adds optional preserve flag to selectively merge-clear transient state instead of nulling the whole bank account key. |
| src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx | Passes true to preserve routing data when clearing on entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1e59aa03c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
| function clearPersonalBankAccount(shouldPreserveRoutingData = false) { | ||
| /** | ||
| * Clears personal bank account state. Pass `shouldPreserveAccountData=true` to only clear UI/error | ||
| * fields while keeping routing and context fields intact. | ||
| */ | ||
| function clearPersonalBankAccount(shouldPreserveAccountData = false) { | ||
| clearPlaid(); | ||
| if (shouldPreserveRoutingData) { | ||
| if (shouldPreserveAccountData) { |
There was a problem hiding this comment.
I don't think we should do it this way, because we currently have cleanup logic throughout the entire project, so I think we should keep it.
I think that when we added the cleanup logic, we simply forgot that onSuccessFallbackRoute shouldn’t be cleaned up, so I think we should just fix that. If we leave it as it is now, we’ll definitely get regressions and incorrect behavior.
I think it should look something like this:
function clearPersonalBankAccount(preservedData?: Partial<PersonalBankAccount>) {
clearPlaid();
Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, preservedData ?? null);
Onyx.set(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, null);
clearPersonalBankAccountSetupType();
}And pass it like this:
useEffect(() => {
clearPersonalBankAccount(personalBankAccount?.onSuccessFallbackRoute ? {onSuccessFallbackRoute: personalBankAccount.onSuccessFallbackRoute} : undefined);
}, []);What do you think about that?
There was a problem hiding this comment.
Agreed, that's much cleaner approach. Updated clearPersonalBankAccount to take preservedData?: Partial<PersonalBankAccount> with Onyx.set(preservedData ?? null), and the entry point now passes only onSuccessFallbackRoute. All other call sites stay as a full reset, so no behavior change of any kind there.
…g data and update its usage in AccountFlowEntryPoint
…nkAccount function
Eskalifer1
left a comment
There was a problem hiding this comment.
Please fill out the Explanation of Change section
Let's also add some information to the Offline tests section, specifically “N/A” and the reason why there are no offline tests here
|
I also think we should update the |
Co-authored-by: Eskalifer1 <artemkryt1800@gmail.com>
|
Thanks @Eskalifer1 for taking a look, I'm looking into this and will update the PR shortly. |
|
Sure! Let me know when this PR will be ready for next round of reviews! |
|
@codex review |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp91873-android-native.movAndroid: mWeb Chrome91873-android-web.moviOS: HybridApp91873-ios-native.moviOS: mWeb Safari91873-ios-web.movMacOS: Chrome / Safari91873-web.mov |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 249dba04e1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| useEffect(() => { | ||
| clearPersonalBankAccount(); | ||
| }, []); | ||
| clearPersonalBankAccount(onSuccessFallbackRoute ? {onSuccessFallbackRoute} : undefined); |
There was a problem hiding this comment.
Avoid reusing stale KYC fallback routes
When a user starts a Pay/KYC flow that sets onSuccessFallbackRoute and then backs out from this entry point, nothing in the entry-point back path clears PERSONAL_BANK_ACCOUNT; with this change, the stale fallback route is preserved instead of being wiped. If the same user later starts a normal Wallet > Add bank account flow, this line preserves that old route again, and AddPersonalBankAccountPage.exitFlow(true) will call continueSetup(...) instead of just returning to Wallet after success. Please only preserve the fallback for the active KYC-launched flow or clear it when the add-bank flow is abandoned.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is valid concern. I think we should find a solution to this. Although it's unlikely, it's still possible
There was a problem hiding this comment.
Updated the logic to handle this edge case.
There was a problem hiding this comment.
Hi @samranahm, I don't really think that's the approach we want to take. After all, it makes more sense to clear the previous data on entry (which is much more stable) rather than on exit—plus, that's the logic that's been working for a very long time.
Can you check how onSuccessFallbackRoute was being cleared before this useEffect was added? Maybe that will give us a little more information.
There was a problem hiding this comment.
@MelvinBot How can this be fixed using this approach:
const [personalBankAccount, personalBankAccountResult] = useOnyx(ONYXKEYS.PERSONAL_BANK_ACCOUNT);
const isLoadingPersonalBankAccount = isLoadingOnyxValue(personalBankAccountResult)
useEffect(() => {
if (isLoadingPersonalBankAccount) {
return;
}
clearPersonalBankAccount(onSuccessFallbackRoute ? {onSuccessFallbackRoute} : undefined);
}, [isLoadingPersonalBankAccount]);How was onSuccessFallbackRoute cleared before this useEffect was added?
There was a problem hiding this comment.
Short version: onSuccessFallbackRoute has never had a dedicated clear — it's only ever wiped as a side effect of a full clearPersonalBankAccount() (which nulls the whole PERSONAL_BANK_ACCOUNT key). The entry-point mount clear was one of those full wipes, and it was the only thing protecting this screen from a stale route. So the isLoadingOnyxValue version fixes your deps concern, but on its own it does not close the stale-route hole chatgpt-codex-connector flagged in this thread — you need one more piece for that.
Q2 — How was onSuccessFallbackRoute cleared before this useEffect?
There is no route-specific clear anywhere in the codebase. The field's whole lifecycle:
- Set before a pay/KYC flow begins, via
setPersonalBankAccountContinueKYCOnSuccess— called fromSettlementButton,PaymentUtils,Search,WalletPage. - Consumed on success by
exitFlow(true)→continueSetup(it reads the route to navigate; it does not clear it). - Cleared only by full
clearPersonalBankAccount()calls scattered through the flow: theAccountFlowEntryPointmount clear (the long-standing one you're referring to), plusAddBankAccount.tsx:73,AddPersonalBankAccountPage.tsx:52,PersonalInfo.tsx:34-37(KYC continuation),FeesAndTerms.tsx:27(end of flow).
So on main, entering this screen always nuked the entire object. That wipe is simultaneously (a) what kept a stale route from leaking into a later flow, and (b) what wiped the route this PR needs to keep — the bug. That's the tension: the moment you preserve the route on entry, you remove the thing that used to protect against stale routes. Note openPersonalBankAccountSetupView({}) — the generic Wallet → Add bank account entry — does not clear PERSONAL_BANK_ACCOUNT, which is precisely why chatgpt-codex-connector's scenario is reachable.
Q1 — Making the isLoadingOnyxValue approach work
It needs two pieces, not one.
Piece A — the entry-point effect (your version, corrected)
const [personalBankAccount, personalBankAccountResult] = useOnyx(ONYXKEYS.PERSONAL_BANK_ACCOUNT);
const isLoadingPersonalBankAccount = isLoadingOnyxValue(personalBankAccountResult);
const onSuccessFallbackRoute = personalBankAccount?.onSuccessFallbackRoute;
useEffect(() => {
if (isLoadingPersonalBankAccount) {
return;
}
clearPersonalBankAccount(onSuccessFallbackRoute ? {onSuccessFallbackRoute} : undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoadingPersonalBankAccount]);with clearPersonalBankAccount(preservedData?: Partial<PersonalBankAccount>) doing Onyx.set(PERSONAL_BANK_ACCOUNT, preservedData ?? null).
Two things to get right:
onSuccessFallbackRoutemust be derived frompersonalBankAccount?.onSuccessFallbackRoute(your snippet references it but doesn't declare it).- The
isLoadingOnyxValueguard is load-bearing, not cosmetic:useOnyxreturnsundefinedwhile the key hydrates. Without the guard, the first render readsundefined→clearPersonalBankAccount(undefined)→Onyx.set(..., null)→ the route is wiped again (the original bug). Gating on hydration guarantees you read the real route before clearing, and keeping the dep array as[isLoadingPersonalBankAccount]only means the re-seed write won't re-trigger the effect (which is also what fixes your duplicate-clean concern). ✅
Piece B — close the stale-route hole (the concern this thread is about)
Piece A alone still preserves the route on every entry, so the abandon path you and chatgpt-codex-connector discussed is still open: set route → land here → preserve → back out (nothing clears it) → later generic flow reuses the stale route.
Since you (rightly) want to keep clear-on-entry rather than move it to unmount, the targeted fix is to wipe fully when the flow is actually abandoned at this entry screen. Wrap the back handler so leaving without proceeding does a full reset:
const handleBackButtonPress = () => {
clearPersonalBankAccount(); // full wipe — abandoning the flow must not leave a stale route
onBackButtonPress();
};and pass handleBackButtonPress to HeaderWithBackButton. That preserves the route while moving forward through the flow, but clears it on a real abandon — satisfying both "clear on entry is stable" and "no stale route."
To be transparent: I verified the data flow and call sites directly from the code (links above), but I did not reproduce the stale-route scenario in a running app — it's plausible and you already agreed it's valid, just flagged here as analysis rather than a confirmed repro.
There was a problem hiding this comment.
@samranahm I think we can try to use this approach
There was a problem hiding this comment.
@Eskalifer1 We have same approach in AddPersonalBankAccountPage to clear the data on unmount, this make sure we clear everything when flow finish to make sure each new flow start fresh.
This is already established approach through out the codebase.
There was a problem hiding this comment.
It's already 1AM for me, so i will take a closer look tomorrow
…fter-adding-account
|
@Eskalifer1 Preserving We actually merge Here's what I found to be the most professional solution Wipe |
|
This looks pretty good to me. Sorry to be a stickler, but @Eskalifer1 would you mind running through the tests one more time on the various platforms to make sure that the latest change to the useEffect dependencies array in AccountFlowEntryPoint is safe? Thanks 🙏 |
|
Hi @chuckdries Actually, we haven't really changed the logic much compared to |
|
removing |
…t to prevent unexpected re-render
|
@Eskalifer1 good eye! Yes, let's please fix that spacing/padding issue. |
|
Hi @chuckdries, i have tested all works good(atleast i didn;t find any issue), i think we can procced with this one! Also desgn team decided we should fix this issue, can you create new issue for that pls! I think i can work with Melvin on this one to make sure all works well and i didn't miss something! Or we can open for external! |
I don't think this is related to this issue and seems like change to logic that is working now. But it's just IMO |
|
I don't mind that idea, but I also don't think we need to make any changes to the flow - the extra account name confirmation isn't necessarily a bad thing. |
|
I don't think we should skip the choose account screen here, would be out of scope for this fix. |
|
Ok, just running through the test steps one last time. Test 1 passes, but for test 2, I noticed that the user is returned to the "Streamline payments" screen, even though we can see underneath that the bank account was successfully added. @samranahm @Eskalifer1 can either of you confirm whether this is a regression in this PR? kyc.PR.test.mp4 |
I see there is the same bahaviour on stage, so it's not regression. Let's update Test case @samranahm and resolve conflict |
|
Agree with @Eskalifer1, we're not changing this behaviour in our PR. Updated the test steps. |
…fter-adding-account # Conflicts: # src/components/KYCWall/BaseKYCWall.tsx
|
Resolved the conflict, @chuckdries all yours. |
|
🚧 chuckdries has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/chuckdries in version: 9.4.20-0 🚀
|
|
🤖 I reviewed the changes in this PR against Expensify's help-site articles under Why: This PR is a bug fix. It restores the KYC/identity-verification step so it correctly triggers after a user adds a personal bank account while paying another user. It threads The behavior this PR restores is already accurately documented:
Since these articles describe the intended flow — which is exactly what this PR makes work again — none of them are now inaccurate or incomplete. No draft docs PR has been created. @samranahm, please confirm you agree no help-site updates are needed here. If you believe a specific article should be updated (e.g., to call out the verification step in the pay flow more explicitly), let me know and I'll create the draft PR. |
|
🚀 Deployed to production by https://github.com/blimpich in version: 9.4.20-1 🚀
Bundle Size Analysis (Sentry): |

Explanation of Change
When paying another user with a personal bank account, the KYC flow wasn't triggering after adding the account. The cause was
AccountFlowEntryPointcallingclearPersonalBankAccount()on mount, which wiped theonSuccessFallbackRoutethat the pay flow sets before this screen and thatAddPersonalBankAccountPagereads after it, so the app could no longer continue into KYC.To fix this we:
Thread
onSuccessFallbackRoutethrough the KYC flow, pay/search/settlement flows passpersonalBankAccountOnSuccessFallbackRouteintotriggerKYCFlow, and Wallet usesgetPersonalBankAccountOnSuccessFallbackRoute, both store the route inPERSONAL_BANK_ACCOUNTviaopenPersonalBankAccountSetupViewwhen the user selects "Personal bank account".Preserve the route on entry,
AccountFlowEntryPointstill clears stalePERSONAL_BANK_ACCOUNTstate on mount (for deep links and any edge case leftover data), but passesonSuccessFallbackRouteintoclearPersonalBankAccountso the KYC continuation route survives.Reset cleanly between flows,
openPersonalBankAccountSetupViewnow useOnyx.setinstead ofmerge, so each new flow starts with only the fields explicitly passed.Fixed Issues
$ #91873
PROPOSAL: #91873 (comment)
Tests
Precondition:
Test 01
Test 02
Offline tests
N/A – Bank account setup can only be run while online.
QA Steps
Same as test
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android.Native.mp4
Android: mWeb Chrome
Android.mWeb.Chrome.mp4
iOS: Native
IOS.Native.mp4
iOS: mWeb Safari
IOS.mWeb.Safari.mp4
MacOS: Chrome / Safari
macOS.Chrome.mp4