Skip to content

[internal QA] Add confirmation before settling Expensify Card balance - #97401

Merged
youssef-lr merged 9 commits into
mainfrom
claude-settleBalanceConfirmation
Aug 27, 2026
Merged

[internal QA] Add confirmation before settling Expensify Card balance#97401
youssef-lr merged 9 commits into
mainfrom
claude-settleBalanceConfirmation

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Clicking Settle balance on the Expensify Card page used to fire an off-cycle balance settlement immediately, with no confirmation step — so users could (and did) trigger settlement by accident.

This gates the action behind a confirmation modal. handleSettleBalanceButtonClick in WorkspaceCardsListLabel.tsx now opens a danger confirm modal via the useConfirmModal hook, and queueExpensifyCardForBilling only runs when the user confirms (it's a no-op on cancel/dismiss). This mirrors the existing high-consequence confirmation pattern used in WorkspaceMembersPage.tsx.

New copy (settleBalanceConfirmationTitle / settleBalanceConfirmationPrompt) was added under workspace.expensifyCard in en.ts, and the existing settleBalance string is reused for the confirm button. This is New Expensify only — no Classic changes.

Implements the approved proposal, using the useConfirmModal hook per the C+ review (since ConfirmModal is being deprecated).

Fixed Issues

$ #96998
PROPOSAL: #96998 (comment)

Tests

  1. Open a workspace with an Expensify Card feed set to monthly settlement (so Settle balance is shown).
  2. Click Settle balance.
  3. Verify a confirmation modal appears with title "Settle balance?", the prompt copy, a red "Settle balance" confirm button, and a "Cancel" button.
  4. Click Cancel / dismiss — verify no settlement is initiated.
  5. Click Settle balance again, then confirm — verify the balance settlement is initiated as before.
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as Tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

MelvinBot and others added 2 commits July 29, 2026 22:28
Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team July 29, 2026 22:37
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from a branch on Expensify/App since you don't have push access to a fork of it. To take ownership of this branch so you can push updates, fork the repo (if you haven't already), then run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-settleBalanceConfirmation
git checkout -b claude-settleBalanceConfirmation upstream/claude-settleBalanceConfirmation
git push -u origin claude-settleBalanceConfirmation

Then you can close this PR and open a new one from your fork, or push directly here if you'd prefer to keep this PR.

@OSBotify

This comment has been minimized.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
...orkspace/expensifyCard/WorkspaceCardsListLabel.tsx 0.00% <0.00%> (ø)
... and 143 files with indirect coverage changes

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 I looked into the failing checks and there's nothing to fix in the code — every substantive check is green.

The only failing check is PR Author Checklist / checklist, and it fails simply because the checklist boxes aren't ticked ("0 out of 38 checklist items"). That's a human-only check — I won't tick the author checklist boxes or fill in the Tests / QA Steps sections on your behalf (the PR body flags those as TODOs for the human co-author). To turn it green, please complete the checklist items and the Tests/QA sections, then it'll re-run and pass.

All the real CI checks pass:

  • typecheck ✅, all 8 test (job N) ✅, Bun tests
  • ESLint check ✅, Oxfmt check ✅, React Compiler Compliance ✅, Storybook tests
  • All build (...) jobs ✅, generateTranslations ✅, spellcheck ✅, Check for unused styles ✅, Compare knip issues against main
  • baseline-perf-tests ✅ (branch-perf-tests was still running when I checked — it's the perf comparison, which reports as a separate comment and doesn't gate the merge)

So no push was needed. Let me know if you'd like me to take another look once the checklist is completed.

@situchan

situchan commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot pull main

@situchan

situchan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot pull main. Update QA Steps

@situchan

situchan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
android.mov
Android: mWeb Chrome
iOS: HybridApp
ios.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov

@situchan

situchan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot mark as ready for review

@joekaufmanexpensify
joekaufmanexpensify marked this pull request as ready for review August 5, 2026 19:38
@joekaufmanexpensify
joekaufmanexpensify requested a review from a team as a code owner August 5, 2026 19:38
@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and situchan and removed request for a team August 5, 2026 19:38
@melvin-bot

melvin-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

@situchan 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]

@melvin-bot
melvin-bot Bot removed the request for review from a team August 5, 2026 19:38
@joekaufmanexpensify
joekaufmanexpensify requested review from joekaufmanexpensify and removed request for JmillsExpensify August 5, 2026 19:39
@situchan

situchan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
Details

Explanation of Change

Clicking Settle balance on the Expensify Card page used to fire an off-cycle balance settlement immediately, with no confirmation step — so users could (and did) trigger settlement by accident.

This gates the action behind a confirmation modal. handleSettleBalanceButtonClick in WorkspaceCardsListLabel.tsx now opens a danger confirm modal via the useConfirmModal hook, and queueExpensifyCardForBilling only runs when the user confirms (it's a no-op on cancel/dismiss). This mirrors the existing high-consequence confirmation pattern used in WorkspaceMembersPage.tsx.

New copy (settleBalanceConfirmationTitle / settleBalanceConfirmationPrompt) was added under workspace.expensifyCard in en.ts, and the existing settleBalance string is reused for the confirm button. This is New Expensify only — no Classic changes.

Implements the approved proposal, using the useConfirmModal hook per the C+ review (since ConfirmModal is being deprecated).

Fixed Issues

$ #96998
PROPOSAL: #96998 (comment)

Tests

  1. Open a workspace with an Expensify Card feed set to monthly settlement (so Settle balance is shown).
  2. Click Settle balance.
  3. Verify a confirmation modal appears with title "Settle balance?", the prompt copy, a red "Settle balance" confirm button, and a "Cancel" button.
  4. Click Cancel / dismiss — verify no settlement is initiated.
  5. Click Settle balance again, then confirm — verify the balance settlement is initiated as before.
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as Tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

@MelvinBot please set the PR body to the content in the details section above

@melvin-bot
melvin-bot Bot requested a review from youssef-lr August 5, 2026 19:42
@joekaufmanexpensify

joekaufmanexpensify commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The confirmation modal looks good. One gap: there's no feedback after confirming on the modal that a settlement is pending. That's also true now, but I feel like some feedback would significantly improve the UX. cc @shawnborton

One idea would be to disable the button after a settlement request is made and show a tooltip indicating that a settlement has already been requested. I investigated with Claude, and seems like this should be possible with no backend changes. Here is what it said:

Backend already supports fixing this cleanly. QueueExpensifyCardForBilling sets private_expensifyCardManualBilling_{domainAccountID} to "true" via onyx data, and BillExpensifyCardDomain clears it back once the settlement actually runs. OpenPolicyExpensifyCardsPage already returns this NVP on every page load (see QueueExpensifyCardForBillingTest.cpp), so it's a reliable source of truth that survives refreshes.

Suggestion: disable the Settle balance button and show a tooltip (e.g. "Settlement already requested") whenever that onyx key is "true", and re-enable it once it clears. No backend changes needed, this data is already available, just not read on the client yet.

Separately, worth noting for anyone reviewing: queueExpensifyCardForBilling doesn't snapshot transactions, it just flags the domain. The actual transaction set is pulled by BillExpensifyCardDomain when the job runs (next business day), so any transactions posted between confirming and settlement will be included. That's expected/idempotent behavior, just flagging it since it wasn't obvious from the client code alone.

@shawnborton

Copy link
Copy Markdown
Contributor

Should the button still be there even if you have a $0.00 balance? Feels odd that it's there.

I don't love the idea of showing it but in a disabled state either. I think we recently had a similar thing come up for Travel Invoicing, and we landed to hide the button after settlement was initiated but add some small text like this (Figma):
CleanShot 2026-08-06 at 11 30 52@2x

cc @Expensify/design for thoughts.

@dannymcclain

Copy link
Copy Markdown
Contributor

Yeah I like following what we did for travel invoicing here.

@dannymcclain

Copy link
Copy Markdown
Contributor

Also, I don't think we want the Settle balance button to be red in the confirmation modal. I'd expect it to be green.

@shawnborton

Copy link
Copy Markdown
Contributor

Agree with that!

MelvinBot and others added 2 commits August 20, 2026 10:48
…onfirmation

Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
…ement is queued

Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Pulled main and addressed both comments. Pushed to claude-settleBalanceConfirmation.

What changed

  • Once a settlement is queued, the Settle balance button is now hidden and a message is shown in its place: "Payment of $xx.xx is queued and will be processed soon." — the exact copy you suggested, matching the Travel Invoicing pattern from the earlier discussion.
  • This mirrors WorkspaceTravelBillingSection: the UI keys off pendingSettlementAmount from the card settings — when it's > 0 the button is hidden and the queued message renders (WorkspaceCardsListLabel.tsx:126-127, message JSX).
  • queueExpensifyCardForBilling now sets pendingSettlementAmount optimistically (reverting on failure), so the button hides and the message appears immediately — same approach as payTravelBillingSpend (Card.ts:1700).
  • Added settleBalancePaymentQueued under workspace.expensifyCard in all 11 locales, reusing the already-translated Travel Billing copy.
Notes
  • The queued message uses the amount the backend surfaces in pendingSettlementAmount; the optimistic value seeds it from the current balance until the authoritative amount arrives.
  • One React Compiler adjustment was needed: reading the settings-derived pendingSettlementAmount in render tripped preserve-manual-memoization on the pre-existing isConnectedWithPlaid useMemo, so that manual memo was replaced with a plain computed value (React Compiler auto-memoizes it).

Checks run: typecheck-tsgo, lint-changed, react-compiler-compliance-check, and the relevant Jest suites (TravelBillingTest, TravelBillingUtilsTest, CardsSectionUtilsTest) all pass.

Testing note: the queued state only appears once the backend marks a settlement as pending, so — as you noted — exercising the live "queued" UI needs a real settlement transaction. The behavior in the normal (no-pending) state is unchanged from what's already on this PR.


view run

@OSBotify

Copy link
Copy Markdown
Contributor

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 9924abfb..b1a20537 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6040,7 +6040,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
             settleBalance: 'Saldo ausgleichen',
             settleBalanceConfirmationTitle: 'Saldo ausgleichen?',
             settleBalanceConfirmationPrompt:
-                'Dadurch wird dein aktueller Saldo am nächsten Werktag ausgeglichen. Nach erfolgreicher Abwicklung wird der Betrag deinem verbleibenden Limit wieder gutgeschrieben.',
+                'Dies wird Ihren aktuellen Saldo am nächsten Geschäftstag ausgleichen. Sobald dies erfolgreich ist, wird der Betrag wieder zu Ihrem verbleibenden Limit hinzugefügt.',
             settleBalancePaymentQueued: (amount: string) => `Die Zahlung über ${amount} ist in der Warteschlange und wird in Kürze bearbeitet.`,
             cardLimit: 'Kartenlimit',
             remaining: 'Verbleibend',
diff --git a/src/languages/el.ts b/src/languages/el.ts
index 3e8d265d..844e86bd 100644
--- a/src/languages/el.ts
+++ b/src/languages/el.ts
@@ -6162,8 +6162,9 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
             balanceWillBeSettledOn: (settlementDate: string) => `Το υπόλοιπο θα διακανονιστεί στις ${settlementDate}`,
             settleBalance: 'Εξόφληση υπολοίπου',
             settleBalanceConfirmationTitle: 'Εξόφληση υπολοίπου;',
-            settleBalanceConfirmationPrompt: 'Αυτό θα εξοφλήσει το τρέχον υπόλοιπό σας την επόμενη εργάσιμη ημέρα. Μόλις ολοκληρωθεί, το ποσό θα προστεθεί ξανά στο υπόλοιπο όριό σας.',
-            settleBalancePaymentQueued: (amount: string) => `Η πληρωμή ποσού ${amount} είναι σε αναμονή και θα διεκπεραιωθεί σύντομα.`,
+            settleBalanceConfirmationPrompt:
+                'Αυτό θα εξοφλήσει το τρέχον υπόλοιπό σας την επόμενη εργάσιμη ημέρα. Μόλις ολοκληρωθεί με επιτυχία, το ποσό θα προστεθεί ξανά στο διαθέσιμο όριό σας.',
+            settleBalancePaymentQueued: (amount: string) => `Η πληρωμή ποσού ${amount} έχει μπει σε ουρά και θα διεκπεραιωθεί σύντομα.`,
             cardLimit: 'Όριο κάρτας',
             remainingLimit: 'Υπόλοιπο ορίου',
             requestLimitIncrease: 'Αίτημα αύξησης ορίου',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 7aa00937..abcc64f9 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -5914,8 +5914,9 @@ ${amount} para ${merchant} - ${date}`,
                 'El saldo actual es la suma de todas las transacciones contabilizadas con la Tarjeta Expensify que se han producido desde la última fecha de liquidación.',
             balanceWillBeSettledOn: (settlementDate) => `El saldo se liquidará el ${settlementDate}.`,
             settleBalance: 'Liquidar saldo',
-            settleBalanceConfirmationTitle: '¿Liquidar saldo?',
-            settleBalanceConfirmationPrompt: 'Esto liquidará tu saldo actual el próximo día hábil. Una vez completado, el importe se sumará de nuevo a tu límite restante.',
+            settleBalanceConfirmationTitle: '¿Liquidar el saldo?',
+            settleBalanceConfirmationPrompt:
+                'Esto liquidará tu saldo actual el siguiente día hábil. Una vez que se complete correctamente, el importe se añadirá de nuevo a tu límite restante.',
             settleBalancePaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`,
             cardLimit: 'Límite de la tarjeta',
             remaining: 'Restante',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 6924574e..2d995ad7 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -6048,10 +6048,10 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
             currentBalanceDescription: 'Le solde actuel est la somme de toutes les transactions Carte Expensify comptabilisées depuis la dernière date de règlement.',
             balanceWillBeSettledOn: (settlementDate: string) => `Le solde sera réglé le ${settlementDate}`,
             settleBalance: 'Régler le solde',
-            settleBalanceConfirmationTitle: 'Régler le solde ?',
+            settleBalanceConfirmationTitle: 'Régler le solde ?',
             settleBalanceConfirmationPrompt:
-                'Cela réglera votre solde actuel le prochain jour ouvrable. Une fois l’opération réussie, le montant sera de nouveau ajouté à votre limite restante.',
-            settleBalancePaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera traité bientôt.`,
+                'Cela réglera votre solde actuel le jour ouvrable suivant. Une fois l’opération effectuée avec succès, le montant sera réajouté à votre plafond restant.',
+            settleBalancePaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera bientôt traité.`,
             cardLimit: 'Plafond de carte',
             remaining: 'Restant',
             remainingLimit: 'Plafond restant',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 269280a7..d42d5243 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6004,9 +6004,9 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
             currentBalanceDescription: 'Il saldo attuale è la somma di tutte le transazioni contabilizzate della Carta Expensify che si sono verificate dalla data dell’ultima liquidazione.',
             balanceWillBeSettledOn: (settlementDate: string) => `Il saldo sarà regolato il ${settlementDate}`,
             settleBalance: 'Saldo da saldare',
-            settleBalanceConfirmationTitle: 'Saldare il saldo?',
+            settleBalanceConfirmationTitle: 'Regolare il saldo?',
             settleBalanceConfirmationPrompt:
-                'Questa operazione salderà il tuo saldo attuale il giorno lavorativo successivo. Una volta completata, l’importo verrà aggiunto nuovamente al tuo limite rimanente.',
+                'Questo salderà il tuo saldo attuale nel giorno lavorativo successivo. Una volta andata a buon fine, l’importo verrà aggiunto di nuovo al tuo limite residuo.',
             settleBalancePaymentQueued: (amount: string) => `Il pagamento di ${amount} è in coda e verrà elaborato a breve.`,
             cardLimit: 'Limite carta',
             remaining: 'Rimanente',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index b52575ab..80c43b13 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -5939,9 +5939,9 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO
             currentBalanceDescription: '現在残高は、前回の精算日以降に発生し記帳されたすべての Expensify カード取引の合計です。',
             balanceWillBeSettledOn: (settlementDate: string) => `残高は${settlementDate}に精算されます`,
             settleBalance: '残高を清算',
-            settleBalanceConfirmationTitle: '残高を清算しますか?',
-            settleBalanceConfirmationPrompt: '現在の残高が翌営業日に清算されます。清算が完了すると、その金額は残りの利用限度額に戻されます。',
-            settleBalancePaymentQueued: (amount: string) => `${amount} の支払いはキューに登録されており、まもなく処理されます。`,
+            settleBalanceConfirmationTitle: '残高を精算しますか?',
+            settleBalanceConfirmationPrompt: 'この操作により、現在の残高は翌営業日に精算されます。完了すると、その金額は残りの利用可能枠に戻ります。',
+            settleBalancePaymentQueued: (amount: string) => `${amount} の支払いはキューに追加され、まもなく処理されます。`,
             cardLimit: 'カード上限',
             remaining: '残り',
             remainingLimit: '残りの上限',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index f9918292..483296b8 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -5996,9 +5996,8 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_
             currentBalanceDescription: 'Het huidige saldo is de som van alle geboekte Expensify Kaart-transacties die hebben plaatsgevonden sinds de laatste afwikkelingsdatum.',
             balanceWillBeSettledOn: (settlementDate: string) => `Saldo wordt vereffend op ${settlementDate}`,
             settleBalance: 'Saldo vereffenen',
-            settleBalanceConfirmationTitle: 'Saldo vereffenen?',
-            settleBalanceConfirmationPrompt:
-                'Hiermee wordt je huidige saldo op de volgende werkdag vereffend. Zodra dit is gelukt, wordt het bedrag weer aan je resterende limiet toegevoegd.',
+            settleBalanceConfirmationTitle: 'Saldo afrekenen?',
+            settleBalanceConfirmationPrompt: 'Dit zal je huidige saldo op de volgende werkdag vereffenen. Zodra dit is gelukt, wordt het bedrag weer toegevoegd aan je resterende limiet.',
             settleBalancePaymentQueued: (amount: string) => `Betaling van ${amount} staat in de wachtrij en wordt binnenkort verwerkt.`,
             cardLimit: 'Kaartlimiet',
             remaining: 'Resterend',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index b62f9e3b..cbf9dcd4 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -5979,8 +5979,8 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
             settleBalance: 'Ureguluj saldo',
             settleBalanceConfirmationTitle: 'Uregulować saldo?',
             settleBalanceConfirmationPrompt:
-                'Spowoduje to uregulowanie bieżącego salda w następnym dniu roboczym. Po pomyślnym zakończeniu kwota zostanie ponownie dodana do pozostałego limitu.',
-            settleBalancePaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i wkrótce zostanie przetworzona.`,
+                'To spłaci twoje bieżące saldo w następny dzień roboczy. Po pomyślnym zakończeniu kwota zostanie dodana z powrotem do twojego dostępnego limitu.',
+            settleBalancePaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i zostanie wkrótce przetworzona.`,
             cardLimit: 'Limit karty',
             remaining: 'Pozostało',
             remainingLimit: 'Pozostały limit',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 1fd85fb3..f603b73f 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -5996,8 +5996,9 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
             balanceWillBeSettledOn: (settlementDate: string) => `O saldo será liquidado em ${settlementDate}`,
             settleBalance: 'Quitar saldo',
             settleBalanceConfirmationTitle: 'Quitar saldo?',
-            settleBalanceConfirmationPrompt: 'Isso quitará seu saldo atual no próximo dia útil. Após a conclusão, o valor será adicionado novamente ao seu limite restante.',
-            settleBalancePaymentQueued: (amount: string) => `O pagamento de ${amount} está na fila e será processado em breve.`,
+            settleBalanceConfirmationPrompt:
+                'Isso vai quitar seu saldo atual no próximo dia útil. Quando for concluído com sucesso, o valor será adicionado de volta ao seu limite disponível.',
+            settleBalancePaymentQueued: (amount: string) => `O pagamento de ${amount} está em fila e será processado em breve.`,
             cardLimit: 'Limite do cartão',
             remaining: 'Restante',
             remainingLimit: 'Limite restante',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index c16487f8..ed64cdf3 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -5805,8 +5805,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
             balanceWillBeSettledOn: (settlementDate: string) => `余额将于 ${settlementDate} 结清`,
             settleBalance: '结清余额',
             settleBalanceConfirmationTitle: '结清余额?',
-            settleBalanceConfirmationPrompt: '这将在下一个工作日结清您的当前余额。成功后,该金额将重新计入您的剩余额度。',
-            settleBalancePaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,稍后将被处理。`,
+            settleBalanceConfirmationPrompt: '这笔款项将在下一个工作日结清您当前的余额。成功后,该金额将重新计入您的剩余额度。',
+            settleBalancePaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,即将处理。`,
             cardLimit: '卡片限额',
             remaining: '剩余',
             remainingLimit: '剩余额度',

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

View workflow run

@situchan

situchan commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@joekaufmanexpensify can you please run build and test with a live transaction?

pending text will look like this: See #97401 (comment)

test

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — set the PR body to the content you provided.


view run

@situchan

Copy link
Copy Markdown
Contributor

Actually, I overlooked. We already have this message: Balance will be settled on {date}

Screenshot 2026-08-20 at 10 16 00 PM

@MelvinBot revert 34be8b6 as it's not needed

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Reverted 34be8b6 — the existing Balance will be settled on {date} message already covers this, so the extra "payment queued" message and its optimistic update weren't needed.

Pushed the revert (6a058879e2) to claude-settleBalanceConfirmation. It cleanly undoes all of 34be8b6 (the WorkspaceCardsListLabel.tsx / Card.ts / language-file changes), leaving the PR back at just the confirmation-modal change.


view run

@situchan

Copy link
Copy Markdown
Contributor

I think we're good to go. #55607 (comment)

@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

I see. I thought there was no message based on the video farther up in the PR where it isn't shown. Is that only because the balance was $0 at that time?

@situchan

Copy link
Copy Markdown
Contributor

I see. I thought there was no message based on the video farther up in the PR where it isn't shown. Is that only because the balance was $0 at that time?

yes it's just mock test. No real transaction

@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

Okay, sounds good. I will need to run a real transaction to test this, which will take ~1 day to settle. If we're confident it's working now, I can just test on staging. QA probably won't be able to test this, so it will have to be internal QA

@situchan

Copy link
Copy Markdown
Contributor

@youssef-lr all yours

@joekaufmanexpensify joekaufmanexpensify changed the title Add confirmation before settling Expensify Card balance [internal QA] Add confirmation before settling Expensify Card balance Aug 21, 2026

@joekaufmanexpensify joekaufmanexpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for product.

@youssef-lr youssef-lr added the InternalQA This pull request required internal QA label Aug 27, 2026
@youssef-lr
youssef-lr merged commit 2ebad4f into main Aug 27, 2026
46 of 47 checks passed
@youssef-lr
youssef-lr deleted the claude-settleBalanceConfirmation branch August 27, 2026 02:15
@github-actions

Copy link
Copy Markdown
Contributor

🚧 youssef-lr has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/youssef-lr in version: 9.4.63-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Help site review

Yes — a docs update is required. This PR gates Settle balance behind a confirmation modal, and one help article documented the old one-click flow.

The affected article is How to Troubleshoot Expensify Card Issues, in the section on refreshing the workspace's Expensify Card limit. It said to click Settle Balance to trigger an early settlement in a single step — which no longer matches the app, and also used the wrong capitalization (the live label is Settle balance).

I opened a draft help site PR with the fix:

What changed in the docs

The steps now read:

  1. Go to Workspaces > [Workspace Name] > Expensify Card
  2. Click Settle balance.
  3. In the confirmation window, click Settle balance again to confirm the early settlement.
  4. Once the settlement clears, the workspace limit will be refreshed.

Copy verified against the merged strings in en.ts — button label settleBalance: 'Settle balance' and confirmation title settleBalanceConfirmationTitle: 'Settle balance?'.

@situchan, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review


view run

@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

Tested. Modal and message after balance was settled are working as expected.

image image

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/AndrewGable in version: 9.4.63-1 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

InternalQA This pull request required internal QA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants