Skip to content

[HOLD on App#96146][No QA] Show deposit-only settlement accounts for Consolidated Travel Billing#96997

Open
blimpich wants to merge 2 commits into
mainfrom
blimpich-ctbDepositOnlySettlementAccount
Open

[HOLD on App#96146][No QA] Show deposit-only settlement accounts for Consolidated Travel Billing#96997
blimpich wants to merge 2 commits into
mainfrom
blimpich-ctbDepositOnlySettlementAccount

Conversation

@blimpich

Copy link
Copy Markdown
Contributor

Explanation of Change

Split out of App #96146 so the deposit-only account selection can ship separately from the billing UI. Part of the Consolidated Travel Billing pay-by-invoice project (plan in the linked issue).

A small set of covenant customers can't be auto-debited for their monthly travel spend: they settle from a deposit-only bank account by paying an invoice by wire. This makes deposit-only settlement accounts selectable in the Consolidated Travel Billing enablement flow when the customer is configured for pay-by-invoice (invoiceTo present in their travel settings). Deposit-only options are labeled "Deposit only" in the account selector so they're distinguishable from withdrawal accounts. The server remains the authority (it accepts the deposit-only account only when the private_travelBillingSettlementMethod NVP is set); invoiceTo is the client-visible hint, since that private NVP never reaches Onyx.

Depends on the billing UI PR (App #96146) for the invoiceTo field and pay-by-invoice helper, hence the HOLD.

Fixed Issues

https://github.com/Expensify/Expensify/issues/657008 (partial — App portion; intentionally not using $ so the issue doesn't auto-close)
PROPOSAL:

Tests

Requires a dev backend with the Auth/Web pay-by-invoice changes and a validated deposit-only (personal) bank account.

  1. Set invoiceTo in the workspace's TRAVEL_US card settings (pay-by-invoice config).
  2. Open the workspace's Travel settings and start the Consolidated Travel Billing enablement flow.
  3. Open the settlement account selector.
  4. Verify the deposit-only bank account appears in the list with an "Account ending in NNNN • Deposit only" subtitle.
  5. Clear invoiceTo and verify the deposit-only account no longer appears in the selector.
  • Verify that no errors appear in the JS console

Offline tests

  1. Go offline.
  2. Verify the settlement account selector still renders the deposit-only account from cached data.

QA Steps

None: this UI is gated behind server-side pay-by-invoice configuration (invoiceTo), which doesn't exist on staging yet.

  • 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 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 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.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

– written by Claude on Ben's behalf

@blimpich
blimpich requested review from a team as code owners July 24, 2026 17:45
@melvin-bot
melvin-bot Bot requested review from joekaufmanexpensify and mjasikowski and removed request for a team July 24, 2026 17:45
@melvin-bot

melvin-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

@mjasikowski 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 July 24, 2026 17:46
@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 2b4c88ee9c3..eb0fbfed54d 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6105,6 +6105,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
                     invalidDateRangeError: 'Das Startdatum muss vor dem Enddatum liegen',
                     enabled: 'Zentrale Reiseabrechnung aktiviert!',
                     enabledDescription: 'Alle Reisekosten in diesem Workspace werden nun in einer monatlichen Rechnung zentralisiert.',
+                    depositOnly: 'Nur Einzahlung',
                 },
                 personalDetailsDescription: 'Um eine Reise zu buchen, gib bitte deinen amtlichen Namen genau so ein, wie er auf deinem amtlichen Ausweis steht.',
             },
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 293750179f1..c3a10a8675b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -5938,6 +5938,7 @@ ${amount} para ${merchant} - ${date}`,
                     invalidDateRangeError: 'La fecha de inicio debe ser anterior a la fecha de fin',
                     enabled: '¡Facturación de viajes consolidada habilitada!',
                     enabledDescription: 'Todos los gastos de viaje de este espacio de trabajo ahora se centralizarán en una factura mensual.',
+                    depositOnly: 'Solo depósito',
                 },
                 personalDetailsDescription: 'Para poder reservar el viaje, por favor ingrese su nombre legal tal como aparece en su identificación oficial emitida por el gobierno.',
             },
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 86ad445d9ff..2ed25021d26 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -6126,6 +6126,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
                     invalidDateRangeError: 'La date de début doit être antérieure à la date de fin',
                     enabled: 'Facturation de voyage consolidée activée !',
                     enabledDescription: 'Toutes les dépenses de voyage sur cet espace de travail seront désormais centralisées dans une facture mensuelle.',
+                    depositOnly: 'Dépôt uniquement',
                 },
                 personalDetailsDescription: 'Pour pouvoir réserver un voyage, veuillez saisir votre nom légal tel qu’il apparaît sur votre pièce d’identité délivrée par le gouvernement.',
             },
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 1647e4c0e87..edea5b12d37 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6088,6 +6088,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
                     invalidDateRangeError: 'La data di inizio deve essere precedente alla data di fine',
                     enabled: 'Fatturazione viaggio consolidata abilitata!',
                     enabledDescription: 'Tutte le spese di viaggio in questo spazio di lavoro saranno ora accentrate in una fattura mensile.',
+                    depositOnly: 'Solo deposito',
                 },
                 personalDetailsDescription: 'Per prenotare il viaggio, inserisci il tuo nome legale così come appare sul tuo documento d’identità rilasciato dal governo.',
             },
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 7a8dc0136b0..73616ab3921 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -6011,6 +6011,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO
                     invalidDateRangeError: '開始日は終了日より前でなければなりません',
                     enabled: '一括出張請求が有効になりました!',
                     enabledDescription: 'このワークスペースでの出張費用は、今後すべて月次の請求書に集約されます。',
+                    depositOnly: '入金専用',
                 },
                 personalDetailsDescription: '旅行を予約するために、政府発行の身分証明書に記載されているとおりの正式な氏名を入力してください。',
             },
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 43980fc8472..1a03d3a38b0 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6075,6 +6075,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_
                     invalidDateRangeError: 'De begindatum moet vóór de einddatum liggen',
                     enabled: 'Geconsolideerde reiskostenfacturatie ingeschakeld!',
                     enabledDescription: 'Alle reiskosten in deze workspace worden nu gebundeld op één maandelijkse factuur.',
+                    depositOnly: 'Alleen storting',
                 },
                 personalDetailsDescription: 'Om een reis te boeken, voer je wettelijke naam in zoals deze op je door de overheid uitgegeven identiteitsbewijs staat.',
             },
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 9b4bcdc9d1d..6d8ab41af54 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6055,6 +6055,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
                     invalidDateRangeError: 'Data początkowa musi być wcześniejsza niż data końcowa',
                     enabled: 'Włączono zbiorcze rozliczanie podróży!',
                     enabledDescription: 'Wszystkie wydatki podróżne w tym obszarze roboczym będą teraz scentralizowane na miesięcznym rachunku.',
+                    depositOnly: 'Tylko wpłata',
                 },
                 personalDetailsDescription: 'Aby zarezerwować podróż, wpisz swoje imię i nazwisko dokładnie tak, jak widnieje w Twoim dokumencie tożsamości wydanym przez organ państwowy.',
             },
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 4f1ee271622..73ca805765b 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6068,6 +6068,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
                     invalidDateRangeError: 'A data de início deve ser anterior à data de término',
                     enabled: 'Faturamento de viagem consolidado ativado!',
                     enabledDescription: 'Todos os gastos de viagem neste workspace agora serão centralizados em uma fatura mensal.',
+                    depositOnly: 'Somente depósito',
                 },
                 personalDetailsDescription: 'Para reservar viagens, insira seu nome legal exatamente como consta no documento de identificação emitido pelo governo.',
             },
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index eed8b03bf3c..ebca2ee128c 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -5875,6 +5875,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
                     invalidDateRangeError: '开始日期必须早于结束日期',
                     enabled: '已启用合并差旅账单!',
                     enabledDescription: '此工作区的所有差旅支出现在将统一汇总到一份月度账单中。',
+                    depositOnly: '仅限存入',
                 },
                 personalDetailsDescription: '为预订行程,请输入您在政府签发的身份证件上显示的法定姓名。',
             },

Note

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

View workflow run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 906001aeec

ℹ️ 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".

// Bank account eligibility for toggle handler
const isSetupUnfinished = hasInProgressUSDVBBA(reimbursementAccount?.achData);
const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList);
const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountList, isPayByInvoice);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Define the pay-by-invoice flag before using it

In this branch, isPayByInvoice is never declared or imported in this component, and the only added use of the pay-by-invoice helper in the settlement page imports a symbol that is not exported from TravelInvoicingUtils; as a result the Travel settings code fails to compile when this file is checked. Please derive this flag from travelSettings (and add/export the helper if needed) before passing it to getEligibleBankAccountsForTravelInvoicing.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants