App icon indicating copy to clipboard operation
App copied to clipboard

[$250] Wallet - Bank account icon still exists after deletion

Open nlemma opened this issue 2 weeks ago • 13 comments

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: v9.2.86-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/TR-2489/folder/13176687/41236662/1089920276?p=2 Email or phone of affected tester (no customers): N/A Issue reported by: Applause Internal Team Bug source: Regression TC Execution Device used: iPhone 14 / iOS 26.1 App Component: User Settings

Action Performed:

  1. Open the NewDot app
  2. Navigate the account settings
  3. Tap on the Wallet
  4. Tap on the Enable wallet button
  5. Complete adding the bank account with the Onfido verify step
  6. Tap on the three dots button on the bank account
  7. Select Delete -> Tap Delete on the confirm banner

Expected Result:

The bank account was removed completely

Actual Result:

The name disappears, but the icon of the bank account still exists

Workaround:

Unknown

Platforms:

  • [ ] Android: App
  • [ ] Android: mWeb Chrome
  • [x] iOS: App
  • [ ] iOS: mWeb Safari
  • [ ] iOS: mWeb Chrome
  • [x] Windows: Chrome
  • [x] MacOS: Chrome / Safari

Screenshots/Videos

https://github.com/user-attachments/assets/2cfe47bf-e1b0-499d-976f-3c12aea1acad

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022003808029537028224
  • Upwork Job ID: 2003808029537028224
  • Last Price Increase: 2025-12-24
Issue OwnerCurrent Issue Owner: @Pujan92

nlemma avatar Dec 23 '25 12:12 nlemma

Triggered auto assignment to @heyjennahay (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

melvin-bot[bot] avatar Dec 23 '25 12:12 melvin-bot[bot]

Proposal

Please re-state the problem that we are trying to solve in this issue.

When users delete a bank account from the Wallet in account settings, the bank account name disappears but the bank account icon still remains visible on screen.

What is the root cause of that problem?

The issue is in the formatPaymentMethods function in App/src/libs/PaymentUtils.ts which doesn't handle null entries in the bank account list.

How deletion works:

When a bank account is deleted, the Onyx store is updated in two phases:

  1. Optimistic Update (App/src/libs/actions/BankAccounts.ts lines 384-390):
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`,
                value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}},
            },
        ],
  1. Success Update (App/src/libs/actions/BankAccounts.ts lines 394-400):
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`,
                value: {[bankAccountID]: null},
            },
        ],

On successful deletion, the bank account entry is set to null in BANK_ACCOUNT_LIST.

The Problem - formatPaymentMethods (App/src/libs/PaymentUtils.ts lines 90-113):

function formatPaymentMethods(bankAccountList: Record<string, BankAccount>, fundList: Record<string, Fund> | Fund[], styles: ThemeStyles, translate: LocalizedTranslate): PaymentMethod[] {
    const combinedPaymentMethods: PaymentMethod[] = [];

    for (const bankAccount of Object.values(bankAccountList)) {
        // Add all bank accounts besides the wallet
        if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT_TYPES.WALLET) {
            continue;
        }

        const {icon, iconSize, iconHeight, iconWidth, iconStyles} = getBankIcon({
            bankName: bankAccount?.accountData?.additionalData?.bankName,
            isCard: false,
            styles,
        });
        combinedPaymentMethods.push({
            ...bankAccount,
            description: getPaymentMethodDescription(bankAccount?.accountType, bankAccount.accountData, translate, bankAccount.bankCurrency),
            icon,
            iconSize,
            iconHeight,
            iconWidth,
            iconStyles,
        });
    }
    // ... rest of function
}

The Bug Flow:

  1. Object.values(bankAccountList) includes null entries (deleted accounts)
  2. The loop iterates over these null entries
  3. For a null entry, bankAccount?.accountData?.type is undefined (not WALLET), so it doesn't skip
  4. getBankIcon is called with bankName: undefined

getBankIcon with undefined bankName (App/src/components/Icon/BankIcons/index.ts lines 20-31):

export default function getBankIcon({styles, bankName, isCard = false}: BankIconParams): BankIcon {
    const bankIcon: BankIcon = {
        icon: isCard ? GenericBankCard : GenericBank,
    };
    if (bankName) {
        const bankNameKey = getBankNameKey(bankName.toLowerCase());

        if (bankNameKey && bankNameKey in CONST.BANK_NAMES) {
            bankIcon.icon = (getBankIconAsset(bankNameKey, isCard) as BankIconAsset).default;
        }
    }
    // Returns GenericBank icon when bankName is undefined
}
  1. When bankName is undefined, getBankIcon returns the generic bank icon
  2. A payment method entry is created with the generic icon but no title/name (because bankAccount is null)
  3. The UI renders an icon with no text

What changes do you think we should make in order to solve the problem?

Add null/undefined checks at the start of both loops in formatPaymentMethods to skip deleted entries.

File to modify: App/src/libs/PaymentUtils.ts

Change 1 - Bank Accounts Loop (line 93):

for (const bankAccount of Object.values(bankAccountList)) {
    // Skip null/undefined entries (deleted accounts)
    if (!bankAccount) {
        continue;
    }

    // Add all bank accounts besides the wallet
    if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT_TYPES.WALLET) {
        continue;
    }
    // ... rest of loop
}

Change 2 - Fund List Loop (line 120):

for (const card of Object.values(fundList)) {
    // Skip null/undefined entries (deleted cards)
    if (!card) {
        continue;
    }

    const {icon, iconSize, iconHeight, iconWidth, iconStyles} = getBankIcon({bankName: card?.accountData?.bank, isCard: true, styles});
    // ... rest of loop
}

What alternative solutions did you explore? (Optional)

None

abbasifaizan70 avatar Dec 23 '25 13:12 abbasifaizan70

Job added to Upwork: https://www.upwork.com/jobs/~022003808029537028224

melvin-bot[bot] avatar Dec 24 '25 12:12 melvin-bot[bot]

Triggered auto assignment to Contributor-plus team member for initial proposal review - @Pujan92 (External)

melvin-bot[bot] avatar Dec 24 '25 12:12 melvin-bot[bot]

Note there might be delays on the Expensify side of things while people are out for the holidays

heyjennahay avatar Dec 24 '25 12:12 heyjennahay

@abbasifaizan70 your RCA makes sense but are you able to reproduce this? I tried on the web but was unable to reproduce it(haven't tried on native yet)

Pujan92 avatar Dec 24 '25 12:12 Pujan92

@Pujan92 i am not able to add bank.

abbasifaizan70 avatar Dec 24 '25 22:12 abbasifaizan70

You can use this comment to add a bank account.

Pujan92 avatar Dec 25 '25 13:12 Pujan92

Please re-state the problem that we are trying to solve in this issue

Deleting a wallet bank account removes the name but leaves its icon visible in the Wallet list.

What is the root cause of that problem?

The payment method builder includes bank accounts even when their data is empty or their status is marked as DELETED. This results in entries that only display an icon. The logic currently adds every bank account without excluding deleted ones.

What changes do you think we should make in order to solve the problem?

Ignore bank accounts that do not have accountData or are marked as DELETED before adding them to the payment method list. This ensures removed accounts fully disappear and do not leave icons behind.

Updated code:

// src/libs/PaymentUtils.ts
for (const bankAccount of Object.values(bankAccountList)) {
    if (!bankAccount?.accountData || bankAccount.accountData.state === CONST.BANK_ACCOUNT.STATE.DELETED) {
        continue;
    }

    if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT_TYPES.WALLET) {
        continue;
    }

    const {icon, iconSize, iconHeight, iconWidth, iconStyles} = getBankIcon({
        bankName: bankAccount?.accountData?.additionalData?.bankName,
        isCard: false,
        styles,
    });

    combinedPaymentMethods.push({
        ...bankAccount,
        description: getPaymentMethodDescription(
            bankAccount?.accountType,
            bankAccount.accountData,
            translate,
            bankAccount.bankCurrency,
        ),
        icon,
        iconSize,
        iconHeight,
        iconWidth,
        iconStyles,
    });
}

ShridharGoel avatar Dec 25 '25 14:12 ShridharGoel

⚠️ @ShridharGoel Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.

github-actions[bot] avatar Dec 25 '25 14:12 github-actions[bot]

@Pujan92, can you please add my email to the Expensify Slack, as I am unable to access the above Slack chat?

[email protected]

abbasifaizan70 avatar Dec 25 '25 15:12 abbasifaizan70

You can request to join in the slack group. @heyjennahay can help you to allow access.

Pujan92 avatar Dec 25 '25 15:12 Pujan92

You can request to join in the slack group. @heyjennahay can help you to allow access.

I filled out the form to join Expensify Slack, but I didn't receive any reply. So waiting for response.Thanks for helping @Pujan92

abbasifaizan70 avatar Dec 25 '25 15:12 abbasifaizan70

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

melvin-bot[bot] avatar Dec 31 '25 16:12 melvin-bot[bot]

@Pujan92 Still overdue 6 days?! Let's take care of this!

melvin-bot[bot] avatar Jan 02 '26 00:01 melvin-bot[bot]

@nlemma is it still reproducible for you?

I am not able to bypass onfido screen yet.

Pujan92 avatar Jan 05 '26 14:01 Pujan92

@Pujan92 the issue is still reproducible.

https://github.com/user-attachments/assets/02a50172-684c-4ced-81c6-609d18e95e24

nlemma avatar Jan 06 '26 09:01 nlemma

@nlemma can you add me to slack please. Here my email [email protected]

abbasifaizan70 avatar Jan 06 '26 10:01 abbasifaizan70

Thanks @nlemma , I am able to reproduce with the enabled wallet.

Pujan92 avatar Jan 06 '26 12:01 Pujan92

@heyjennahay this looks like a BE issue as DeletePaymentBankAccount api responding the incorrect data which causes this issue. On reopening the page(OpenPaymentsPage) it sets the data correctly.

Let's add an internal label here so they can investigate this.

https://github.com/user-attachments/assets/84eae3f3-8119-4f86-aaae-56cad5fd09bd

Pujan92 avatar Jan 06 '26 12:01 Pujan92

Thanks @Pujan92 issue label updated!

heyjennahay avatar Jan 06 '26 17:01 heyjennahay

@heyjennahay @Pujan92 this issue was created 2 weeks ago. Are we close to a solution? Let's make sure we're treating this as a top priority. Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

melvin-bot[bot] avatar Jan 06 '26 21:01 melvin-bot[bot]