Repository navigation
Reuse the active workspace when adding a bank account to pay an invoice as a business - #97047
Conversation
…e-add-bank-account-duplicate-workspace
…e-add-bank-account-duplicate-workspace
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 670c7673f1
ℹ️ 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".
| if (hasActivePolicyAsAdmin) { | ||
| return activePolicy.id; |
There was a problem hiding this comment.
Keep bank setup currency aligned with the invoice
When the active admin workspace is EUR but the invoice and payer currency are USD—the exact scenario added to the regression test—this branch now opens bank setup for the EUR workspace. The non-USD setup fixes the account currency to policy.outputCurrency (NonUSDVerifiedBankAccountFlowPage.tsx lines 74–76), while SettlementButton only offers accounts whose bankCurrency matches the invoice currency via matchesCurrency() (PaymentUtils.ts lines 165–166). The newly created EUR account therefore never appears as a payment method for the USD invoice, leaving the payer unable to complete payment; reuse needs to route the workspace through a currency change first or otherwise preserve creation of a currency-compatible workspace.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Verified this in the app — the mechanism is real. With a EUR active workspace the reuse branch does enter the non-USD flow, so the new account's currency follows the workspace rather than the invoice.
It isn't a regression though. In that same state (payer local currency IDR, USD invoice, EUR active workspace), main doesn't reuse, so it creates a workspace in the payer's local currency — IDR — which isn't globally reimbursable and dead-ends on the "set your workspace currency to USD" screen. And it does that on every press, which is the duplicate-workspace bug being fixed here. Neither path produces a payable account in that scenario; main just also litters workspaces.
main only lands on a compatible currency when the payer's local currency happens to match the invoice, since it comes from localCurrencyCode and is independent of the invoice currency. So bank-account currency vs invoice currency is a pre-existing gap rather than something this change introduces, and it's worth tracking separately — making reuse currency-aware here would mean reinstating the exact gate that causes the duplicates.
There was a problem hiding this comment.
Thanks for checking this @wildan-m. What do you think about the specific case where the invoice and the payer’s payment currency are USD, while the active workspace is EUR?
I believe this may differ from the IDR scenario mentioned in your response. Could the resulting EUR bank account be filtered out when returning to the USD invoice? I’m not completely certain, so perhaps it would be worth verifying this exact scenario. What do you think?
There was a problem hiding this comment.
@brunovjk Yes, it'd be filtered — matchesCurrency compares bankCurrency against the invoice currency. Whether that's a regression turns on the payer's localCurrencyCode rather than their payment currency: if it's non-USD (what I tested) main also ends up unpayable and spawns a workspace per press, so only a USD localCurrencyCode favours main — and that one I can't reproduce, since it's set at signup with no setting to change it.
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppUploading 97047_android_native.mov… Android: mWeb ChromeUploading 97047_android_web.mov… iOS: HybridAppUploading 97047_ios_native.mov… iOS: mWeb SafariUploading 97047_ios_web.mov… MacOS: Chrome / Safari97047_web_chrome.mov |
|
All yours @marcochavezf. Thanks. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 marcochavezf has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/marcochavezf in version: 9.4.46-0 🚀
|
|
No help site changes are required for this PR, so I did not create a draft docs PR. Why: This is a bug fix to internal logic. The change in I reviewed the relevant help article, Pay-an-invoice.md, which documents the payer flow (Pay → pay as an individual / as a business → Add Bank Account). That flow is unchanged by this PR — the fix corrects the duplicate-workspace bug behind the scenes, and the duplicate behavior was never documented. So the article already reflects the corrected behavior; nothing needs to be added or edited. @wildan-m, if you believe there's a documented behavior I missed that this PR changes, let me know and I'll draft the help site update. |
|
🚀 Deployed to production by https://github.com/marcaaron in version: 9.4.46-10 🚀
Bundle Size Analysis (Sentry): |
Explanation of Change
When paying an invoice as a business, the "Add bank account" option resolves which workspace the bank account should be attached to. For an individual invoice receiver it reuses the active workspace only when that workspace is one the user administers and its currency is already supported for direct reimbursement — and only US dollars qualify. A payer whose workspace is in any other currency therefore fails the reuse check on every attempt, so a brand new workspace is minted each time and the account fills up with duplicates. The first press makes matters worse rather than better: the workspace it creates takes the payer's local currency, becomes the active one, and then sends the user into the currency setup screen, so the very next press hits the same failing check again and loops.
The currency check does not belong in the reuse-or-create decision. Whether a workspace is already in a supported currency governs whether direct reimbursement can be offered, not whether an in-progress workspace should be abandoned — the screen the user lands on next exists precisely to finish configuring it. Basing the decision on administration rights alone is strictly broader than the previous condition, since the old one already implied it, so every case that reused a workspace before still reuses the same one; only the looping path changes. Visibility of the option is untouched and continues to use the currency check.
Fixed Issues
$ #92859
PROPOSAL: #92859 (comment)
Tests
Preconditions
payInvoiceViaExpensifybeta. If you are running the branch locally without it, overridePermissions.canUseAllBetasto returntrue— the approach documented in CONTRIBUTING.md § Working on beta features. Revert before pushing.Steps
policyID— and that no new workspace was created (open Workspaces and confirm the count is unchanged).policyIDis used every time and the workspace count never grows.On
main, step 1 creates a brand new workspace (named<workspace> 1,<workspace> 2, and so on) and every repeat creates another one.Offline tests
Same as tests. This change only alters which existing workspace ID is chosen before navigating, so there is no new network request and no new offline behavior. Reusing a workspace avoids the workspace-creation write entirely, so the offline path performs strictly less optimistic work than before.
QA Steps
Same as tests, with one requirement that cannot be worked around on staging: the payer's account must have the
payInvoiceViaExpensifybeta enabled. Without it the "Pay as a business" submenu has no bank account options at all.PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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: Nativehttps://github.com/user-attachments/assets/6afe40a8-7bc2-4d2f-878f-b8d9a3bb7e84
summary>
Android: mWeb Chrome
Kapture.2026-07-27.at.21.52.54.mp4
iOS: Native
Kapture.2026-07-27.at.21.42.08.mp4
iOS: mWeb Safari
Kapture.2026-07-27.at.21.34.50.mp4
MacOS: Chrome / Safari
Kapture.2026-07-27.at.21.22.38.mp4