Skip to main content

Overview

Expensify supports importing transactions from both personal cards and company card programs. This allows for comprehensive expense tracking across all payment methods.

Personal Cards

Setting Card Reimbursability

Control whether transactions from a personal card are eligible for reimbursement.
function setPersonalCardReimbursable(
    cardID: number,
    reimbursable: boolean,
    previousValue?: boolean
) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    reimbursable,
                    pendingFields: {
                        reimbursable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                    },
                    errorFields: {
                        reimbursable: null,
                    },
                },
            },
        },
    ];

    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    ...(previousValue === undefined ? {} : {reimbursable: previousValue}),
                    pendingFields: {
                        reimbursable: null,
                    },
                    errorFields: {
                        reimbursable: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
                    },
                },
            },
        },
    ];

    const parameters: SetPersonalCardReimbursableParams = {
        cardID,
        reimbursable,
    };

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

Syncing Card Transactions

Manually trigger a sync to fetch the latest transactions from a connected card.
function syncCard(
    cardID: number,
    lastScrapeResult?: number,
    breakConnection?: boolean
) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    isLoadingLastUpdated: true,
                    lastScrapeResult: CONST.JSON_CODE.SUCCESS,
                    pendingFields: {
                        lastScrape: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                    },
                    errorFields: {
                        lastScrape: null,
                    },
                },
            },
        },
    ];

    const finallyData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    isLoadingLastUpdated: false,
                    pendingFields: {
                        lastScrape: null,
                    },
                },
            },
        },
    ];

    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    lastScrapeResult,
                    isLoadingLastUpdated: false,
                    pendingFields: {
                        lastScrape: null,
                    },
                    errorFields: {
                        lastScrape: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
                    },
                },
            },
        },
    ];

    const parameters: {cardID: number; breakConnection?: number} = {
        cardID,
    };

    if (breakConnection) {
        // Simulate "Account not found" error code for testing
        parameters.breakConnection = 434;
    }

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

Unassigning Cards

Remove a personal card from the account.
function unassignCard(card: Card) {
    const cardID = card.cardID;
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
                },
            },
        },
    ];

    const successData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: null,
            },
        },
    ];

    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    pendingAction: null,
                    errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
                },
            },
        },
    ];

    const parameters: UnassignCardParams = {
        cardID,
    };

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

Deleting Personal Cards

Permanently remove a personal card and its associated transactions.
type DeletePersonalCardData = {
    cardID: number;
    card?: Card;
    allTransactions: OnyxCollection<Transaction>;
    allReports: OnyxCollection<Report>;
    savedColumnLayout?: SavedCSVColumnLayoutData;
};

function deletePersonalCard({
    cardID,
    card,
    allTransactions,
    allReports,
    savedColumnLayout
}: DeletePersonalCardData) {
    // Find all transactions associated with this card that are on open/unsubmitted reports
    // This matches the backend logic which only deletes transactions on open reports
    const transactionsToDelete: Transaction[] = [];
    for (const transaction of Object.values(allTransactions ?? {})) {
        if (transaction?.cardID === cardID && isReportOpenOrUnsubmitted(transaction.reportID, allReports)) {
            transactionsToDelete.push(transaction);
        }
    }

    // Optimistically remove the card and its saved column layout immediately for instant UI feedback
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST | typeof ONYXKEYS.NVP_SAVED_CSV_COLUMN_LAYOUT_LIST | typeof ONYXKEYS.COLLECTION.TRANSACTION>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: null,
            },
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.NVP_SAVED_CSV_COLUMN_LAYOUT_LIST,
            value: {
                [cardID]: null,
            },
        },
    ];

    // Optimistically delete transactions and prepare failure data to restore them
    for (const transaction of transactionsToDelete) {
        optimisticData.push({
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
            value: null,
        });

        failureData.push({
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`,
            value: transaction,
        });
    }

    const parameters: DeletePersonalCardParams = {
        cardID,
    };

    API.write(WRITE_COMMANDS.DELETE_PERSONAL_CARD, parameters, {
        optimisticData,
        failureData,
    });
}
Deleting a personal card will also remove all associated transactions from open/unsubmitted reports. This action cannot be undone.

Updating Card Details

Card Name

Customize the display name for a card.
function updateAssignedCardName(
    cardID: string,
    newCardTitle: string,
    oldCardTitle?: string
) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST | typeof ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    nameValuePairs: {
                        cardTitle: newCardTitle,
                        pendingFields: {
                            cardTitle: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                        },
                        errorFields: {
                            cardTitle: null,
                        },
                    },
                },
            },
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES,
            value: {
                [cardID]: newCardTitle,
            },
        },
    ];

    const parameters: UpdateCompanyCardNameParams = {
        cardID: Number(cardID),
        cardName: newCardTitle,
    };

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

Transaction Start Date

Set the date from which transactions should be imported.
function updateAssignedCardTransactionStartDate(
    cardID: string,
    newStartDate: string,
    oldStartDate?: string
) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.CARD_LIST>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: ONYXKEYS.CARD_LIST,
            value: {
                [cardID]: {
                    scrapeMinDate: newStartDate,
                    pendingFields: {
                        scrapeMinDate: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                    },
                    errorFields: {
                        scrapeMinDate: null,
                    },
                },
            },
        },
    ];

    const parameters: UpdateCardTransactionStartDateParams = {
        cardID: Number(cardID),
        startDate: newStartDate,
    };

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

Continuous Reconciliation

Enable automatic reconciliation with accounting integrations.
function toggleContinuousReconciliation(
    workspaceAccountID: number,
    shouldUseContinuousReconciliation: boolean,
    connectionName: ConnectionName,
    oldConnectionName?: ConnectionName
) {
    const parameters = shouldUseContinuousReconciliation
        ? {
              workspaceAccountID,
              shouldUseContinuousReconciliation,
              expensifyCardContinuousReconciliationConnection: connectionName,
          }
        : {
              workspaceAccountID,
              shouldUseContinuousReconciliation,
          };

    const optimisticData: Array<
        OnyxUpdate<typeof ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION | typeof ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION>
    > = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`,
            value: {
                value: shouldUseContinuousReconciliation,
                pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
            },
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`,
            value: connectionName,
        },
    ];

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

Issuing New Cards

Starting the Card Flow

Initiate the process for issuing a new card to a member.
function startIssueNewCardFlow(policyID: string | undefined) {
    const parameters: StartIssueNewCardFlowParams = {
        policyID,
    };

    API.read(READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW, parameters);
}

Configuring Expensify Cards

Set up Expensify Card program for a workspace.
function configureExpensifyCardsForPolicy(
    policyID: string,
    workspaceAccountID: number,
    bankAccountID?: number
) {
    if (!bankAccountID) {
        return;
    }

    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS | typeof ONYXKEYS.COLLECTION.EXPENSIFY_CARD_BANK_ACCOUNT_METADATA>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`,
            value: {
                isLoading: true,
            },
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_BANK_ACCOUNT_METADATA}${workspaceAccountID}`,
            value: {
                isLoading: true,
                isSuccess: false,
            },
        },
    ];

    const parameters = {
        policyID,
        bankAccountID,
    };

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

Overview

Learn about Expensify Card basics

Card Management

Managing card limits and operations

Build docs developers (and LLMs) love