From caff44b81cd940d7ab59f31e8a7e2482d8408060 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 4 Mar 2026 15:20:37 +0100 Subject: [PATCH 1/6] Add tests for useTimeSensitiveBilling hook --- src/libs/SubscriptionUtils.ts | 1 + .../hooks/useTimeSensitiveBilling.ts | 7 + .../hooks/useTimeSensitiveBilling.test.ts | 189 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts create mode 100644 tests/unit/hooks/useTimeSensitiveBilling.test.ts diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index e8165b2feff1..1b8db107f4c3 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -644,6 +644,7 @@ export { getSubscriptionPrice, shouldShowTrialEndedUI, isSubscriptionTypeOfInvoicing, + hasInsufficientFundsError, }; export type {SubscriptionPlanIllustrations}; diff --git a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts new file mode 100644 index 000000000000..7b440edc107d --- /dev/null +++ b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts @@ -0,0 +1,7 @@ +function useTimeSensitiveBilling() { + return { + shouldShowFixFailedBilling: false, + }; +} + +export default useTimeSensitiveBilling; diff --git a/tests/unit/hooks/useTimeSensitiveBilling.test.ts b/tests/unit/hooks/useTimeSensitiveBilling.test.ts new file mode 100644 index 000000000000..c30e678ad12d --- /dev/null +++ b/tests/unit/hooks/useTimeSensitiveBilling.test.ts @@ -0,0 +1,189 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {renderHook} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import {hasCardExpiredError, hasInsufficientFundsError} from '@libs/SubscriptionUtils'; +import useTimeSensitiveBilling from '@pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling'; +import ONYXKEYS from '@src/ONYXKEYS'; +import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; + +jest.mock('@libs/SubscriptionUtils', () => ({ + hasCardExpiredError: jest.fn(() => false), + hasInsufficientFundsError: jest.fn(() => false), +})); + +const mockedHasCardExpiredError = hasCardExpiredError as jest.Mock; +const mockedHasInsufficientFundsError = hasInsufficientFundsError as jest.Mock; + +describe('useTimeSensitiveBilling', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + }); + + beforeEach(async () => { + await Onyx.clear(); + await waitForBatchedUpdates(); + jest.clearAllMocks(); + mockedHasCardExpiredError.mockReturnValue(false); + mockedHasInsufficientFundsError.mockReturnValue(false); + }); + + afterEach(async () => { + await Onyx.clear(); + }); + + describe('when failed billing should NOT be shown', () => { + it('returns false when amountOwed is 0', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 0); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'insufficient_funds', + }); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(false); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(false); + }); + + it('returns false when billingStatus has no decline reason', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 100); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(false); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(false); + }); + + it('returns false when declineReason is present but hasPurchases is false', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 100); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'insufficient_funds', + }); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: false}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(true); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(false); + }); + + it('returns false when hasPurchases is true but no billing error', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 100); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(false); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(false); + }); + + it('returns false when all Onyx values are undefined/empty', () => { + mockedHasInsufficientFundsError.mockReturnValue(false); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(false); + }); + }); + + describe('when failed billing SHOULD be shown', () => { + it('returns true for insufficient_funds with amountOwed > 0 and hasPurchases', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 500); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'insufficient_funds', + }); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(true); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(true); + }); + + it('returns true for expired_card with amountOwed > 0 and hasPurchases', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 250); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'expired_card', + }); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasCardExpiredError.mockReturnValue(true); + + const {result} = renderHook(() => useTimeSensitiveBilling()); + + expect(result.current.shouldShowFixFailedBilling).toBe(true); + }); + }); + + describe('reactivity to Onyx changes', () => { + it('updates when billing status changes', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 100); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + + mockedHasInsufficientFundsError.mockReturnValue(false); + + const {result, rerender} = renderHook(() => useTimeSensitiveBilling()); + expect(result.current.shouldShowFixFailedBilling).toBe(false); + + mockedHasInsufficientFundsError.mockReturnValue(true); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'insufficient_funds', + }); + await waitForBatchedUpdates(); + rerender({}); + + expect(result.current.shouldShowFixFailedBilling).toBe(true); + }); + + it('updates when account.hasPurchases changes', async () => { + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED, 100); + await Onyx.merge(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS, { + action: 'action', + periodMonth: '01', + periodYear: '2026', + declineReason: 'expired_card', + }); + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: false}); + await waitForBatchedUpdates(); + + mockedHasCardExpiredError.mockReturnValue(true); + + const {result, rerender} = renderHook(() => useTimeSensitiveBilling()); + expect(result.current.shouldShowFixFailedBilling).toBe(false); + + await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); + await waitForBatchedUpdates(); + rerender({}); + + expect(result.current.shouldShowFixFailedBilling).toBe(true); + }); + }); +}); From ba9c231755109b056db268ff8f5cdd75a1985325 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 5 Mar 2026 15:30:14 +0100 Subject: [PATCH 2/6] Add translations --- src/languages/en.ts | 4 ++++ src/languages/es.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9edf6ea39d71..bef6ad125139 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1039,6 +1039,10 @@ const translations = { subtitle: 'Account', cta: 'Validate', }, + fixFailedBilling: { + title: "We couldn't bill your card on file", + subtitle: 'Subscription', + }, }, assignedCards: 'Your Expensify Cards', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} remaining`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 656b3a881a86..ec762fcde330 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -908,6 +908,10 @@ const translations: TranslationDeepObject = { subtitle: 'Cuenta', cta: 'Validar', }, + fixFailedBilling: { + title: 'No pudimos cobrar a la tarjeta registrada.', + subtitle: 'Suscripción', + }, }, assignedCards: 'Tus tarjetas Expensify', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restantes`, From 09b6a4cdee5b34cabab5f3b138fabce9159e4f8c Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 5 Mar 2026 15:45:23 +0100 Subject: [PATCH 3/6] Add FixFailedBilling widget to TimeSensitiveSection for declined billing cards --- src/libs/SubscriptionUtils.ts | 1 + .../hooks/useTimeSensitiveBilling.ts | 12 +++++- src/pages/home/TimeSensitiveSection/index.tsx | 39 +++++++++++-------- .../items/FixFailedBilling.tsx | 26 +++++++++++++ 4 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 src/pages/home/TimeSensitiveSection/items/FixFailedBilling.tsx diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 1b8db107f4c3..0254033e5c04 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -628,6 +628,7 @@ export { getFreeTrialText, getSubscriptionStatus, hasCardAuthenticatedError, + hasCardExpiredError, hasGracePeriodOverdue, hasRetryBillingError, hasSubscriptionGreenDotInfo, diff --git a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts index 7b440edc107d..3d2ddf361c50 100644 --- a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts +++ b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts @@ -1,6 +1,16 @@ +import useOnyx from '@hooks/useOnyx'; +import {hasCardExpiredError, hasInsufficientFundsError} from '@libs/SubscriptionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; + function useTimeSensitiveBilling() { + const [billingStatus] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + + const hasBillingError = hasCardExpiredError(billingStatus) || hasInsufficientFundsError(billingStatus); + const shouldShowFixFailedBilling = hasBillingError && account?.hasPurchases === true; + return { - shouldShowFixFailedBilling: false, + shouldShowFixFailedBilling, }; } diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index 537e583d0aa0..ff0590e9486e 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -17,6 +17,7 @@ import {isCurrentUserValidated} from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; import type {ConnectionName, PolicyConnectionName} from '@src/types/onyx/Policy'; +import useTimeSensitiveBilling from './hooks/useTimeSensitiveBilling'; import useTimeSensitiveCards from './hooks/useTimeSensitiveCards'; import useTimeSensitiveOffers from './hooks/useTimeSensitiveOffers'; import ActivateCard from './items/ActivateCard'; @@ -24,6 +25,7 @@ import AddPaymentCard from './items/AddPaymentCard'; import AddShippingAddress from './items/AddShippingAddress'; import FixAccountingConnection from './items/FixAccountingConnection'; import FixCompanyCardConnection from './items/FixCompanyCardConnection'; +import FixFailedBilling from './items/FixFailedBilling'; import FixPersonalCardConnection from './items/FixPersonalCardConnection'; import Offer25off from './items/Offer25off'; import Offer50off from './items/Offer50off'; @@ -67,6 +69,7 @@ function TimeSensitiveSection() { // Use custom hooks for offers and cards (Release 3) const {shouldShow50off, shouldShow25off, shouldShowAddPaymentCard, firstDayFreeTrial, discountInfo} = useTimeSensitiveOffers(); const {shouldShowAddShippingAddress, shouldShowActivateCard, shouldShowReviewCardFraud, cardsNeedingShippingAddress, cardsNeedingActivation, cardsWithFraud} = useTimeSensitiveCards(); + const {shouldShowFixFailedBilling} = useTimeSensitiveBilling(); // Selector for filtering admin policies (Release 4) const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''), [login]); @@ -152,6 +155,7 @@ function TimeSensitiveSection() { // must be reflected here to avoid showing an empty "Time sensitive" section. const hasAnyTimeSensitiveContent = shouldShowValidateAccount || + shouldShowFixFailedBilling || shouldShowReviewCardFraud || shouldShowAddPaymentCard || shouldShow50off || @@ -168,21 +172,24 @@ function TimeSensitiveSection() { // Priority order: // 1. Validate account - // 2. Potential card fraud - // 3. Add payment card (trial ended, no payment card) - // 4. Broken bank connections (company cards) - // 5. Broken bank connections (personal cards) - // 6. Broken accounting connections - // 7. Early adoption discount (50% or 25%) - // 8. Expensify card shipping - // 9. Expensify card activation + // 2. Fix failed billing (existing customers with declined cards) + // 3. Potential card fraud + // 4. Add payment card (trial ended, no payment card) + // 5. Broken bank connections (company cards) + // 6. Broken bank connections (personal cards) + // 7. Broken accounting connections + // 8. Early adoption discount (50% or 25%) + // 9. Expensify card shipping + // 10. Expensify card activation return ( {/* Priority 1: Validate account */} {shouldShowValidateAccount && } + {/* Priority 2: Failed billing for existing customers */} + {!!shouldShowFixFailedBilling && } - {/* Priority 2: Card fraud alerts */} + {/* Priority 3: Card fraud alerts */} {shouldShowReviewCardFraud && cardsWithFraud.map((card) => { if (!card.nameValuePairs?.possibleFraud) { @@ -196,9 +203,9 @@ function TimeSensitiveSection() { ); })} - {/* Priority 3: Add payment card (trial ended, no payment card) */} + {/* Priority 4: Add payment card (trial ended, no payment card) */} {shouldShowAddPaymentCard && } - {/* Priority 4: Broken company card connections */} + {/* Priority 5: Broken company card connections */} {brokenCompanyCardConnections.map((connection) => { const card = cardFeedErrors.cardsWithBrokenFeedConnection[connection.cardID]; if (!card) { @@ -214,7 +221,7 @@ function TimeSensitiveSection() { ); })} - {/* Priority 5: Broken personal card connections */} + {/* Priority 6: Broken personal card connections */} {brokenPersonalCardConnections.map((connection) => { const card = cardFeedErrors.personalCardsWithBrokenConnection[connection.cardID]; if (!card) { @@ -228,7 +235,7 @@ function TimeSensitiveSection() { ); })} - {/* Priority 6: Broken accounting connections */} + {/* Priority 7: Broken accounting connections */} {brokenAccountingConnections.map((connection) => ( ))} - {/* Priority 7: Early adoption discount offers */} + {/* Priority 8: Early adoption discount offers */} {shouldShow50off && } {shouldShow25off && !!discountInfo && } - {/* Priority 8: Expensify card shipping */} + {/* Priority 9: Expensify card shipping */} {shouldShowAddShippingAddress && cardsNeedingShippingAddress.map((card) => ( ))} - {/* Priority 9: Expensify card activation */} + {/* Priority 10: Expensify card activation */} {shouldShowActivateCard && cardsNeedingActivation.map((card) => ( + ); +} + +export default FixFailedBilling; From 14a33052b2ac4a61aa308652966b697a0a42ff08 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 6 Mar 2026 13:19:32 +0100 Subject: [PATCH 4/6] Subscribe to NVP_PRIVATE_AMOUNT_OWED in useTimeSensitiveBilling to ensure re-renders on balance changes --- .../hooks/useTimeSensitiveBilling.ts | 3 ++- tests/unit/hooks/useTimeSensitiveBilling.test.ts | 10 +--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts index 3d2ddf361c50..3b9c2f5684ab 100644 --- a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts +++ b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts @@ -5,8 +5,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; function useTimeSensitiveBilling() { const [billingStatus] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS); const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); - const hasBillingError = hasCardExpiredError(billingStatus) || hasInsufficientFundsError(billingStatus); + const hasBillingError = !!amountOwed && (hasCardExpiredError(billingStatus) || hasInsufficientFundsError(billingStatus)); const shouldShowFixFailedBilling = hasBillingError && account?.hasPurchases === true; return { diff --git a/tests/unit/hooks/useTimeSensitiveBilling.test.ts b/tests/unit/hooks/useTimeSensitiveBilling.test.ts index c30e678ad12d..265d303b9b37 100644 --- a/tests/unit/hooks/useTimeSensitiveBilling.test.ts +++ b/tests/unit/hooks/useTimeSensitiveBilling.test.ts @@ -43,7 +43,7 @@ describe('useTimeSensitiveBilling', () => { await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); await waitForBatchedUpdates(); - mockedHasInsufficientFundsError.mockReturnValue(false); + mockedHasInsufficientFundsError.mockReturnValue(true); const {result} = renderHook(() => useTimeSensitiveBilling()); @@ -55,8 +55,6 @@ describe('useTimeSensitiveBilling', () => { await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); await waitForBatchedUpdates(); - mockedHasInsufficientFundsError.mockReturnValue(false); - const {result} = renderHook(() => useTimeSensitiveBilling()); expect(result.current.shouldShowFixFailedBilling).toBe(false); @@ -85,16 +83,12 @@ describe('useTimeSensitiveBilling', () => { await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); await waitForBatchedUpdates(); - mockedHasInsufficientFundsError.mockReturnValue(false); - const {result} = renderHook(() => useTimeSensitiveBilling()); expect(result.current.shouldShowFixFailedBilling).toBe(false); }); it('returns false when all Onyx values are undefined/empty', () => { - mockedHasInsufficientFundsError.mockReturnValue(false); - const {result} = renderHook(() => useTimeSensitiveBilling()); expect(result.current.shouldShowFixFailedBilling).toBe(false); @@ -145,8 +139,6 @@ describe('useTimeSensitiveBilling', () => { await Onyx.merge(ONYXKEYS.ACCOUNT, {hasPurchases: true}); await waitForBatchedUpdates(); - mockedHasInsufficientFundsError.mockReturnValue(false); - const {result, rerender} = renderHook(() => useTimeSensitiveBilling()); expect(result.current.shouldShowFixFailedBilling).toBe(false); From ac495092331b0b384cea27d2416046c0a44c9934 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Fri, 6 Mar 2026 13:40:10 +0100 Subject: [PATCH 5/6] Fix useTimeSensitiveBilling --- .../home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts index 3b9c2f5684ab..ca172f6da0c1 100644 --- a/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts +++ b/src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveBilling.ts @@ -7,7 +7,7 @@ function useTimeSensitiveBilling() { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); - const hasBillingError = !!amountOwed && (hasCardExpiredError(billingStatus) || hasInsufficientFundsError(billingStatus)); + const hasBillingError = !!amountOwed && (hasCardExpiredError(billingStatus, amountOwed) || hasInsufficientFundsError(billingStatus, amountOwed)); const shouldShowFixFailedBilling = hasBillingError && account?.hasPurchases === true; return { From 5a5fb57b55362c980e7be384b14871d33676b91a Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Mon, 9 Mar 2026 14:06:40 +0100 Subject: [PATCH 6/6] Add translations --- src/languages/de.ts | 1 + src/languages/fr.ts | 1 + 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 + 8 files changed, 8 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 87d3cb0c0bd5..e390c56bf9cb 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -995,6 +995,7 @@ const translations: TranslationDeepObject = { subtitle: 'Wallet', }, validateAccount: {title: 'Bestätigen Sie Ihr Konto, um Expensify weiter zu verwenden', subtitle: 'Konto', cta: 'Bestätigen'}, + fixFailedBilling: {title: 'Wir konnten Ihre hinterlegte Karte nicht belasten', subtitle: 'Abonnement'}, }, assignedCards: 'Ihre Expensify Karten', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} verbleibend`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 998d373ecb99..e77aecd13b14 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -998,6 +998,7 @@ const translations: TranslationDeepObject = { subtitle: 'Portefeuille', }, validateAccount: {title: 'Validez votre compte pour continuer à utiliser Expensify', subtitle: 'Compte', cta: 'Valider'}, + fixFailedBilling: {title: 'Nous n’avons pas pu débiter votre carte enregistrée', subtitle: 'Abonnement'}, }, assignedCards: 'Vos cartes Expensify', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restant`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 3848fd44d8f5..a6b7bf05d7dd 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -995,6 +995,7 @@ const translations: TranslationDeepObject = { subtitle: 'Portafoglio', }, validateAccount: {title: 'Conferma il tuo account per continuare a usare Expensify', subtitle: 'Account', cta: 'Conferma'}, + fixFailedBilling: {title: 'Non abbiamo potuto addebitare la carta salvata nel profilo', subtitle: 'Abbonamento'}, }, assignedCards: 'Le tue Carte Expensify', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} rimanenti`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index f42d9379d14e..163b0ae20c36 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -978,6 +978,7 @@ const translations: TranslationDeepObject = { }, fixPersonalCardConnection: {title: ({cardName}: {cardName?: string}) => (cardName ? `${cardName}個人カードの接続を修正` : '個人カードの連携を修正'), subtitle: 'ウォレット'}, validateAccount: {title: 'Expensify を引き続きご利用いただくには、アカウントを認証してください', subtitle: 'アカウント', cta: '検証する'}, + fixFailedBilling: {title: '登録されているカードから請求できませんでした', subtitle: 'サブスクリプション'}, }, assignedCards: 'お客様の Expensify カード', assignedCardsRemaining: ({amount}: {amount: string}) => `残額:${amount}`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9d68c80a7d44..60af47f87dda 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -994,6 +994,7 @@ const translations: TranslationDeepObject = { subtitle: 'Portemonnee', }, validateAccount: {title: 'Valideer je account om Expensify te blijven gebruiken', subtitle: 'Account', cta: 'Valideren'}, + fixFailedBilling: {title: 'We konden je kaart in ons bestand niet belasten', subtitle: 'Abonnement'}, }, assignedCards: 'Je Expensify Kaarten', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} resterend`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index af2bf985657e..e124cb69b1c3 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -995,6 +995,7 @@ const translations: TranslationDeepObject = { subtitle: 'Portfel', }, validateAccount: {title: 'Zweryfikuj swoje konto, aby dalej korzystać z Expensify', subtitle: 'Konto', cta: 'Zatwierdź'}, + fixFailedBilling: {title: 'Nie mogliśmy obciążyć zapisanej karty', subtitle: 'Subskrypcja'}, }, assignedCards: 'Twoje Karty Expensify', assignedCardsRemaining: ({amount}: {amount: string}) => `Pozostało ${amount}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f90152259181..f5cba89e5792 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -993,6 +993,7 @@ const translations: TranslationDeepObject = { subtitle: 'Carteira', }, validateAccount: {title: 'Valide sua conta para continuar usando o Expensify', subtitle: 'Conta', cta: 'Validar'}, + fixFailedBilling: {title: 'Não foi possível cobrar o cartão cadastrado', subtitle: 'Assinatura'}, }, assignedCards: 'Seus Cartões Expensify', assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restante`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 779d1f8e5ed3..df85754b5995 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -961,6 +961,7 @@ const translations: TranslationDeepObject = { subtitle: ({policyName}: {policyName: string}) => `${policyName} > 会计`, }, validateAccount: {title: '验证您的账户以继续使用 Expensify', subtitle: '账户', cta: '验证'}, + fixFailedBilling: {title: '我们无法向您档案中的银行卡收费', subtitle: '订阅'}, }, assignedCards: '你的 Expensify 卡', assignedCardsRemaining: ({amount}: {amount: string}) => `剩余 ${amount}`,