Skip to main content

Overview

Payment methods in Expensify include bank accounts and debit cards. Users can manage multiple payment methods and set defaults for different contexts.

Getting Payment Methods

Retrieve all payment methods for the current user.
function getPaymentMethods(includePartiallySetupBankAccounts?: boolean) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.IS_LOADING_PAYMENT_METHODS>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
            value: true,
        },
    ];
    const successData: Array<OnyxUpdate<typeof ONYXKEYS.IS_LOADING_PAYMENT_METHODS>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
            value: false,
        },
    ];
    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.IS_LOADING_PAYMENT_METHODS>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS,
            value: false,
        },
    ];

    return API.read(
        READ_COMMANDS.OPEN_PAYMENTS_PAGE,
        {includePartiallySetupBankAccounts},
        {
            optimisticData,
            successData,
            failureData,
        },
    );
}

Setting Default Payment Method

Making a Payment Method Default

function makeDefaultPaymentMethod(
    bankAccountID: number,
    fundID: number,
    previousPaymentMethod?: PaymentMethod,
    currentPaymentMethod?: PaymentMethod
) {
    const parameters: MakeDefaultPaymentMethodParams = {
        bankAccountID,
        fundID,
    };

    API.write(WRITE_COMMANDS.MAKE_DEFAULT_PAYMENT_METHOD, parameters, {
        optimisticData: getMakeDefaultPaymentOnyxData(bankAccountID, fundID, previousPaymentMethod, currentPaymentMethod, true),
        failureData: getMakeDefaultPaymentOnyxData(bankAccountID, fundID, previousPaymentMethod, currentPaymentMethod, false),
    });
}

Generating Onyx Data for Default Payment Method

This helper function generates the appropriate Onyx updates when changing default payment methods.
function getMakeDefaultPaymentOnyxData(
    bankAccountID: number,
    fundID?: number,
    previousPaymentMethod?: PaymentMethod,
    currentPaymentMethod?: PaymentMethod,
    isOptimisticData = true,
): Array<OnyxUpdate<typeof ONYXKEYS.USER_WALLET | typeof ONYXKEYS.BANK_ACCOUNT_LIST | typeof ONYXKEYS.FUND_LIST>> {
    const onyxData: Array<OnyxUpdate<typeof ONYXKEYS.USER_WALLET | typeof ONYXKEYS.BANK_ACCOUNT_LIST | typeof ONYXKEYS.FUND_LIST>> = [
        isOptimisticData
            ? {
                  onyxMethod: Onyx.METHOD.MERGE,
                  key: ONYXKEYS.USER_WALLET,
                  value: {
                      walletLinkedAccountID: bankAccountID || fundID,
                      walletLinkedAccountType: bankAccountID 
                          ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT 
                          : CONST.PAYMENT_METHODS.DEBIT_CARD,
                      errors: null,
                  },
              }
            : {
                  onyxMethod: Onyx.METHOD.MERGE,
                  key: ONYXKEYS.USER_WALLET,
                  value: {
                      walletLinkedAccountID: bankAccountID || fundID,
                      walletLinkedAccountType: bankAccountID 
                          ? CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT 
                          : CONST.PAYMENT_METHODS.DEBIT_CARD,
                  },
              },
    ];

    if (previousPaymentMethod?.methodID) {
        onyxData.push({
            onyxMethod: Onyx.METHOD.MERGE,
            key: previousPaymentMethod.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT 
                ? ONYXKEYS.BANK_ACCOUNT_LIST 
                : ONYXKEYS.FUND_LIST,
            value: {
                [previousPaymentMethod.methodID]: {
                    isDefault: !isOptimisticData,
                },
            },
        });
    }

    if (currentPaymentMethod?.methodID) {
        onyxData.push({
            onyxMethod: Onyx.METHOD.MERGE,
            key: currentPaymentMethod.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT 
                ? ONYXKEYS.BANK_ACCOUNT_LIST 
                : ONYXKEYS.FUND_LIST,
            value: {
                [currentPaymentMethod.methodID]: {
                    isDefault: isOptimisticData,
                },
            },
        });
    }

    return onyxData;
}

Payment Cards

Adding a Payment Card

Add a new debit card for payments.
function addPaymentCard(accountID: number, params: PaymentCardParams) {
    const cardMonth = CardUtils.getMonthFromExpirationDateString(params.expirationDate);
    const cardYear = CardUtils.getYearFromExpirationDateString(params.expirationDate);

    const parameters: AddPaymentCardParams = {
        cardNumber: CardUtils.getMCardNumberString(params.cardNumber),
        cardYear,
        cardMonth,
        cardCVV: params.securityCode,
        addressName: params.nameOnCard,
        addressZip: params.addressZipCode,
        currency: CONST.PAYMENT_CARD_CURRENCY.USD,
        isP2PDebitCard: true,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM,
            value: {isLoading: true},
        },
    ];

    API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, {
        optimisticData,
        successData,
        failureData,
    });

    GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
}

Adding Subscription Payment Card

Add a card specifically for subscription billing, with SCA support.
function addSubscriptionPaymentCard(
    accountID: number,
    cardData: {
        cardNumber: string;
        cardYear: string;
        cardMonth: string;
        cardCVV: string;
        addressName: string;
        addressZip: string;
        currency: ValueOf<typeof CONST.PAYMENT_CARD_CURRENCY>;
    },
    fundList: OnyxEntry<FundList>,
) {
    const {cardNumber, cardYear, cardMonth, cardCVV, addressName, addressZip, currency} = cardData;

    const parameters: AddPaymentCardParams = {
        cardNumber,
        cardYear,
        cardMonth,
        cardCVV,
        addressName,
        addressZip,
        currency,
        isP2PDebitCard: false,
        shouldClaimEarlyDiscountOffer: true,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM,
            value: {isLoading: true},
        },
    ];

    if (CONST.SCA_CURRENCIES.has(currency)) {
        addPaymentCardSCA(parameters, {optimisticData, successData, failureData});
    } else {
        API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, {
            optimisticData,
            successData,
            failureData,
        });
    }
    
    if (getCardForSubscriptionBilling(fundList)) {
        Log.info(`[GTM] Not logging ${CONST.ANALYTICS.EVENT.PAID_ADOPTION} because a card was already added`);
    } else {
        GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
    }
}

SCA (Strong Customer Authentication) Cards

For European cards requiring 3DS authentication.
function addPaymentCardSCA(
    params: AddPaymentCardParams,
    onyxData: OnyxData<typeof ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM> = {}
) {
    API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD_SCA, params, onyxData);
}

function verifySetupIntent(accountID: number, isVerifying = true) {
    API.write(WRITE_COMMANDS.VERIFY_SETUP_INTENT, {accountID, isVerifying});
}

Deleting Payment Cards

function deletePaymentCard(fundID: number) {
    const parameters: DeletePaymentCardParams = {
        fundID,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.FUND_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.FUND_LIST}`,
            value: {[fundID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}},
        },
    ];

    API.write(WRITE_COMMANDS.DELETE_PAYMENT_CARD, parameters, {
        optimisticData,
    });
}

Wallet Transfer

Transferring Wallet Balance

Transfer funds from the Expensify Wallet to a connected bank account or debit card.
function transferWalletBalance(paymentMethod: PaymentMethod) {
    const paymentMethodIDKey =
        paymentMethod.accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT 
            ? CONST.PAYMENT_METHOD_ID_KEYS.BANK_ACCOUNT 
            : CONST.PAYMENT_METHOD_ID_KEYS.DEBIT_CARD;

    const parameters: TransferWalletBalanceParams = {
        [paymentMethodIDKey]: paymentMethod.methodID,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.WALLET_TRANSFER>> = [
        {
            onyxMethod: 'merge',
            key: ONYXKEYS.WALLET_TRANSFER,
            value: {
                loading: true,
                errors: null,
            },
        },
    ];

    const successData: Array<OnyxUpdate<typeof ONYXKEYS.WALLET_TRANSFER>> = [
        {
            onyxMethod: 'merge',
            key: ONYXKEYS.WALLET_TRANSFER,
            value: {
                loading: false,
                shouldShowSuccess: true,
                paymentMethodType: paymentMethod.accountType,
            },
        },
    ];

    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.WALLET_TRANSFER>> = [
        {
            onyxMethod: 'merge',
            key: ONYXKEYS.WALLET_TRANSFER,
            value: {
                loading: false,
                shouldShowSuccess: false,
            },
        },
    ];

    API.write(WRITE_COMMANDS.TRANSFER_WALLET_BALANCE, parameters, {
        optimisticData,
        successData,
        failureData,
    });
}

Managing Transfer State

function resetWalletTransferData() {
    Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {
        selectedAccountType: '',
        selectedAccountID: null,
        filterPaymentMethodType: null,
        loading: false,
        shouldShowSuccess: false,
    });
}

function saveWalletTransferAccountTypeAndID(
    selectedAccountType: string | undefined,
    selectedAccountID: string | undefined
) {
    Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {selectedAccountType, selectedAccountID});
}

function saveWalletTransferMethodType(filterPaymentMethodType?: FilterMethodPaymentType) {
    Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {filterPaymentMethodType});
}

Billing Currency

Update the currency used for subscription billing.
function updateBillingCurrency(
    currency: ValueOf<typeof CONST.PAYMENT_CARD_CURRENCY>,
    cardCVV: string
) {
    const parameters: UpdateBillingCurrencyParams = {
        cardCVV,
        currency,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM,
            value: {
                isLoading: true,
                errors: null,
            },
        },
    ];

    API.write(WRITE_COMMANDS.UPDATE_BILLING_CARD_CURRENCY, parameters, {
        optimisticData,
        successData,
        failureData,
    });
}

Invoicing Transfer Account

Set the default bank account for receiving invoice payments.
function setInvoicingTransferBankAccount(
    bankAccountID: number,
    policyID: string,
    previousBankAccountID: number
) {
    const parameters: SetInvoicingTransferBankAccountParams = {
        bankAccountID,
        policyID,
    };

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                invoice: {
                    bankAccount: {
                        transferBankAccountID: bankAccountID,
                    },
                },
            },
        },
    ];

    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                invoice: {
                    bankAccount: {
                        transferBankAccountID: previousBankAccountID,
                    },
                },
            },
        },
    ];

    API.write(WRITE_COMMANDS.SET_INVOICING_TRANSFER_BANK_ACCOUNT, parameters, {
        optimisticData,
        failureData,
    });
}

Error Handling

Checking for Payment Method Errors

function hasPaymentMethodError(
    bankList: OnyxEntry<BankAccountList>,
    fundList: OnyxEntry<FundList>,
    cardList: OnyxEntry<CardList>
): boolean {
    const combinedPaymentMethods = {...bankList, ...fundList, ...cardList};

    return Object.values(combinedPaymentMethods).some((item) => 
        Object.keys(item.errors ?? {}).length
    );
}

Clearing Errors

function clearDeletePaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: number) {
    Onyx.merge(paymentListKey, {
        [paymentMethodID]: {
            pendingAction: null,
            errors: null,
        },
    });
}

function clearAddPaymentMethodError(paymentListKey: PaymentListKey, paymentMethodID: number) {
    Onyx.merge(paymentListKey, {
        [paymentMethodID]: null,
    });
}

function clearWalletError() {
    Onyx.merge(ONYXKEYS.USER_WALLET, {errors: null});
}

Connecting Accounts

Learn about connecting bank accounts

Wallet

Managing your Expensify Wallet

Build docs developers (and LLMs) love