Skip to main content

Overview

Expensify supports connecting both personal and business bank accounts for sending and receiving payments. This guide covers the complete process of connecting accounts via Plaid or manual entry.

Personal Bank Accounts

Opening Setup Flow

Initiate the personal bank account setup with optional parameters for routing.
type OpenPersonalBankAccountSetupViewProps = {
    /** The reportID of the report to redirect to once the flow is finished */
    exitReportID?: string;

    /** The policyID of the policy to set the bank account on */
    policyID?: string;

    /** The source of the bank account */
    source?: string;

    /** Whether to set up a US bank account */
    shouldSetUpUSBankAccount?: boolean;

    /** Whether the user is validated */
    isUserValidated?: boolean;
};

function openPersonalBankAccountSetupView({
    exitReportID,
    policyID,
    source,
    shouldSetUpUSBankAccount = false,
    isUserValidated = true
}: OpenPersonalBankAccountSetupViewProps) {
    clearInternationalBankAccount().then(() => {
        if (exitReportID) {
            Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID});
        }
        if (policyID) {
            Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {policyID});
        }
        if (source) {
            Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {source});
        }
        if (!isUserValidated) {
            Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.ADD_BANK_ACCOUNT_VERIFY_ACCOUNT.path));
            return;
        }
        if (shouldSetUpUSBankAccount) {
            Navigation.navigate(ROUTES.SETTINGS_ADD_US_BANK_ACCOUNT);
            return;
        }
        Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT.getRoute(Navigation.getActiveRoute()));
    });
}

Connecting via Plaid

Connect a bank account using Plaid for secure, instant verification.
function connectBankAccountWithPlaid(
    bankAccountID: number,
    selectedPlaidBankAccount: PlaidBankAccount,
    policyID: string
) {
    const parameters: ConnectBankAccountParams = {
        bankAccountID,
        routingNumber: selectedPlaidBankAccount.routingNumber,
        accountNumber: selectedPlaidBankAccount.accountNumber,
        bank: selectedPlaidBankAccount.bankName,
        plaidAccountID: selectedPlaidBankAccount.plaidAccountID,
        plaidAccessToken: selectedPlaidBankAccount.plaidAccessToken,
        plaidMask: selectedPlaidBankAccount.mask,
        isSavings: selectedPlaidBankAccount.isSavings,
        policyID,
    };

    API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID, parameters, getVBBADataForOnyx());
}

Manual Bank Account Entry

For cases where Plaid is unavailable, allow manual entry of bank details.
function connectBankAccountManually(
    bankAccountID: number,
    bankAccount: PlaidBankAccount,
    policyID: string
) {
    const parameters: ConnectBankAccountParams = {
        bankAccountID,
        routingNumber: bankAccount.routingNumber,
        accountNumber: bankAccount.accountNumber,
        bank: bankAccount.bankName,
        plaidAccountID: bankAccount.plaidAccountID,
        plaidAccessToken: bankAccount.plaidAccessToken,
        plaidMask: bankAccount.mask,
        isSavings: bankAccount.isSavings,
        policyID,
    };

    API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_MANUALLY, parameters, getVBBADataForOnyx());
}

Adding Personal Bank Account

Complete the process of adding a personal bank account with all required information.
function addPersonalBankAccount(
    account: Partial<PlaidBankAccount & PersonalBankAccountForm>,
    personalPolicyID: string | undefined,
    policyID?: string,
    source?: string,
    lastPaymentMethod?: LastPaymentMethodType | string | undefined,
) {
    const parameters: AddPersonalBankAccountParams = {
        addressName: account?.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL 
            ? `${account?.legalFirstName} ${account?.legalLastName}` 
            : account.addressName,
        routingNumber: account?.routingNumber,
        accountNumber: account?.accountNumber,
        isSavings: account.isSavings ?? false,
        setupType: account?.setupType,
        bank: account?.bankName,
        plaidAccountID: account?.plaidAccountID,
        plaidAccessToken: account?.plaidAccessToken,
        phoneNumber: account?.phoneNumber,
        legalFirstName: account?.legalFirstName,
        legalLastName: account?.legalLastName,
        addressStreet: getFormattedStreet(account?.addressStreet, account?.addressStreet2),
        addressCity: account?.addressCity,
        addressState: account?.addressState,
        addressZip: account?.addressZipCode,
        addressCountry: account?.country,
    };
    
    if (policyID) {
        parameters.policyID = policyID;
    }
    if (source) {
        parameters.source = source;
    }

    const onyxData: OnyxData<typeof ONYXKEYS.PERSONAL_BANK_ACCOUNT | typeof ONYXKEYS.USER_WALLET | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD> = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
                value: {
                    isLoading: true,
                    errors: null,
                    plaidAccountID: account.plaidAccountID,
                },
            },
        ],
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
                value: {
                    isLoading: false,
                    errors: null,
                    shouldShowSuccess: true,
                },
            },
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.USER_WALLET,
                value: {
                    currentStep: CONST.WALLET.STEP.ADDITIONAL_DETAILS,
                },
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
                value: {
                    isLoading: false,
                    errors: getMicroSecondOnyxErrorWithTranslationKey('walletPage.addBankAccountFailure'),
                },
            },
        ],
    };

    API.write(WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT, parameters, onyxData);
}

Business Bank Accounts (Reimbursement Accounts)

Opening Reimbursement Account Page

Fetch and display data for the reimbursement account setup process.
type OpenReimbursementAccountPageActionParams = {
    stepToOpen?: ReimbursementAccountStep;
    subStep?: ReimbursementAccountSubStep;
    localCurrentStep?: ReimbursementAccountStep;
    policyID?: string;
    bankAccountID?: number;
    shouldPreserveDraft?: boolean;
};

function openReimbursementAccountPage({
    stepToOpen = '',
    subStep = '',
    localCurrentStep = '',
    policyID,
    bankAccountID,
    shouldPreserveDraft
}: OpenReimbursementAccountPageActionParams) {
    const onyxData: OnyxData<typeof ONYXKEYS.REIMBURSEMENT_ACCOUNT> = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: true,
                },
            },
        ],
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: false,
                },
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: false,
                },
            },
        ],
    };

    const parameters: OpenReimbursementAccountPageParams = {
        stepToOpen,
        subStep,
        localCurrentStep,
        policyID,
        bankAccountID,
        shouldPreserveDraft,
    };

    return API.read(READ_COMMANDS.OPEN_REIMBURSEMENT_ACCOUNT_PAGE, parameters, onyxData);
}

Updating Company Information

Submit business information during the bank account verification process.
function updateCompanyInformationForBankAccount(
    bankAccountID: number,
    params: Partial<CompanyStepProps>,
    policyID: string | undefined,
    isConfirmPage: boolean
) {
    API.write(
        WRITE_COMMANDS.UPDATE_COMPANY_INFORMATION_FOR_BANK_ACCOUNT,
        {
            ...params,
            bankAccountID,
            policyID,
            confirm: isConfirmPage,
        },
        getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.COMPANY, isConfirmPage),
    );
}

Updating Personal Information

Submit requestor personal details for bank account verification.
function updatePersonalInformationForBankAccount(
    bankAccountID: number,
    params: RequestorStepProps,
    policyID: string | undefined,
    isConfirmPage: boolean
) {
    API.write(
        WRITE_COMMANDS.UPDATE_PERSONAL_INFORMATION_FOR_BANK_ACCOUNT,
        {
            ...params,
            bankAccountID,
            policyID,
            confirm: isConfirmPage,
        },
        getVBBADataForOnyx(CONST.BANK_ACCOUNT.STEP.REQUESTOR, isConfirmPage),
    );
}

Adding Beneficial Owners

Submit beneficial owner information for compliance.
function updateBeneficialOwnersForBankAccount(
    bankAccountID: number,
    params: Partial<BeneficialOwnersStepProps>,
    policyID: string | undefined
) {
    API.write(
        WRITE_COMMANDS.UPDATE_BENEFICIAL_OWNERS_FOR_BANK_ACCOUNT,
        {
            ...params,
            bankAccountID,
            policyID,
        },
        getVBBADataForOnyx(),
    );
}

ACH Contract Acceptance

Accept the ACH terms and conditions to finalize bank account setup.
function acceptACHContractForBankAccount(
    bankAccountID: number,
    params: ACHContractStepProps,
    policyID?: string,
    lastPaymentMethod?: LastPaymentMethodType | string
) {
    const onyxData = getOnyxDataForConnectingVBBAAndLastPaymentMethod(policyID, lastPaymentMethod);

    API.write(
        WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT,
        {
            ...params,
            bankAccountID,
            policyID,
        },
        onyxData,
    );
}

Bank Account Validation

Micro-deposit Validation

Validate a bank account using micro-deposit amounts.
function validateBankAccount(
    bankAccountID: number,
    validateCode: string,
    policyID?: string
) {
    const parameters: ValidateBankAccountWithTransactionsParams = {
        bankAccountID,
        validateCode,
        policyID,
    };

    const onyxData: OnyxData<typeof ONYXKEYS.REIMBURSEMENT_ACCOUNT> = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: true,
                    errors: null,
                },
            },
        ],
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: false,
                },
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
                value: {
                    isLoading: false,
                    errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
                },
            },
        ],
    };

    API.write(WRITE_COMMANDS.VALIDATE_BANK_ACCOUNT_WITH_TRANSACTIONS, parameters, onyxData);
}

Identity Verification

Verify identity using Onfido for enhanced security.
function verifyIdentityForBankAccount(
    bankAccountID: number,
    onfidoData: OnfidoDataWithApplicantID,
    policyID?: string
) {
    const parameters: VerifyIdentityForBankAccountParams = {
        bankAccountID,
        onfidoData: JSON.stringify(onfidoData),
        policyID,
    };

    API.write(WRITE_COMMANDS.VERIFY_IDENTITY_FOR_BANK_ACCOUNT, parameters, getVBBADataForOnyx());
}

Deleting Bank Accounts

Remove a bank account from the system.
function deletePaymentBankAccount(
    bankAccountID: number,
    personalPolicyID: string | undefined,
    lastUsedPaymentMethods?: LastPaymentMethod,
    bankAccount?: OnyxEntry<PersonalBankAccount>,
    newBankAccountID?: number,
) {
    const parameters: DeletePaymentBankAccountParams = {bankAccountID};

    const bankAccountFailureData = {
        ...bankAccount,
        errors: getMicroSecondOnyxErrorWithTranslationKey('bankAccount.error.deletePaymentBankAccount'),
        pendingAction: null,
    };

    const onyxData: OnyxData<typeof ONYXKEYS.BANK_ACCOUNT_LIST | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD | typeof ONYXKEYS.USER_WALLET> = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`,
                value: {[bankAccountID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}},
            },
        ],
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`,
                value: {[bankAccountID]: null},
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.BANK_ACCOUNT_LIST}`,
                value: {
                    [bankAccountID]: bankAccountFailureData,
                },
            },
        ],
    };

    // Handle setting a new default if deleting the current default
    if (newBankAccountID) {
        const newDefaultPaymentMethodOnyxData = getMakeDefaultPaymentOnyxData(newBankAccountID);
        onyxData.optimisticData?.push(
            ...(newDefaultPaymentMethodOnyxData as Array<OnyxUpdate<typeof ONYXKEYS.BANK_ACCOUNT_LIST | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD | typeof ONYXKEYS.USER_WALLET>>),
        );
    }

    API.write(WRITE_COMMANDS.DELETE_PAYMENT_BANK_ACCOUNT, parameters, onyxData);
}
Deleting a bank account that is set as the default payment method will automatically set the next most recent bank account as the default, if available.

Payment Methods

Managing payment methods and defaults

Wallet

Expensify Wallet configuration

Build docs developers (and LLMs) love