diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index d43c8410d496..1b01f9662018 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -7191,9 +7191,9 @@ const CONST = {
},
LAST_PAYMENT_METHOD: {
LAST_USED: 'lastUsed',
- IOU: 'Iou',
- EXPENSE: 'Expense',
- INVOICE: 'Invoice',
+ IOU: 'iou',
+ EXPENSE: 'expense',
+ INVOICE: 'invoice',
},
SKIPPABLE_COLLECTION_MEMBER_IDS: [String(DEFAULT_NUMBER_ID), '-1', 'undefined', 'null', 'NaN'] as string[],
SETUP_SPECIALIST_LOGIN: 'Setup Specialist',
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index d68eeebcaef0..b7a62c7aa063 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -310,7 +310,19 @@ function Button(
const textComponent = secondLineText ? (
{primaryText}
- {secondLineText}
+
+ {secondLineText}
+
) : (
primaryText
diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx
index dd4551fcd679..09bcd1574bcb 100644
--- a/src/components/ButtonWithDropdownMenu/index.tsx
+++ b/src/components/ButtonWithDropdownMenu/index.tsx
@@ -50,6 +50,8 @@ function ButtonWithDropdownMenu({
testID,
secondLineText = '',
icon,
+ shouldPopoverUseScrollView = false,
+ containerStyles,
shouldUseModalPaddingStyle = true,
}: ButtonWithDropdownMenuProps) {
const theme = useTheme();
@@ -73,6 +75,10 @@ function ButtonWithDropdownMenu({
const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE;
const nullCheckRef = (ref: RefObject) => ref ?? null;
+ useEffect(() => {
+ setSelectedItemIndex(defaultSelectedIndex);
+ }, [defaultSelectedIndex]);
+
useEffect(() => {
if (!dropdownAnchor.current) {
return;
@@ -233,17 +239,27 @@ function ButtonWithDropdownMenu({
// eslint-disable-next-line react-compiler/react-compiler
anchorRef={nullCheckRef(dropdownAnchor)}
withoutOverlay
- shouldUseScrollView
scrollContainerStyle={!shouldUseModalPaddingStyle && isSmallScreenWidth && styles.pv4}
- shouldUseModalPaddingStyle={shouldUseModalPaddingStyle}
anchorAlignment={anchorAlignment}
+ shouldUseModalPaddingStyle={shouldUseModalPaddingStyle}
headerText={menuHeaderText}
+ shouldUseScrollView={shouldPopoverUseScrollView}
+ containerStyles={containerStyles}
menuItems={options.map((item, index) => ({
...item,
onSelected: item.onSelected
- ? () => item.onSelected?.()
+ ? () => {
+ item.onSelected?.();
+ if (item.shouldUpdateSelectedIndex) {
+ setSelectedItemIndex(index);
+ }
+ }
: () => {
onOptionSelected?.(item);
+ if (!item.shouldUpdateSelectedIndex && typeof item.shouldUpdateSelectedIndex === 'boolean') {
+ return;
+ }
+
setSelectedItemIndex(index);
},
shouldCallAfterModalHide: true,
diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts
index 395d445e100f..82806a4d8074 100644
--- a/src/components/ButtonWithDropdownMenu/types.ts
+++ b/src/components/ButtonWithDropdownMenu/types.ts
@@ -40,6 +40,8 @@ type DropdownOption = {
descriptionTextStyle?: StyleProp;
wrapperStyle?: StyleProp;
displayInDefaultIconColor?: boolean;
+ /** Whether the selected index should be updated when the option is selected even if we have onSelected callback */
+ shouldUpdateSelectedIndex?: boolean;
subMenuItems?: PopoverMenuItem[];
backButtonText?: string;
avatarSize?: ValueOf;
@@ -140,6 +142,12 @@ type ButtonWithDropdownMenuProps = {
/** Icon for main button */
icon?: IconAsset;
+ /** Whether the popover content should be scrollable */
+ shouldPopoverUseScrollView?: boolean;
+
+ /** Container style to be applied to the popover of the dropdown menu */
+ containerStyles?: StyleProp;
+
/** Whether to use modal padding style for the popover menu */
shouldUseModalPaddingStyle?: boolean;
};
diff --git a/src/components/Icon/BankIcons/index.native.ts b/src/components/Icon/BankIcons/index.native.ts
index aaf8456b2771..246eebb7357c 100644
--- a/src/components/Icon/BankIcons/index.native.ts
+++ b/src/components/Icon/BankIcons/index.native.ts
@@ -1,7 +1,7 @@
-import GenericBank from '@assets/images/bank-icons/generic-bank-account.svg';
import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg';
import type {BankIconParams} from '@components/Icon/BankIconsUtils';
import {getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils';
+import {Bank} from '@components/Icon/Expensicons';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {BankIcon} from '@src/types/onyx/Bank';
@@ -11,7 +11,7 @@ import type {BankIcon} from '@src/types/onyx/Bank';
*/
export default function getBankIcon({styles, bankName, isCard = false}: BankIconParams): BankIcon {
const bankIcon: BankIcon = {
- icon: isCard ? GenericBankCard : GenericBank,
+ icon: isCard ? GenericBankCard : Bank,
};
if (bankName) {
const bankNameKey = getBankNameKey(bankName.toLowerCase());
diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx
index 68e43f43538e..efe19d75109e 100644
--- a/src/components/KYCWall/BaseKYCWall.tsx
+++ b/src/components/KYCWall/BaseKYCWall.tsx
@@ -5,18 +5,20 @@ import type {EmitterSubscription, GestureResponderEvent, View} from 'react-nativ
import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';
import useOnyx from '@hooks/useOnyx';
import {openPersonalBankAccountSetupView} from '@libs/actions/BankAccounts';
-import {completePaymentOnboarding} from '@libs/actions/IOU';
+import {completePaymentOnboarding, savePreferredPaymentMethod} from '@libs/actions/IOU';
+import {moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {hasExpensifyPaymentMethod} from '@libs/PaymentUtils';
-import {isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils';
+import {getPolicyExpenseChat, isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils';
import {kycWallRef} from '@userActions/PaymentMethods';
import {createWorkspaceFromIOUPayment} from '@userActions/Policy/Policy';
import {setKYCWallSource} from '@userActions/Wallet';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {Policy} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import viewRef from '@src/types/utils/viewRef';
import type {AnchorPosition, DomRect, KYCWallProps, PaymentMethod} from './types';
@@ -45,11 +47,11 @@ function KYCWall({
source,
shouldShowPersonalBankAccountOption = false,
}: KYCWallProps) {
- const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
- const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
- const [fundList] = useOnyx(ONYXKEYS.FUND_LIST);
- const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
- const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
+ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
+ const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true});
+ const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
+ const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
+ const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});
const anchorRef = useRef(null);
const transferBalanceButtonRef = useRef(null);
@@ -100,16 +102,41 @@ function KYCWall({
}, [getAnchorPosition]);
const selectPaymentMethod = useCallback(
- (paymentMethod: PaymentMethod) => {
- onSelectPaymentMethod(paymentMethod);
+ (paymentMethod?: PaymentMethod, policy?: Policy) => {
+ if (paymentMethod) {
+ onSelectPaymentMethod(paymentMethod);
+ }
if (paymentMethod === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) {
openPersonalBankAccountSetupView();
} else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) {
Navigation.navigate(addDebitCardRoute ?? ROUTES.HOME);
- } else if (paymentMethod === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) {
+ } else if (paymentMethod === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT || policy) {
if (iouReport && isIOUReport(iouReport)) {
+ if (policy) {
+ const policyExpenseChatReportID = getPolicyExpenseChat(iouReport.ownerAccountID, policy.id)?.reportID;
+ if (!policyExpenseChatReportID) {
+ const {policyExpenseChatReportID: newPolicyExpenseChatReportID} = moveIOUReportToPolicyAndInviteSubmitter(iouReport.reportID, policy.id) ?? {};
+ savePreferredPaymentMethod(iouReport.policyID, policy.id, CONST.LAST_PAYMENT_METHOD.IOU);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(newPolicyExpenseChatReportID));
+ } else {
+ moveIOUReportToPolicy(iouReport.reportID, policy.id, true);
+ savePreferredPaymentMethod(iouReport.policyID, policy.id, CONST.LAST_PAYMENT_METHOD.IOU);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(policyExpenseChatReportID));
+ }
+
+ if (policy?.achAccount) {
+ return;
+ }
+ // Navigate to the bank account set up flow for this specific policy
+ Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policy.id));
+ return;
+ }
+
const {policyID, workspaceChatReportID, reportPreviewReportActionID, adminsChatReportID} = createWorkspaceFromIOUPayment(iouReport) ?? {};
+ if (policyID) {
+ savePreferredPaymentMethod(iouReport.policyID, policyID, CONST.LAST_PAYMENT_METHOD.IOU);
+ }
completePaymentOnboarding(CONST.PAYMENT_SELECTED.BBA, adminsChatReportID, policyID);
if (workspaceChatReportID) {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReportID, reportPreviewReportActionID));
@@ -117,7 +144,6 @@ function KYCWall({
// Navigate to the bank account set up flow for this specific policy
Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID));
-
return;
}
Navigation.navigate(addBankAccountRoute);
@@ -133,7 +159,7 @@ function KYCWall({
*
*/
const continueAction = useCallback(
- (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType) => {
+ (event?: GestureResponderEvent | KeyboardEvent, iouPaymentType?: PaymentMethodType, paymentMethod?: PaymentMethod, policy?: Policy) => {
const currentSource = walletTerms?.source ?? source;
/**
@@ -170,6 +196,13 @@ function KYCWall({
const clickedElementLocation = getClickedTargetLocation(targetElement as HTMLDivElement);
const position = getAnchorPosition(clickedElementLocation);
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ if (paymentMethod || policy) {
+ setShouldShowAddPaymentMenu(false);
+ selectPaymentMethod(paymentMethod, policy);
+ return;
+ }
+
setPositionAddPaymentMenu(position);
setShouldShowAddPaymentMenu(true);
diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts
index 5214e5ea15b9..71c557a859e6 100644
--- a/src/components/KYCWall/types.ts
+++ b/src/components/KYCWall/types.ts
@@ -4,7 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {Route} from '@src/ROUTES';
-import type {Report} from '@src/types/onyx';
+import type {Policy, Report} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
@@ -63,6 +63,9 @@ type KYCWallProps = {
/** Children to build the KYC */
children: (continueAction: (event: GestureResponderEvent | KeyboardEvent | undefined, method?: PaymentMethodType) => void, anchorRef: RefObject) => void;
+
+ /** The policy used for payment */
+ policy?: Policy;
};
export type {AnchorPosition, KYCWallProps, PaymentMethod, DomRect, PaymentMethodType, Source};
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index acc97a7154c6..1cc4e661a2d6 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -315,7 +315,7 @@ function MoneyReportHeader({
payInvoice(type, chatReport, moneyRequestReport, payAsBusiness, methodID, paymentMethod);
} else {
startAnimation();
- payMoneyRequest(type, chatReport, moneyRequestReport, true);
+ payMoneyRequest(type, chatReport, moneyRequestReport, undefined, true);
}
},
[chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, showDelegateNoAccessModal, isInvoiceReport, moneyRequestReport, startAnimation],
@@ -525,6 +525,7 @@ function MoneyReportHeader({
isPaidAnimationRunning={isPaidAnimationRunning}
isApprovedAnimationRunning={isApprovedAnimationRunning}
onAnimationFinish={stopAnimation}
+ formattedAmount={totalAmount}
canIOUBePaid
onlyShowPayElsewhere={onlyShowPayElsewhere}
currency={moneyRequestReport?.currency}
diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx
index b5e7eff18aee..e4f5b0bb2ecb 100644
--- a/src/components/PopoverMenu.tsx
+++ b/src/components/PopoverMenu.tsx
@@ -312,13 +312,10 @@ function PopoverMenu({
}
setFocusedIndex(menuIndex);
}}
- wrapperStyle={StyleUtils.getItemBackgroundColorStyle(
- !!item.isSelected,
- focusedIndex === menuIndex,
- item.disabled ?? false,
- theme.activeComponentBG,
- theme.hoverComponentBG,
- )}
+ wrapperStyle={[
+ StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, focusedIndex === menuIndex, item.disabled ?? false, theme.activeComponentBG, theme.hoverComponentBG),
+ shouldUseScrollView && StyleUtils.getOptionMargin(menuIndex, currentMenuItems.length - 1),
+ ]}
shouldRemoveHoverBackground={item.isSelected}
titleStyle={StyleSheet.flatten([styles.flex1, item.titleStyle])}
// Spread other props dynamically
diff --git a/src/components/ProcessMoneyReportHoldMenu.tsx b/src/components/ProcessMoneyReportHoldMenu.tsx
index ba320a594135..0a155fa4c41b 100644
--- a/src/components/ProcessMoneyReportHoldMenu.tsx
+++ b/src/components/ProcessMoneyReportHoldMenu.tsx
@@ -4,7 +4,7 @@ import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import Navigation from '@libs/Navigation/Navigation';
import {isLinkedTransactionHeld} from '@libs/ReportActionsUtils';
-import * as IOU from '@userActions/IOU';
+import {approveMoneyRequest, payMoneyRequest} from '@userActions/IOU';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
@@ -69,15 +69,15 @@ function ProcessMoneyReportHoldMenu({
if (startAnimation) {
startAnimation();
}
- IOU.approveMoneyRequest(moneyRequestReport, full);
- if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) {
- Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? ''));
+ approveMoneyRequest(moneyRequestReport, full);
+ if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId(), moneyRequestReport?.reportID)) {
+ Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID));
}
} else if (chatReport && paymentType) {
if (startAnimation) {
startAnimation();
}
- IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport, full);
+ payMoneyRequest(paymentType, chatReport, moneyRequestReport, undefined, full);
}
onClose();
};
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index f01411780ef6..4a1a26b5c47e 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -79,15 +79,22 @@ function mapTransactionItemToSelectedEntry(item: TransactionListItemType, report
];
}
-function mapToTransactionItemWithAdditionalInfo(item: TransactionListItemType, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean, shouldAnimateInHighlight: boolean) {
- return {...item, shouldAnimateInHighlight, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple};
+function mapToTransactionItemWithAdditionalInfo(
+ item: TransactionListItemType,
+ selectedTransactions: SelectedTransactions,
+ canSelectMultiple: boolean,
+ shouldAnimateInHighlight: boolean,
+ hash?: number,
+) {
+ return {...item, shouldAnimateInHighlight, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple, hash};
}
-function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean, shouldAnimateInHighlight: boolean) {
+function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean, shouldAnimateInHighlight: boolean, hash?: number) {
if (isTaskListItemType(item)) {
return {
...item,
shouldAnimateInHighlight,
+ hash,
};
}
@@ -95,18 +102,22 @@ function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions:
return {
...item,
shouldAnimateInHighlight,
+ hash,
};
}
return isTransactionListItemType(item)
- ? mapToTransactionItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight)
+ ? mapToTransactionItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash)
: {
...item,
shouldAnimateInHighlight,
- transactions: item.transactions?.map((transaction) => mapToTransactionItemWithAdditionalInfo(transaction, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight)),
+ transactions: item.transactions?.map((transaction) =>
+ mapToTransactionItemWithAdditionalInfo(transaction, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash),
+ ),
isSelected:
item?.transactions?.length > 0 &&
item.transactions?.filter((t) => !isTransactionPendingDelete(t)).every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected && canSelectMultiple),
+ hash,
};
}
@@ -507,7 +518,7 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS
// Determine if either the base key or any transaction key matches
const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch;
- return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight);
+ return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash);
});
const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline;
diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts
index 11f492ab8015..a0945051b1d7 100644
--- a/src/components/Search/types.ts
+++ b/src/components/Search/types.ts
@@ -1,4 +1,5 @@
import type {ValueOf} from 'type-fest';
+import type {PaymentMethodType} from '@components/KYCWall/types';
import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types';
import type CONST from '@src/CONST';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
@@ -51,7 +52,7 @@ type SelectedReports = {
type PaymentData = {
reportID: string;
amount: number;
- paymentType: ValueOf;
+ paymentType: PaymentMethodType;
};
type SortOrder = ValueOf;
diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx
index 5dd54e8e52b7..30d94f2d8708 100644
--- a/src/components/SelectionList/Search/ActionCell.tsx
+++ b/src/components/SelectionList/Search/ActionCell.tsx
@@ -1,16 +1,23 @@
-import React from 'react';
+import React, {useCallback} from 'react';
import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import Badge from '@components/Badge';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
+import type {PaymentMethodType} from '@components/KYCWall/types';
+import SettlementButton from '@components/SettlementButton';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import {payMoneyRequestOnSearch} from '@libs/actions/Search';
+import {getBankAccountRoute} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type {SearchTransactionAction} from '@src/types/onyx/SearchResults';
const actionTranslationsMap: Record = {
@@ -31,6 +38,10 @@ type ActionCellProps = {
isChildListItem?: boolean;
parentAction?: string;
isLoading?: boolean;
+ policyID?: string;
+ reportID?: string;
+ hash?: number;
+ amount?: number;
};
function ActionCell({
@@ -41,6 +52,10 @@ function ActionCell({
isChildListItem = false,
parentAction = '',
isLoading = false,
+ policyID = '',
+ reportID = '',
+ hash,
+ amount,
}: ActionCellProps) {
const {translate} = useLocalize();
const theme = useTheme();
@@ -48,8 +63,22 @@ function ActionCell({
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
+ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: true});
const text = isChildListItem ? translate(actionTranslationsMap[CONST.SEARCH.ACTION_TYPES.VIEW]) : translate(actionTranslationsMap[action]);
const shouldUseViewAction = action === CONST.SEARCH.ACTION_TYPES.VIEW || (parentAction === CONST.SEARCH.ACTION_TYPES.PAID && action === CONST.SEARCH.ACTION_TYPES.PAID);
+ const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, {canBeMissing: true});
+ const bankAccountRoute = getBankAccountRoute(chatReport);
+
+ const confirmPayment = useCallback(
+ (type: PaymentMethodType | undefined) => {
+ if (!type || !reportID || !hash || !amount) {
+ return;
+ }
+
+ payMoneyRequestOnSearch(hash, [{amount, paymentType: type, reportID}]);
+ },
+ [hash, amount, reportID],
+ );
if (!isChildListItem && ((parentAction !== CONST.SEARCH.ACTION_TYPES.PAID && action === CONST.SEARCH.ACTION_TYPES.PAID) || action === CONST.SEARCH.ACTION_TYPES.DONE)) {
return (
@@ -94,6 +123,23 @@ function ActionCell({
) : null;
}
+ if (action === CONST.SEARCH.ACTION_TYPES.PAY) {
+ return (
+
+ );
+ }
+
return (
)}
diff --git a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx
index 5915b7d6785f..c8fb1b1575c1 100644
--- a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx
+++ b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx
@@ -49,6 +49,8 @@ function UserInfoAndActionButtonRow({
goToItem={handleActionButtonPress}
isSelected={item.isSelected}
isLoading={item.isActionLoading}
+ policyID={item.policyID}
+ reportID={item.reportID}
/>
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index 5039022561e9..e51cfebf1731 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -212,6 +212,9 @@ type ListItem = {
/** Boolean whether to display the right icon */
shouldShowRightIcon?: boolean;
+
+ /** Used to initiate payment from search page */
+ hash?: number;
};
type TransactionListItemType = ListItem &
@@ -264,6 +267,12 @@ type TransactionListItemType = ListItem &
/** Attendees in the transaction */
attendees?: Attendee[];
+ /** IOUs report */
+ iouReportID?: string | undefined;
+
+ /** Whether the report is policyExpenseChat */
+ isPolicyExpenseChat?: boolean;
+
/** Precomputed violations */
violations?: TransactionViolation[];
};
@@ -703,6 +712,7 @@ type SelectionListProps = Partial & {
alternateTextNumberOfLines?: number;
/** Ref for textInput */
+ // eslint-disable-next-line deprecation/deprecation
textInputRef?: MutableRefObject | ((ref: TextInput | null) => void);
/** Styles for the section title */
diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx
index f656772b8212..6c306521a7cd 100644
--- a/src/components/SettlementButton/index.tsx
+++ b/src/components/SettlementButton/index.tsx
@@ -1,24 +1,51 @@
-import React, {useContext} from 'react';
+import isEmpty from 'lodash/isEmpty';
+import truncate from 'lodash/truncate';
+import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react';
+import type {GestureResponderEvent} from 'react-native';
import {useOnyx} from 'react-native-onyx';
+import type {TupleToUnion} from 'type-fest';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
-import type {DropdownOption, PaymentType} from '@components/ButtonWithDropdownMenu/types';
+import * as Expensicons from '@components/Icon/Expensicons';
import KYCWall from '@components/KYCWall';
+import type {PaymentMethod} from '@components/KYCWall/types';
import {LockedAccountContext} from '@components/LockedAccountModalProvider';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
-import usePaymentOptions from '@hooks/usePaymentOptions';
-import {selectPaymentType} from '@libs/PaymentUtils';
-import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {isCurrencySupportedForDirectReimbursement} from '@libs/actions/Policy/Policy';
+import {getCurrentUserAccountID} from '@libs/actions/Report';
+import {getLastPolicyBankAccountID, getLastPolicyPaymentMethod} from '@libs/actions/Search';
+import Navigation from '@libs/Navigation/Navigation';
+import {formatPaymentMethods} from '@libs/PaymentUtils';
import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils';
-import {doesReportBelongToWorkspace, isInvoiceReport as isInvoiceReportUtil} from '@libs/ReportUtils';
-import {savePreferredPaymentMethod as savePreferredPaymentMethodIOU} from '@userActions/IOU';
+import {getActiveAdminWorkspaces, hasVBBA} from '@libs/PolicyUtils';
+import {hasRequestFromCurrentAccount} from '@libs/ReportActionsUtils';
+import {
+ doesReportBelongToWorkspace,
+ isBusinessInvoiceRoom,
+ isExpenseReport as isExpenseReportUtil,
+ isIndividualInvoiceRoom as isIndividualInvoiceRoomUtil,
+ isInvoiceReport as isInvoiceReportUtil,
+ isIOUReport,
+} from '@libs/ReportUtils';
+import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
+import {setPersonalBankAccountContinueKYCOnSuccess} from '@userActions/BankAccounts';
+import {approveMoneyRequest, savePreferredPaymentMethod as savePreferredPaymentMethodIOU} from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type {AccountData, BankAccount, LastPaymentMethodType, Policy} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import type SettlementButtonProps from './types';
+type KYCFlowEvent = GestureResponderEvent | KeyboardEvent | undefined;
+
+type TriggerKYCFlow = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, paymentMethod?: PaymentMethod, policy?: Policy) => void;
+
+type CurrencyType = TupleToUnion;
+
function SettlementButton({
addDebitCardRoute = ROUTES.IOU_SEND_ADD_DEBIT_CARD,
addBankAccountRoute = '',
@@ -54,7 +81,10 @@ function SettlementButton({
onPaymentOptionsHide,
onlyShowPayElsewhere,
wrapperStyle,
+ shouldUseShortForm = false,
+ hasOnlyHeldExpenses = false,
}: SettlementButtonProps) {
+ const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
// The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here.
@@ -63,42 +93,458 @@ function SettlementButton({
const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: true});
const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : [];
const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID) : false;
- const policyIDKey = reportBelongsToWorkspace ? policyID : CONST.POLICY.ID_FAKE;
- const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false});
+ const policyIDKey = reportBelongsToWorkspace ? policyID : (iouReport?.policyID ?? CONST.POLICY.ID_FAKE);
+ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
+ const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? '');
+
+ const [lastPaymentMethod, lastPaymentMethodResult] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {
+ canBeMissing: true,
+ selector: (paymentMethod) => getLastPolicyPaymentMethod(policyIDKey, paymentMethod, iouReport?.type as keyof LastPaymentMethodType, isIOUReport(iouReport)),
+ });
+
+ const lastBankAccountID = getLastPolicyBankAccountID(policyIDKey, iouReport?.type as keyof LastPaymentMethodType);
+ const [fundList = {}] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
+ const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
+ const currentUserAccountID = getCurrentUserAccountID().toString();
+ const activeAdminPolicies = getActiveAdminWorkspaces(policies, currentUserAccountID).sort((a, b) => (a.name || '').localeCompare(b.name || ''));
+ const reportID = iouReport?.reportID;
+
+ const hasPreferredPaymentMethod = !!lastPaymentMethod;
+ const isLoadingLastPaymentMethod = isLoadingOnyxValue(lastPaymentMethodResult);
+ const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
+ const isLastPaymentPolicy = !Object.values({...CONST.PAYMENT_METHODS, ...CONST.IOU.PAYMENT_TYPE}).includes(lastPaymentMethod as PaymentMethod);
+ const lastPaymentPolicy = isLastPaymentPolicy ? policies?.[`${ONYXKEYS.COLLECTION.POLICY}${lastPaymentMethod}`] : undefined;
+ const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
+ const bankAccount = bankAccountList[lastBankAccountID ?? CONST.DEFAULT_NUMBER_ID];
+ // whether the user has single policy and the expense isn't inside a workspace
+ const hasSinglePolicy = !policy && activeAdminPolicies.length === 1;
+ const hasMultiplePolicies = !policy && activeAdminPolicies.length > 1;
+ const lastPaymentMethodRef = useRef(lastPaymentMethod);
+ const formattedPaymentMethods = formatPaymentMethods(bankAccountList, fundList, styles);
+ const hasIntentToPay = formattedPaymentMethods.length === 1 && !lastPaymentMethod;
+
+ useEffect(() => {
+ if (isLoadingLastPaymentMethod) {
+ return;
+ }
+ lastPaymentMethodRef.current = lastPaymentMethod;
+ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
+ }, [isLoadingLastPaymentMethod]);
+
const isInvoiceReport = (!isEmptyObject(iouReport) && isInvoiceReportUtil(iouReport)) || false;
const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext);
+ const shouldShowPayWithExpensifyOption = !shouldHidePaymentOptions;
+ const shouldShowPayElsewhereOption = !shouldHidePaymentOptions && !isInvoiceReport;
- const paymentButtonOptions = usePaymentOptions({
- addBankAccountRoute,
- currency,
+ const getPaymentSubitems = useCallback(
+ (payAsBusiness: boolean) => {
+ const requiredAccountType = payAsBusiness ? CONST.BANK_ACCOUNT.TYPE.BUSINESS : CONST.BANK_ACCOUNT.TYPE.PERSONAL;
+
+ return formattedPaymentMethods
+ .filter((method) => {
+ const accountData = method?.accountData as AccountData;
+ return accountData?.type === requiredAccountType;
+ })
+ .map((formattedPaymentMethod) => ({
+ text: formattedPaymentMethod?.title ?? '',
+ description: formattedPaymentMethod?.description ?? '',
+ icon: formattedPaymentMethod?.icon,
+ shouldUpdateSelectedIndex: true,
+ onSelected: () => {
+ onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, payAsBusiness, formattedPaymentMethod.methodID, formattedPaymentMethod.accountType, undefined);
+ },
+ iconStyles: formattedPaymentMethod?.iconStyles,
+ iconHeight: formattedPaymentMethod?.iconSize,
+ iconWidth: formattedPaymentMethod?.iconSize,
+ value: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ }));
+ },
+ [formattedPaymentMethods, onPress],
+ );
+
+ function getLatestBankAccountItem() {
+ if (!hasVBBA(policy?.id)) {
+ return;
+ }
+ const policyBankAccounts = formattedPaymentMethods.filter((method) => method.methodID === policy?.achAccount?.bankAccountID);
+
+ return policyBankAccounts.map((formattedPaymentMethod) => ({
+ text: formattedPaymentMethod?.title ?? '',
+ description: formattedPaymentMethod?.description ?? '',
+ icon: formattedPaymentMethod?.icon,
+ onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, true, undefined),
+ methodID: formattedPaymentMethod?.methodID,
+ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
+ }));
+ }
+
+ function getLatestPersonalBankAccount() {
+ return formattedPaymentMethods.filter((ba) => (ba.accountData as AccountData)?.type === CONST.BANK_ACCOUNT.TYPE.PERSONAL);
+ }
+
+ const getLastPaymentMethodType = () => {
+ if (isInvoiceReport) {
+ return CONST.LAST_PAYMENT_METHOD.INVOICE;
+ }
+
+ if (policy) {
+ return CONST.LAST_PAYMENT_METHOD.EXPENSE;
+ }
+
+ return CONST.LAST_PAYMENT_METHOD.IOU;
+ };
+
+ const savePreferredPaymentMethod = (id: string, value: string) => {
+ savePreferredPaymentMethodIOU(id, value, getLastPaymentMethodType());
+ };
+
+ const personalBankAccountList = getLatestPersonalBankAccount();
+ const latestBankItem = getLatestBankAccountItem();
+
+ const paymentButtonOptions = useMemo(() => {
+ const buttonOptions = [];
+ const isExpenseReport = isExpenseReportUtil(iouReport);
+ const paymentMethods = {
+ [CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]: {
+ text: hasActivatedWallet ? translate('iou.settleWallet', {formattedAmount: ''}) : translate('iou.settlePersonal', {formattedAmount: ''}),
+ icon: Expensicons.User,
+ value: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
+ shouldUpdateSelectedIndex: false,
+ },
+ [CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]: {
+ text: translate('iou.settleBusiness', {formattedAmount: ''}),
+ icon: Expensicons.Building,
+ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
+ shouldUpdateSelectedIndex: false,
+ },
+ [CONST.IOU.PAYMENT_TYPE.ELSEWHERE]: {
+ text: translate('iou.payElsewhere', {formattedAmount: onlyShowPayElsewhere ? formattedAmount : ''}),
+ icon: Expensicons.CheckCircle,
+ value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
+ shouldUpdateSelectedIndex: false,
+ },
+ };
+
+ const approveButtonOption = {
+ text: translate('iou.approve', {formattedAmount}),
+ icon: Expensicons.ThumbsUp,
+ value: CONST.IOU.REPORT_ACTION_TYPE.APPROVE,
+ disabled: !!shouldDisableApproveButton,
+ };
+
+ const canUseWallet = !isExpenseReport && !isInvoiceReport && currency === CONST.CURRENCY.USD;
+ const canUseBusinessBankAccount = isExpenseReport || (isIOUReport(iouReport) && reportID && !hasRequestFromCurrentAccount(reportID, Number(currentUserAccountID) ?? -1));
+
+ const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || isIOUReport;
+
+ const isPersonalOnlyOption = canUsePersonalBankAccount && !canUseBusinessBankAccount;
+
+ // Only show the Approve button if the user cannot pay the expense
+ if (shouldHidePaymentOptions && shouldShowApproveButton) {
+ return [approveButtonOption];
+ }
+
+ if (onlyShowPayElsewhere) {
+ return [paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]];
+ }
+
+ // To achieve the one tap pay experience we need to choose the correct payment type as default.
+ if (canUseWallet) {
+ if (personalBankAccountList.length && canUsePersonalBankAccount) {
+ buttonOptions.push({
+ text: translate('iou.settleWallet', {formattedAmount: ''}),
+ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
+ icon: Expensicons.Wallet,
+ });
+ } else if (canUsePersonalBankAccount) {
+ buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]);
+ }
+
+ if (activeAdminPolicies.length === 0 && !isPersonalOnlyOption) {
+ buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]);
+ }
+ }
+
+ const shouldShowBusinessBankAccountOptions = isExpenseReport && shouldShowPayWithExpensifyOption && !isPersonalOnlyOption;
+
+ if (shouldShowBusinessBankAccountOptions) {
+ if (!isEmpty(latestBankItem) && latestBankItem) {
+ buttonOptions.push({
+ text: latestBankItem.at(0)?.text ?? '',
+ icon: latestBankItem.at(0)?.icon,
+ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
+ description: latestBankItem.at(0)?.description,
+ });
+ } else {
+ buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]);
+ }
+ }
+
+ if ((hasMultiplePolicies || hasSinglePolicy) && canUseWallet && !isPersonalOnlyOption) {
+ activeAdminPolicies.forEach((activePolicy) => {
+ const policyName = activePolicy.name;
+ buttonOptions.push({
+ text: translate('iou.payWithPolicy', {policyName: truncate(policyName, {length: 20}), formattedAmount: ''}),
+ icon: Expensicons.Building,
+ value: activePolicy.id,
+ shouldUpdateSelectedIndex: false,
+ });
+ });
+ }
+
+ if (shouldShowPayElsewhereOption) {
+ buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
+ }
+
+ if (isInvoiceReport) {
+ const isCurrencySupported = isCurrencySupportedForDirectReimbursement(currency as CurrencyType);
+ const getInvoicesOptions = (payAsBusiness: boolean) => {
+ return [
+ ...(isCurrencySupported ? getPaymentSubitems(payAsBusiness) : []),
+ {
+ text: translate('workspace.invoices.paymentMethods.addBankAccount'),
+ icon: Expensicons.Bank,
+ onSelected: () => Navigation.navigate(addBankAccountRoute),
+ value: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ {
+ text: translate('iou.payElsewhere', {formattedAmount: ''}),
+ icon: Expensicons.Cash,
+ value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
+ shouldUpdateSelectedIndex: true,
+ onSelected: () => {
+ onPress(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, payAsBusiness, undefined);
+ savePreferredPaymentMethod(policyIDKey, CONST.IOU.PAYMENT_TYPE.ELSEWHERE);
+ },
+ },
+ ];
+ };
+
+ if (isIndividualInvoiceRoomUtil(chatReport) || shouldUseShortForm) {
+ buttonOptions.push({
+ text: translate('iou.settlePersonal', {formattedAmount}),
+ icon: Expensicons.User,
+ value: hasIntentToPay ? CONST.IOU.PAYMENT_TYPE.EXPENSIFY : (lastPaymentMethod ?? CONST.IOU.PAYMENT_TYPE.ELSEWHERE),
+ backButtonText: translate('iou.individual'),
+ subMenuItems: getInvoicesOptions(false),
+ });
+ buttonOptions.push({
+ text: translate('iou.settleBusiness', {formattedAmount}),
+ icon: Expensicons.Building,
+ value: hasIntentToPay ? CONST.IOU.PAYMENT_TYPE.EXPENSIFY : (lastPaymentMethod ?? CONST.IOU.PAYMENT_TYPE.ELSEWHERE),
+ backButtonText: translate('iou.business'),
+ subMenuItems: getInvoicesOptions(true),
+ });
+ } else {
+ // If there is pay as business option, we should show the submenu items instead.
+ buttonOptions.push(...getInvoicesOptions(true));
+ }
+ }
+
+ if (shouldShowApproveButton) {
+ buttonOptions.push(approveButtonOption);
+ }
+
+ return buttonOptions;
+ // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not initialized yet.
+ // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates.
+ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
+ }, [
+ isLoadingLastPaymentMethod,
iouReport,
- chatReportID,
+ translate,
formattedAmount,
- policyID,
- onPress,
+ shouldDisableApproveButton,
+ isInvoiceReport,
+ currency,
shouldHidePaymentOptions,
shouldShowApproveButton,
- shouldDisableApproveButton,
+ shouldShowPayWithExpensifyOption,
+ shouldShowPayElsewhereOption,
+ chatReport,
+ onPress,
onlyShowPayElsewhere,
- });
+ latestBankItem,
+ ]);
+
+ const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType) => {
+ if (policy && shouldRestrictUserBillableActions(policy.id)) {
+ Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
+ return;
+ }
- const filteredPaymentOptions = paymentButtonOptions.filter((option) => option.value !== undefined) as Array>;
+ if (iouPaymentType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) {
+ if (confirmApproval) {
+ confirmApproval();
+ } else {
+ approveMoneyRequest(iouReport);
+ }
+ return;
+ }
+ if (isInvoiceReport) {
+ // if user has intent to pay, we should get the only bank account information to pay the invoice.
+ if (hasIntentToPay) {
+ const currentBankInformation = formattedPaymentMethods.at(0) as BankAccount;
+ onPress(
+ CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ currentBankInformation.accountType !== CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
+ currentBankInformation.methodID,
+ currentBankInformation.accountType,
+ undefined,
+ );
+ return;
+ }
- const onPaymentSelect = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
+ const isBusinessInvoice = isBusinessInvoiceRoom(chatReport);
+ if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
+ onPress(iouPaymentType, isBusinessInvoice);
+ return;
+ }
+ onPress(
+ iouPaymentType,
+ isBusinessInvoice,
+ lastBankAccountID,
+ isBusinessInvoice ? CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT : CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
+ policyIDKey,
+ );
+ } else {
+ onPress(iouPaymentType, false);
+ }
+ };
+
+ const selectPaymentMethod = (event: KYCFlowEvent, triggerKYCFlow: TriggerKYCFlow, paymentMethod?: PaymentMethod, selectedPolicy?: Policy) => {
+ if (!isUserValidated) {
+ Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute(Navigation.getActiveRoute()));
+ return;
+ }
+
+ if (policy && shouldRestrictUserBillableActions(policy.id)) {
+ Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
+ return;
+ }
+
+ let paymentType;
+ switch (paymentMethod) {
+ case CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT:
+ paymentType = CONST.IOU.PAYMENT_TYPE.EXPENSIFY;
+ break;
+ case CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT:
+ paymentType = CONST.IOU.PAYMENT_TYPE.VBBA;
+ break;
+ default:
+ paymentType = CONST.IOU.PAYMENT_TYPE.ELSEWHERE;
+ }
+ triggerKYCFlow(event, paymentType, paymentMethod, selectedPolicy ?? lastPaymentPolicy);
+ if (paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || paymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
+ setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS);
+ }
+ };
+
+ const getCustomText = () => {
+ if (shouldUseShortForm) {
+ return translate('iou.pay');
+ }
+
+ if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
+ return translate('iou.payElsewhere', {formattedAmount});
+ }
+
+ return translate('iou.settlePayment', {formattedAmount});
+ };
+
+ const getSecondaryText = (): string | undefined => {
+ if (
+ shouldUseShortForm ||
+ lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE ||
+ (paymentButtonOptions.length === 1 && paymentButtonOptions.every((option) => option.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE)) ||
+ (shouldHidePaymentOptions && (shouldShowApproveButton || onlyShowPayElsewhere))
+ ) {
+ return undefined;
+ }
+
+ if (lastPaymentPolicy) {
+ return lastPaymentPolicy.name;
+ }
+
+ const bankAccountToDisplay = hasIntentToPay ? (formattedPaymentMethods.at(0) as BankAccount) : bankAccount;
+ if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || (hasIntentToPay && isInvoiceReportUtil(iouReport))) {
+ if (bankAccountToDisplay && isInvoiceReportUtil(iouReport)) {
+ const translationKey = bankAccountToDisplay.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS ? 'iou.invoiceBusinessBank' : 'iou.invoicePersonalBank';
+ return translate(translationKey, {lastFour: bankAccountToDisplay?.accountData?.accountNumber?.slice(-4) ?? ''});
+ }
+
+ if (!personalBankAccountList.length) {
+ return;
+ }
+
+ return translate('common.wallet');
+ }
+
+ if ((lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.VBBA || hasIntentToPay) && !!policy?.achAccount) {
+ if (policy?.achAccount?.accountNumber) {
+ return translate('paymentMethodList.bankAccountLastFour', {lastFour: policy?.achAccount?.accountNumber?.slice(-4)});
+ }
+
+ if (!bankAccountToDisplay?.accountData?.accountNumber) {
+ return;
+ }
+
+ return translate('paymentMethodList.bankAccountLastFour', {lastFour: bankAccountToDisplay?.accountData?.accountNumber?.slice(-4)});
+ }
+
+ if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && isExpenseReportUtil(iouReport)) {
+ return translate('paymentMethodList.bankAccountLastFour', {lastFour: bankAccount?.accountData?.accountNumber?.slice(-4) ?? ''});
+ }
+
+ return undefined;
+ };
+
+ const handlePaymentSelection = (
+ event: GestureResponderEvent | KeyboardEvent | undefined,
+ selectedOption: PaymentMethodType | PaymentMethod,
+ triggerKYCFlow: (event: GestureResponderEvent | KeyboardEvent | undefined, method?: PaymentMethodType) => void,
+ ) => {
if (isAccountLocked) {
showLockedAccountModal();
return;
}
- selectPaymentType(event, iouPaymentType, triggerKYCFlow, policy, onPress, isUserValidated, confirmApproval, iouReport);
- };
- const savePreferredPaymentMethod = (id: string, value: PaymentMethodType) => {
- savePreferredPaymentMethodIOU(id, value, undefined);
+ const isPaymentMethod = Object.values(CONST.PAYMENT_METHODS).includes(selectedOption as PaymentMethod);
+ const shouldSelectPaymentMethod = (isPaymentMethod ?? lastPaymentPolicy ?? !isEmpty(latestBankItem)) && !shouldShowApproveButton && !shouldHidePaymentOptions;
+ const selectedPolicy = activeAdminPolicies.find((activePolicy) => activePolicy.id === selectedOption);
+
+ if (!!selectedPolicy || shouldSelectPaymentMethod) {
+ selectPaymentMethod(event, triggerKYCFlow, selectedOption as PaymentMethod, selectedPolicy);
+ return;
+ }
+
+ selectPaymentType(event, selectedOption as PaymentMethodType);
};
+ const customText = getCustomText();
+ const secondaryText = getSecondaryText();
+
+ const defaultSelectedIndex = paymentButtonOptions.findIndex((paymentOption) => {
+ if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) {
+ return paymentOption.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE;
+ }
+
+ if (latestBankItem?.length) {
+ return paymentOption.value === latestBankItem.at(0)?.value;
+ }
+
+ if (lastPaymentPolicy?.id) {
+ return paymentOption.value === lastPaymentPolicy.id;
+ }
+
+ return false;
+ });
+
+ const shouldUseSplitButton = hasPreferredPaymentMethod || !!lastPaymentPolicy || ((isInvoiceReport || isExpenseReportUtil(iouReport)) && hasIntentToPay);
+
return (
onPress(paymentType)}
+ onSuccessfulKYC={(paymentType) => onPress(paymentType, undefined, undefined)}
enablePaymentsRoute={enablePaymentsRoute}
addBankAccountRoute={addBankAccountRoute}
addDebitCardRoute={addDebitCardRoute}
@@ -106,33 +552,32 @@ function SettlementButton({
source={CONST.KYC_WALL_SOURCE.REPORT}
chatReportID={chatReportID}
iouReport={iouReport}
+ policy={lastPaymentPolicy}
anchorAlignment={kycWallAnchorAlignment}
shouldShowPersonalBankAccountOption={shouldShowPersonalBankAccountOption}
>
{(triggerKYCFlow, buttonRef) => (
-
+
onOptionsMenuShow={onPaymentOptionsShow}
onOptionsMenuHide={onPaymentOptionsHide}
buttonRef={buttonRef}
shouldAlwaysShowDropdownMenu={isInvoiceReport && !onlyShowPayElsewhere}
- customText={isInvoiceReport ? translate('iou.settlePayment', {formattedAmount}) : undefined}
+ customText={customText}
menuHeaderText={isInvoiceReport ? translate('workspace.invoices.paymentMethods.chooseInvoiceMethod') : undefined}
- isSplitButton={!isInvoiceReport}
+ isSplitButton={shouldUseSplitButton}
isDisabled={isDisabled}
isLoading={isLoading}
- onPress={(event, iouPaymentType) => {
- onPaymentSelect(event, iouPaymentType, triggerKYCFlow);
- }}
+ defaultSelectedIndex={defaultSelectedIndex !== -1 ? defaultSelectedIndex : 0}
+ onPress={(event, iouPaymentType) => handlePaymentSelection(event, iouPaymentType, triggerKYCFlow)}
+ success={!hasOnlyHeldExpenses}
+ secondLineText={secondaryText}
pressOnEnter={pressOnEnter}
- options={filteredPaymentOptions}
- onOptionSelected={(option) => {
- if (policyID === '-1') {
- return;
- }
- savePreferredPaymentMethod(policyIDKey, option.value);
- }}
+ options={paymentButtonOptions}
+ onOptionSelected={(option) => handlePaymentSelection(undefined, option.value, triggerKYCFlow)}
style={style}
- wrapperStyle={wrapperStyle}
+ shouldPopoverUseScrollView={paymentButtonOptions.length > 5}
+ containerStyles={paymentButtonOptions.length > 5 ? styles.settlementButtonListContainer : {}}
+ wrapperStyle={[wrapperStyle, shouldUseShortForm && shouldUseSplitButton ? {minWidth: 90} : {}]}
disabledStyle={disabledStyle}
buttonSize={buttonSize}
anchorAlignment={paymentMethodDropdownAnchorAlignment}
diff --git a/src/components/SettlementButton/types.ts b/src/components/SettlementButton/types.ts
index 9fe5758ea048..212be067e01e 100644
--- a/src/components/SettlementButton/types.ts
+++ b/src/components/SettlementButton/types.ts
@@ -12,7 +12,7 @@ type EnablePaymentsRoute = typeof ROUTES.ENABLE_PAYMENTS | typeof ROUTES.IOU_SEN
type SettlementButtonProps = {
/** Callback to execute when this button is pressed. Receives a single payment type argument. */
- onPress: (paymentType?: PaymentMethodType, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => void;
+ onPress: (paymentType: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod | undefined, policyID?: string) => void;
/** Callback when the payment options popover is shown */
onPaymentOptionsShow?: () => void;
@@ -94,6 +94,12 @@ type SettlementButtonProps = {
/** Whether we only show pay elsewhere button */
onlyShowPayElsewhere?: boolean;
+
+ /** Whether to use short form for the button */
+ shouldUseShortForm?: boolean;
+
+ /** Whether we the report has only held expenses */
+ hasOnlyHeldExpenses?: boolean;
};
export default SettlementButtonProps;
diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx
index 7bed78327133..8d6ee8e3ba04 100644
--- a/src/components/TransactionItemRow/index.tsx
+++ b/src/components/TransactionItemRow/index.tsx
@@ -80,6 +80,9 @@ type TransactionWithOptionalSearchFields = TransactionWithOptionalHighlight & {
/** Precomputed violations */
violations?: TransactionViolation[];
+
+ /** Used to initiate payment from search page */
+ hash?: number;
};
type TransactionItemRowProps = {
@@ -277,6 +280,9 @@ function TransactionItemRow({
parentAction={transactionItem.parentTransactionID}
goToItem={onButtonPress}
isLoading={isActionLoading}
+ reportID={transactionItem.reportID}
+ hash={transactionItem.hash}
+ amount={transactionItem.amount}
/>
)}
diff --git a/src/hooks/usePaymentOptions.ts b/src/hooks/usePaymentOptions.ts
index 65f3a26b30a2..adf45515700b 100644
--- a/src/hooks/usePaymentOptions.ts
+++ b/src/hooks/usePaymentOptions.ts
@@ -3,7 +3,6 @@ import {useOnyx} from 'react-native-onyx';
import type {TupleToUnion} from 'type-fest';
import * as Expensicons from '@components/Icon/Expensicons';
import type SettlementButtonProps from '@components/SettlementButton/types';
-import type {PaymentOrApproveOption} from '@libs/PaymentUtils';
import {formatPaymentMethods} from '@libs/PaymentUtils';
import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils';
import {
@@ -56,7 +55,7 @@ function usePaymentOptions({
shouldShowApproveButton = false,
shouldDisableApproveButton = false,
onlyShowPayElsewhere,
-}: UsePaymentOptionsProps): PaymentOrApproveOption[] {
+}: UsePaymentOptionsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -78,6 +77,8 @@ function usePaymentOptions({
return (paymentMethod?.[policyIDKey] as LastPaymentMethodType)?.lastUsed.name;
},
});
+ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
+ const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? '');
const isLoadingLastPaymentMethod = isLoadingOnyxValue(lastPaymentMethodResult);
const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
@@ -99,15 +100,17 @@ function usePaymentOptions({
const buttonOptions = [];
const isExpenseReport = isExpenseReportUtil(iouReport);
const paymentMethods = {
- [CONST.IOU.PAYMENT_TYPE.EXPENSIFY]: {
- text: translate('iou.settleExpensify', {formattedAmount}),
- icon: Expensicons.Wallet,
- value: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ [CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]: {
+ text: hasActivatedWallet ? translate('iou.settleWallet', {formattedAmount: ''}) : translate('iou.settlePersonal', {formattedAmount: ''}),
+ icon: Expensicons.User,
+ value: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT,
+ shouldUpdateSelectedIndex: false,
},
- [CONST.IOU.PAYMENT_TYPE.VBBA]: {
- text: translate('iou.settleExpensify', {formattedAmount}),
- icon: Expensicons.Wallet,
- value: CONST.IOU.PAYMENT_TYPE.VBBA,
+ [CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]: {
+ text: translate('iou.settleBusiness', {formattedAmount: ''}),
+ icon: Expensicons.Building,
+ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT,
+ shouldUpdateSelectedIndex: false,
},
[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]: {
text: translate('iou.payElsewhere', {formattedAmount}),
@@ -134,10 +137,10 @@ function usePaymentOptions({
// To achieve the one tap pay experience we need to choose the correct payment type as default.
if (canUseWallet) {
- buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.EXPENSIFY]);
+ buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]);
}
if (isExpenseReport && shouldShowPayWithExpensifyOption) {
- buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.VBBA]);
+ buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]);
}
if (shouldShowPayElsewhereOption) {
buttonOptions.push(paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]);
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 17f809e6b899..53ad9cafa47c 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1110,10 +1112,18 @@ const translations = {
individual: 'Individuum',
business: 'Geschäft',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Bezahle ${formattedAmount} mit Expensify` : `Mit Expensify bezahlen`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zahlen Sie ${formattedAmount} als Einzelperson` : `Als Einzelperson bezahlen`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Bezahle ${formattedAmount} als Privatperson` : `Mit Privatkonto bezahlen`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Bezahle ${formattedAmount} mit Wallet` : `Mit Wallet bezahlen`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Zahlen Sie ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zahlen Sie ${formattedAmount} als Unternehmen` : `Als Unternehmen bezahlen`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zahle ${formattedAmount} anderswo` : `Anderswo bezahlen`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Bezahle ${formattedAmount} als Unternehmen` : `Mit Geschäftskonto bezahlen`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount} als bezahlt markieren` : `Als bezahlt markieren`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `${amount} mit Privatkonto ${last4Digits} bezahlt` : `Mit Privatkonto bezahlt`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `${amount} mit Geschäftskonto ${last4Digits} bezahlt` : `Mit Geschäftskonto bezahlt`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `${formattedAmount} über ${policyName} bezahlen` : `Über ${policyName} bezahlen`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `${amount} mit Bankkonto ${last4Digits} bezahlt.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Privatkonto • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Geschäftskonto • ${lastFour}`,
nextStep: 'Nächste Schritte',
finished: 'Fertiggestellt',
flip: 'Umkehren',
@@ -1150,8 +1160,8 @@ const translations = {
`hat die Zahlung von ${amount} storniert, weil ${submitterDisplayName} ihre Expensify Wallet nicht innerhalb von 30 Tagen aktiviert hat`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} hat ein Bankkonto hinzugefügt. Die Zahlung von ${amount} wurde geleistet.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}woanders bezahlt`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''} mit Expensify bezahlt`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}als bezahlt markiert`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}mit Wallet bezahlt`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''} mit Expensify über Arbeitsbereichsregeln bezahlt`,
noReimbursableExpenses: 'Dieser Bericht hat einen ungültigen Betrag.',
@@ -1810,6 +1820,7 @@ const translations = {
enableWallet: 'Wallet aktivieren',
addBankAccountToSendAndReceive: 'Erhalten Sie eine Rückerstattung für Ausgaben, die Sie an einen Arbeitsbereich einreichen.',
addBankAccount: 'Bankkonto hinzufügen',
+ addDebitOrCreditCard: 'Debit- oder Kreditkarte hinzufügen',
assignedCards: 'Zugewiesene Karten',
assignedCardsDescription: 'Dies sind Karten, die von einem Workspace-Admin zugewiesen wurden, um die Ausgaben des Unternehmens zu verwalten.',
expensifyCard: 'Expensify Card',
@@ -2023,6 +2034,7 @@ const translations = {
cardLastFour: 'Karte endet mit',
addFirstPaymentMethod: 'Fügen Sie eine Zahlungsmethode hinzu, um Zahlungen direkt in der App zu senden und zu empfangen.',
defaultPaymentMethod: 'Standardmäßig',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Bankkonto • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 828b896e0372..caebeb851a7e 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -22,6 +22,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -32,6 +33,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1095,10 +1097,18 @@ const translations = {
individual: 'Individual',
business: 'Business',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with Expensify` : `Pay with Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as an individual` : `Pay as an individual`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as an individual` : `Pay with personal account`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} with wallet` : `Pay with wallet`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as a business` : `Pay as a business`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} elsewhere` : `Pay elsewhere`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pay ${formattedAmount} as a business` : `Pay with business account`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Mark ${formattedAmount} as paid` : `Mark as paid`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Paid ${amount} with personal account ${last4Digits}` : `Paid with personal account`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Paid ${amount} with business account ${last4Digits}` : `Paid with business account`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Pay ${formattedAmount} via ${policyName}` : `Pay via ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Paid ${amount} with bank account ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Personal account • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Business Account • ${lastFour}`,
nextStep: 'Next steps',
finished: 'Finished',
flip: 'Flip',
@@ -1134,8 +1144,8 @@ const translations = {
`canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} added a bank account. The ${amount} payment has been made.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}paid elsewhere`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}paid with Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}marked as paid`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}paid with wallet`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}paid with Expensify via workspace rules`,
noReimbursableExpenses: 'This report has an invalid amount',
@@ -1786,6 +1796,7 @@ const translations = {
enableWallet: 'Enable wallet',
addBankAccountToSendAndReceive: 'Get paid back for expenses you submit to a workspace.',
addBankAccount: 'Add bank account',
+ addDebitOrCreditCard: 'Add debit or credit card',
assignedCards: 'Assigned cards',
assignedCardsDescription: 'These are cards assigned by a workspace admin to manage company spend.',
expensifyCard: 'Expensify Card',
@@ -1995,6 +2006,7 @@ const translations = {
cardLastFour: 'Card ending in',
addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app.',
defaultPaymentMethod: 'Default',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Bank Account • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index bfd280f6f740..5bdb55632e50 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -21,6 +21,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -31,6 +32,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1090,10 +1092,18 @@ const translations = {
individual: 'Individual',
business: 'Empresa',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} con Expensify` : `Pagar con Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pago ${formattedAmount} como individuo` : `Pago individual`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pago ${formattedAmount} como individuo` : `Pago con cuenta personal`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} con billetera` : `con billetera`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como negocio` : `Pagar como empresa`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} de otra forma` : `Pagar de otra forma`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como negocio` : `Pago con cuenta empresarial`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Marcar ${formattedAmount} como pagado` : `Marcar como pagado`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pagado ${amount} con cuenta personal ${last4Digits}` : `Pagado con cuenta personal`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pagado ${amount} con cuenta de empresa ${last4Digits}` : `Pagado con cuenta de empresa`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Pay ${formattedAmount} via ${policyName}` : `Pay via ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Pagó ${amount} con la cuenta bancaria ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Cuenta personal • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Cuenta de empresa • ${lastFour}`,
nextStep: 'Pasos siguientes',
finished: 'Finalizado',
flip: 'Cambiar',
@@ -1129,8 +1139,8 @@ const translations = {
`canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó tu Billetera Expensify en un plazo de 30 días.`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}pagó de otra forma`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}pagó con Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}marcó como pagado`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}pagó con la billetera`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}pagó con Expensify via reglas del espacio de trabajo`,
noReimbursableExpenses: 'El importe de este informe no es válido',
@@ -1785,6 +1795,7 @@ const translations = {
enableWallet: 'Habilitar billetera',
addBankAccountToSendAndReceive: 'Recibe el reembolso de los gastos que envíes a un espacio de trabajo.',
addBankAccount: 'Añadir cuenta bancaria',
+ addDebitOrCreditCard: 'Añadir tarjeta de débito o crédito',
assignedCards: 'Tarjetas asignadas',
assignedCardsDescription: 'Son tarjetas asignadas por un administrador del espacio de trabajo para gestionar los gastos de la empresa.',
expensifyCard: 'Tarjeta Expensify',
@@ -1995,6 +2006,7 @@ const translations = {
cardLastFour: 'Tarjeta terminada en',
addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación.',
defaultPaymentMethod: 'Predeterminado',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Cuenta bancaria • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 1d537a9e24d8..5aedfe2f77b5 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1110,10 +1112,19 @@ const translations = {
individual: 'Individuel',
business: 'Entreprise',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} avec Expensify` : `Payer avec Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} en tant qu'individu` : `Payer en tant qu'individu`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} en tant qu'individu` : `Payer avec un compte personnel`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} avec le portefeuille` : `Payer avec le portefeuille`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Payer ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} en tant qu'entreprise` : `Payer en tant qu'entreprise`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} ailleurs` : `Payer ailleurs`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Payer ${formattedAmount} en tant qu'entreprise` : `Payer avec un compte professionnel`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Marquer ${formattedAmount} comme payé` : `Marquer comme payé`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Payé ${amount} avec le compte personnel ${last4Digits}` : `Payé avec le compte personnel`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) =>
+ amount ? `Payé ${amount} avec le compte professionnel ${last4Digits}` : `Payé avec le compte professionnel`,
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Payer ${formattedAmount} via ${policyName}` : `Payer via ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Payé ${amount} avec le compte bancaire ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Compte personnel • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Compte professionnel • ${lastFour}`,
nextStep: 'Étapes suivantes',
finished: 'Terminé',
flip: 'Inverser',
@@ -1150,8 +1161,8 @@ const translations = {
`a annulé le paiement de ${amount}, car ${submitterDisplayName} n'a pas activé leur Expensify Wallet dans les 30 jours`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} a ajouté un compte bancaire. Le paiement de ${amount} a été effectué.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''} payé ailleurs`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''} payé avec Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}marqué comme payé`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}payé avec le portefeuille`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''} payé avec Expensify via les règles de l'espace de travail`,
noReimbursableExpenses: 'Ce rapport contient un montant invalide',
@@ -1809,6 +1820,7 @@ const translations = {
enableWallet: 'Activer le portefeuille',
addBankAccountToSendAndReceive: 'Soyez remboursé pour les dépenses que vous soumettez à un espace de travail.',
addBankAccount: 'Ajouter un compte bancaire',
+ addDebitOrCreditCard: 'Ajouter une carte de débit ou de crédit',
assignedCards: 'Cartes assignées',
assignedCardsDescription: "Ce sont des cartes attribuées par un administrateur d'espace de travail pour gérer les dépenses de l'entreprise.",
expensifyCard: 'Expensify Card',
@@ -2023,6 +2035,7 @@ const translations = {
cardLastFour: 'Carte se terminant par',
addFirstPaymentMethod: "Ajoutez un mode de paiement pour envoyer et recevoir des paiements directement dans l'application.",
defaultPaymentMethod: 'Par défaut',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Bank Account • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 18eac3b7032f..ee1c90ff73f9 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1106,10 +1108,18 @@ const translations = {
individual: 'Individuale',
business: 'Business',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} con Expensify` : `Paga con Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} come individuo` : `Paga come individuo`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} come individuo` : `Paga con conto personale`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} con portafoglio` : `Paga con portafoglio`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Paga ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} come azienda` : `Paga come un'azienda`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} altrove` : `Paga altrove`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Paga ${formattedAmount} come azienda` : `Paga con conto aziendale`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Segna ${formattedAmount} come pagato` : `Segna come pagato`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pagato ${amount} con conto personale ${last4Digits}` : `Pagato con conto personale`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pagato ${amount} con conto aziendale ${last4Digits}` : `Pagato con conto aziendale`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Paga ${formattedAmount} tramite ${policyName}` : `Paga tramite ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Pagato ${amount} con conto bancario ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Conto personale • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Conto aziendale • ${lastFour}`,
nextStep: 'Prossimi passi',
finished: 'Finito',
flip: 'Inverti',
@@ -1146,8 +1156,8 @@ const translations = {
`annullato il pagamento di ${amount}, perché ${submitterDisplayName} non ha attivato il loro Expensify Wallet entro 30 giorni`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} ha aggiunto un conto bancario. Il pagamento di ${amount} è stato effettuato.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}pagato altrove`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''} pagato con Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}segnato come pagato`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}pagato con portafoglio`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''} ha pagato con Expensify tramite regole dello spazio di lavoro`,
noReimbursableExpenses: 'Questo rapporto ha un importo non valido',
@@ -1802,6 +1812,7 @@ const translations = {
enableWallet: 'Abilita portafoglio',
addBankAccountToSendAndReceive: "Ricevi il rimborso per le spese che invii a un'area di lavoro.",
addBankAccount: 'Aggiungi conto bancario',
+ addDebitOrCreditCard: 'Aggiungi carta di debito o di credito',
assignedCards: 'Carte assegnate',
assignedCardsDescription: 'Queste sono carte assegnate da un amministratore del workspace per gestire le spese aziendali.',
expensifyCard: 'Expensify Card',
@@ -2014,6 +2025,7 @@ const translations = {
cardLastFour: 'Carta che termina con',
addFirstPaymentMethod: "Aggiungi un metodo di pagamento per inviare e ricevere pagamenti direttamente nell'app.",
defaultPaymentMethod: 'Predefinito',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Conto bancario • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 19a5c6fc819f..31c80dc851e4 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1109,10 +1111,18 @@ const translations = {
individual: '個人',
business: 'ビジネス',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Expensifyで${formattedAmount}を支払う` : `Expensifyで支払う`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `個人として${formattedAmount}を支払う` : `個人として支払う`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount}を個人として支払う` : `個人口座で支払う`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `ウォレットで${formattedAmount}を支払う` : `ウォレットで支払う`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `${formattedAmount}を支払う`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount} をビジネスとして支払う` : `ビジネスとして支払う`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `他の場所で${formattedAmount}を支払う` : `他の場所で支払う`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount}をビジネスとして支払う` : `ビジネス口座で支払う`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount}を支払い済みにマーク` : `支払い済みにマーク`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `${amount}を個人口座(${last4Digits})で支払い済み` : `個人口座で支払い済み`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `${amount}をビジネス口座(${last4Digits})で支払い済み` : `ビジネス口座で支払い済み`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `${policyName}経由で${formattedAmount}を支払う` : `${policyName}経由で支払う`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `${amount}を銀行口座(${last4Digits})で支払い済み。`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `個人口座・${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `ビジネス口座・${lastFour}`,
nextStep: '次のステップ',
finished: '完了',
flip: '反転',
@@ -1148,8 +1158,8 @@ const translations = {
`${submitterDisplayName}が30日以内にExpensifyウォレットを有効にしなかったため、${amount}の支払いをキャンセルしました。`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName}が銀行口座を追加しました。${amount}の支払いが行われました。`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}は他で支払われました`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}はExpensifyで支払いました`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}支払い済みにマークされました`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}ウォレットで支払い済み`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}はワークスペースルールを通じてExpensifyで支払いました。`,
noReimbursableExpenses: 'このレポートには無効な金額が含まれています',
@@ -1799,6 +1809,7 @@ const translations = {
enableWallet: 'ウォレットを有効にする',
addBankAccountToSendAndReceive: 'ワークスペースに提出した経費の払い戻しを受ける。',
addBankAccount: '銀行口座を追加',
+ addDebitOrCreditCard: 'デビットカードまたはクレジットカードを追加',
assignedCards: '割り当てられたカード',
assignedCardsDescription: 'これらは、会社の支出を管理するためにワークスペース管理者によって割り当てられたカードです。',
expensifyCard: 'Expensify Card',
@@ -2007,6 +2018,7 @@ const translations = {
cardLastFour: '末尾が',
addFirstPaymentMethod: 'アプリ内で直接送受金を行うために支払い方法を追加してください。',
defaultPaymentMethod: 'デフォルト',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `銀行口座・${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 77a245b2732c..33eaf9664459 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1107,10 +1109,19 @@ const translations = {
individual: 'Individuueel',
business: 'Business',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} met Expensify` : `Betaal met Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} als individu` : `Betaal als individu`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} als individu` : `Betalen met persoonlijke rekening`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} met wallet` : `Betalen met wallet`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Betaal ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} als een bedrijf` : `Betalen als een bedrijf`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} ergens anders` : `Elders betalen`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Betaal ${formattedAmount} als bedrijf` : `Betalen met zakelijke rekening`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `${formattedAmount} als betaald markeren` : `Markeren als betaald`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) =>
+ amount ? `${amount} betaald met persoonlijke rekening ${last4Digits}` : `Betaald met persoonlijke rekening`,
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `${amount} betaald met zakelijke rekening ${last4Digits}` : `Betaald met zakelijke rekening`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Betaal ${formattedAmount} via ${policyName}` : `Betalen via ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `${amount} betaald via bankrekening ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Persoonlijke rekening • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Zakelijke rekening • ${lastFour}`,
nextStep: 'Volgende stappen',
finished: 'Voltooid',
flip: 'Omdraaien',
@@ -1147,8 +1158,8 @@ const translations = {
`heeft de betaling van ${amount} geannuleerd, omdat ${submitterDisplayName} hun Expensify Wallet niet binnen 30 dagen heeft geactiveerd.`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} heeft een bankrekening toegevoegd. De betaling van ${amount} is gedaan.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''} elders betaald`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}betaald met Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}gemarkeerd als betaald`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}betaald met wallet`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}betaald met Expensify via werkruimte regels`,
noReimbursableExpenses: 'Dit rapport heeft een ongeldig bedrag.',
@@ -1802,6 +1813,7 @@ const translations = {
enableWallet: 'Portemonnee inschakelen',
addBankAccountToSendAndReceive: 'Word terugbetaald voor uitgaven die je indient bij een werkruimte.',
addBankAccount: 'Bankrekening toevoegen',
+ addDebitOrCreditCard: 'Debet- of creditcard toevoegen',
assignedCards: 'Toegewezen kaarten',
assignedCardsDescription: 'Dit zijn kaarten die door een werkruimtebeheerder zijn toegewezen om de uitgaven van het bedrijf te beheren.',
expensifyCard: 'Expensify Card',
@@ -2014,6 +2026,7 @@ const translations = {
cardLastFour: 'Kaart eindigend op',
addFirstPaymentMethod: 'Voeg een betaalmethode toe om betalingen direct in de app te verzenden en ontvangen.',
defaultPaymentMethod: 'Standaard',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Bankrekening • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/params.ts b/src/languages/params.ts
index 9d5e3ef201fa..eaad9da6abbd 100644
--- a/src/languages/params.ts
+++ b/src/languages/params.ts
@@ -137,6 +137,11 @@ type SettleExpensifyCardParams = {
formattedAmount: string;
};
+type BusinessBankAccountParams = {
+ amount: string;
+ last4Digits: string;
+};
+
type RequestAmountParams = {amount: string};
type RequestedAmountMessageParams = {formattedAmount: string; comment?: string};
@@ -197,6 +202,8 @@ type TransferParams = {amount: string};
type InstantSummaryParams = {rate: string; minAmount: string};
+type BankAccountLastFourParams = {lastFour: string};
+
type NotYouParams = {user: string};
type DateShouldBeBeforeParams = {dateString: string};
@@ -1028,6 +1035,7 @@ export type {
SettlementDateParams,
PolicyExpenseChatNameParams,
YourPlanPriceValueParams,
+ BusinessBankAccountParams,
NeedCategoryForExportToIntegrationParams,
UpdatedPolicyAuditRateParams,
UpdatedPolicyManualApprovalThresholdParams,
@@ -1041,6 +1049,7 @@ export type {
UpdatedPolicyCategoryExpenseLimitTypeParams,
UpdatedPolicyCategoryMaxAmountNoReceiptParams,
SubscriptionSettingsSummaryParams,
+ BankAccountLastFourParams,
ReviewParams,
CreateExpensesParams,
CurrencyInputDisabledTextParams,
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 00cf070f8f1c..80cd84776b08 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1105,10 +1107,18 @@ const translations = {
individual: 'Indywidualny',
business: 'Biznes',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} za pomocą Expensify` : `Zapłać z Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} jako osoba prywatna` : `Płać jako osoba prywatna`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} jako osoba prywatna` : `Zapłać z konta osobistego`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} portfelem` : `Zapłać portfelem`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Zapłać ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} jako firma` : `Płać jako firma`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} gdzie indziej` : `Zapłać gdzie indziej`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Zapłać ${formattedAmount} jako firma` : `Zapłać z konta firmowego`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Oznacz ${formattedAmount} jako zapłacone` : `Oznacz jako zapłacone`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Zapłacono ${amount} z konta osobistego ${last4Digits}` : `Zapłacono z konta osobistego`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Zapłacono ${amount} z konta firmowego ${last4Digits}` : `Zapłacono z konta firmowego`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Zapłać ${formattedAmount} przez ${policyName}` : `Zapłać przez ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Zapłacono ${amount} z konta bankowego ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Konto osobiste • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Konto firmowe • ${lastFour}`,
nextStep: 'Następne kroki',
finished: 'Zakończono',
flip: 'Odwróć',
@@ -1145,8 +1155,8 @@ const translations = {
`anulowano płatność w wysokości ${amount}, ponieważ ${submitterDisplayName} nie aktywował swojego Portfela Expensify w ciągu 30 dni`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} dodał konto bankowe. Płatność w wysokości ${amount} została dokonana.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}zapłacono gdzie indziej`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}zapłacono za pomocą Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}oznaczono jako zapłacone`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}zapłacono portfelem`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}zapłacono z Expensify za pomocą zasad przestrzeni roboczej`,
noReimbursableExpenses: 'Ten raport ma nieprawidłową kwotę',
@@ -1798,6 +1808,7 @@ const translations = {
enableWallet: 'Włącz portfel',
addBankAccountToSendAndReceive: 'Otrzymaj zwrot kosztów za wydatki, które zgłaszasz do przestrzeni roboczej.',
addBankAccount: 'Dodaj konto bankowe',
+ addDebitOrCreditCard: 'Dodaj kartę debetową lub kredytową',
assignedCards: 'Przypisane karty',
assignedCardsDescription: 'Są to karty przypisane przez administratora przestrzeni roboczej do zarządzania wydatkami firmy.',
expensifyCard: 'Expensify Card',
@@ -2010,6 +2021,7 @@ const translations = {
cardLastFour: 'Karta kończąca się na',
addFirstPaymentMethod: 'Dodaj metodę płatności, aby wysyłać i odbierać płatności bezpośrednio w aplikacji.',
defaultPaymentMethod: 'Domyślny',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Konto bankowe • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 7a00e4bb7e71..072349df2bfe 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1107,10 +1109,18 @@ const translations = {
individual: 'Individual',
business: 'Negócio',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pague ${formattedAmount} com Expensify` : `Pague com Expensify`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como indivíduo` : `Pagar como indivíduo`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como indivíduo` : `Pagar com conta pessoal`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} com carteira` : `Pagar com carteira`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pague ${formattedAmount} como uma empresa` : `Pagar como empresa`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pague ${formattedAmount} em outro lugar` : `Pague em outro lugar`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Pagar ${formattedAmount} como empresa` : `Pagar com conta empresarial`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `Marcar ${formattedAmount} como pago` : `Marcar como pago`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pago ${amount} com conta pessoal ${last4Digits}` : `Pago com conta pessoal`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `Pago ${amount} com conta empresarial ${last4Digits}` : `Pago com conta empresarial`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `Pagar ${formattedAmount} via ${policyName}` : `Pagar via ${policyName}`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `Pago ${amount} com conta bancária ${last4Digits}.`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `Conta pessoal • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `Conta empresarial • ${lastFour}`,
nextStep: 'Próximos passos',
finished: 'Concluído',
flip: 'Inverter',
@@ -1147,8 +1157,8 @@ const translations = {
`cancelou o pagamento de ${amount}, porque ${submitterDisplayName} não ativou sua Expensify Wallet dentro de 30 dias`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) =>
`${submitterDisplayName} adicionou uma conta bancária. O pagamento de ${amount} foi realizado.`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''} pago em outro lugar`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''} pagou com Expensify`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}marcado como pago`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}pago com carteira`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''} pagou com Expensify via regras do workspace`,
noReimbursableExpenses: 'Este relatório possui um valor inválido',
@@ -1801,6 +1811,7 @@ const translations = {
enableWallet: 'Ativar carteira',
addBankAccountToSendAndReceive: 'Receba reembolso pelas despesas que você enviar para um espaço de trabalho.',
addBankAccount: 'Adicionar conta bancária',
+ addDebitOrCreditCard: 'Adicionar cartão de débito ou crédito',
assignedCards: 'Cartões atribuídos',
assignedCardsDescription: 'Estes são cartões atribuídos por um administrador de espaço de trabalho para gerenciar os gastos da empresa.',
expensifyCard: 'Expensify Card',
@@ -2013,6 +2024,7 @@ const translations = {
cardLastFour: 'Cartão terminando em',
addFirstPaymentMethod: 'Adicione um método de pagamento para enviar e receber pagamentos diretamente no aplicativo.',
defaultPaymentMethod: 'Padrão',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `Conta bancária • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 7809a94530b0..69cb31bb0fc1 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -34,6 +34,7 @@ import type {
AuthenticationErrorParams,
AutoPayApprovedReportsLimitErrorParams,
BadgeFreeTrialParams,
+ BankAccountLastFourParams,
BeginningOfChatHistoryAdminRoomPartOneParams,
BeginningOfChatHistoryAnnounceRoomPartOneParams,
BeginningOfChatHistoryDomainRoomPartOneParams,
@@ -44,6 +45,7 @@ import type {
BillingBannerInsufficientFundsParams,
BillingBannerOwnerAmountOwedOverdueParams,
BillingBannerSubtitleWithDateParams,
+ BusinessBankAccountParams,
BusinessTaxIDParams,
CanceledRequestParams,
CardEndingParams,
@@ -1097,10 +1099,18 @@ const translations = {
individual: '个人',
business: '商务',
settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `使用 Expensify 支付 ${formattedAmount}` : `使用Expensify支付`),
- settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `以个人身份支付${formattedAmount}` : `以个人身份支付`),
+ settlePersonal: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `以个人身份支付${formattedAmount}` : `用个人账户支付`),
+ settleWallet: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `用钱包支付${formattedAmount}` : `用钱包支付`),
settlePayment: ({formattedAmount}: SettleExpensifyCardParams) => `支付 ${formattedAmount}`,
- settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `以企业身份支付${formattedAmount}` : `以企业身份支付`),
- payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `在其他地方支付${formattedAmount}` : `在其他地方支付`),
+ settleBusiness: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `以企业身份支付${formattedAmount}` : `用企业账户支付`),
+ payElsewhere: ({formattedAmount}: SettleExpensifyCardParams) => (formattedAmount ? `标记${formattedAmount}为已支付` : `标记为已支付`),
+ settleInvoicePersonal: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `已用个人账户${last4Digits}支付${amount}` : `已用个人账户支付`),
+ settleInvoiceBusiness: ({amount, last4Digits}: BusinessBankAccountParams) => (amount ? `已用企业账户${last4Digits}支付${amount}` : `已用企业账户支付`),
+ payWithPolicy: ({formattedAmount, policyName}: SettleExpensifyCardParams & {policyName: string}) =>
+ formattedAmount ? `通过${policyName}支付${formattedAmount}` : `通过${policyName}支付`,
+ businessBankAccount: ({amount, last4Digits}: BusinessBankAccountParams) => `已用银行账户${last4Digits}支付${amount}。`,
+ invoicePersonalBank: ({lastFour}: BankAccountLastFourParams) => `个人账户 • ${lastFour}`,
+ invoiceBusinessBank: ({lastFour}: BankAccountLastFourParams) => `企业账户 • ${lastFour}`,
nextStep: '下一步',
finished: '完成',
flip: '翻转',
@@ -1134,8 +1144,8 @@ const translations = {
adminCanceledRequest: ({manager}: AdminCanceledRequestParams) => `${manager ? `${manager}: ` : ''}取消了付款`,
canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `取消了${amount}付款,因为${submitterDisplayName}在30天内未启用他们的Expensify Wallet。`,
settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => `${submitterDisplayName} 添加了一个银行账户。${amount} 付款已完成。`,
- paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}在其他地方支付`,
- paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}通过Expensify支付`,
+ paidElsewhere: ({payer}: PaidElsewhereParams = {}) => `${payer ? `${payer} ` : ''}已标记为已支付`,
+ paidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) => `${payer ? `${payer} ` : ''}已用钱包支付`,
automaticallyPaidWithExpensify: ({payer}: PaidWithExpensifyParams = {}) =>
`${payer ? `${payer} ` : ''}通过工作区规则使用Expensify支付`,
noReimbursableExpenses: '此报告的金额无效',
@@ -1783,6 +1793,7 @@ const translations = {
enableWallet: '启用钱包',
addBankAccountToSendAndReceive: '获得报销您提交到工作区的费用。',
addBankAccount: '添加银行账户',
+ addDebitOrCreditCard: '添加借记卡或信用卡',
assignedCards: '已分配的卡片',
assignedCardsDescription: '这些是由工作区管理员分配的卡片,用于管理公司支出。',
expensifyCard: 'Expensify Card',
@@ -1990,6 +2001,7 @@ const translations = {
cardLastFour: '卡号末尾为',
addFirstPaymentMethod: '添加支付方式以便直接在应用中发送和接收付款。',
defaultPaymentMethod: '默认',
+ bankAccountLastFour: ({lastFour}: BankAccountLastFourParams) => `银行账户 • ${lastFour}`,
},
preferencesPage: {
appSection: {
diff --git a/src/libs/API/parameters/MoveIOUReportToExistingPolicyParams.ts b/src/libs/API/parameters/MoveIOUReportToExistingPolicyParams.ts
index a72c7ff4f552..de125c916e8c 100644
--- a/src/libs/API/parameters/MoveIOUReportToExistingPolicyParams.ts
+++ b/src/libs/API/parameters/MoveIOUReportToExistingPolicyParams.ts
@@ -2,6 +2,7 @@ type MoveIOUReportToExistingPolicyParams = {
iouReportID: string;
policyID: string;
changePolicyReportActionID: string;
+ dmMovedReportActionID: string;
};
export default MoveIOUReportToExistingPolicyParams;
diff --git a/src/libs/API/parameters/MoveIOUReportToPolicyAndInviteSubmitterParams.ts b/src/libs/API/parameters/MoveIOUReportToPolicyAndInviteSubmitterParams.ts
index 4868b9d60ab8..9595493ec9bb 100644
--- a/src/libs/API/parameters/MoveIOUReportToPolicyAndInviteSubmitterParams.ts
+++ b/src/libs/API/parameters/MoveIOUReportToPolicyAndInviteSubmitterParams.ts
@@ -4,6 +4,7 @@ type MoveIOUReportToPolicyAndInviteSubmitterParams = {
policyExpenseChatReportID: string;
policyExpenseCreatedReportActionID: string;
changePolicyReportActionID: string;
+ dmMovedReportActionID: string;
};
export default MoveIOUReportToPolicyAndInviteSubmitterParams;
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index d0cafdbc8823..138a6dce2d7f 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -816,6 +816,8 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin
...CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION,
},
deleted: 'string',
+ bankAccountID: 'string',
+ payAsBusiness: 'string',
}),
() =>
validateObject>(value, {
@@ -899,6 +901,8 @@ function validateReportActionDraftProperty(key: keyof ReportAction, value: strin
expenseReportID: 'string',
resolution: 'string',
deleted: 'string',
+ bankAccountID: 'string',
+ payAsBusiness: 'string',
}),
);
}
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index e7b50ef35c79..51d1f4c87716 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -1,10 +1,11 @@
import Onyx from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx';
+import type {LastPaymentMethod, LastPaymentMethodType, OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx';
import type {Attendee} from '@src/types/onyx/IOU';
import type {IOURequestType} from './actions/IOU';
import {getCurrencyUnit} from './CurrencyUtils';
@@ -20,6 +21,12 @@ Onyx.connect({
callback: (val) => (lastLocationPermissionPrompt = val ?? ''),
});
+let lastUsedPaymentMethods: OnyxEntry;
+Onyx.connect({
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ callback: (value) => (lastUsedPaymentMethods = value),
+});
+
function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: IOUType, transactionID: string, reportID: string, iouAction?: IOUAction): void {
if (iouAction === CONST.IOU.ACTION.CATEGORIZE || iouAction === CONST.IOU.ACTION.SUBMIT || iouAction === CONST.IOU.ACTION.SHARE) {
Navigation.goBack();
@@ -216,6 +223,14 @@ function shouldStartLocationPermissionFlow() {
);
}
+function getLastUsedPaymentMethod(policyID?: string): LastPaymentMethodType | undefined {
+ if (!policyID) {
+ return;
+ }
+
+ return lastUsedPaymentMethods?.[policyID] as LastPaymentMethodType;
+}
+
export {
calculateAmount,
insertTagIntoTransactionTagsString,
@@ -228,4 +243,5 @@ export {
formatCurrentUserToAttendee,
shouldStartLocationPermissionFlow,
navigateToParticipantPage,
+ getLastUsedPaymentMethod,
};
diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts
index 21ca10749866..08e4e7455cbc 100644
--- a/src/libs/MoneyRequestReportUtils.ts
+++ b/src/libs/MoneyRequestReportUtils.ts
@@ -147,7 +147,7 @@ const getTotalAmountForIOUReportPreviewButton = (report: OnyxEntry, poli
}
// We shouldn't display the nonHeldAmount as the default option if it's not valid since we cannot pay partially in this case
- if (hasHeldExpensesReportUtils(report?.reportID) && canAllowSettlement && hasValidNonHeldAmount) {
+ if (hasHeldExpensesReportUtils(report?.reportID) && canAllowSettlement && hasValidNonHeldAmount && !hasOnlyHeldExpenses) {
return nonHeldAmount;
}
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 0a6d4aebfb7b..291355d00d5d 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -1938,7 +1938,7 @@ function getDismissedViolationMessageText(originalMessage: ReportAction;
+ payAsBusiness?: boolean;
+ bankAccountID?: number | undefined;
isPersonalTrackingExpense?: boolean;
};
@@ -1560,6 +1562,10 @@ function isIndividualInvoiceRoom(report: OnyxEntry): boolean {
return isInvoiceRoom(report) && report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL;
}
+function isBusinessInvoiceRoom(report: OnyxEntry): boolean {
+ return isInvoiceRoom(report) && report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS;
+}
+
function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean {
if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) {
return currentUserAccountID === report.invoiceReceiver.accountID;
@@ -4574,7 +4580,11 @@ function getReportPreviewMessage(
});
}
- let linkedTransaction: OnyxEntry;
+ // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
+ // eslint-disable-next-line deprecation/deprecation
+ const reportPolicy = getPolicy(report.policyID);
+
+ let linkedTransaction;
if (!isEmptyObject(iouReportAction) && shouldConsiderScanningReceiptOrPendingRoute && iouReportAction && isMoneyRequestAction(iouReportAction)) {
linkedTransaction = getLinkedTransaction(iouReportAction);
}
@@ -4605,13 +4615,22 @@ function getReportPreviewMessage(
if (originalMessage?.automaticAction) {
translatePhraseKey = 'iou.automaticallyPaidWithExpensify';
}
+
+ if (originalMessage?.paymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
+ translatePhraseKey = 'iou.businessBankAccount';
+ }
}
let actualPayerName = report.managerID === currentUserAccountID ? '' : getDisplayNameForParticipant({accountID: report.managerID, shouldUseShortForm: true});
+
actualPayerName = actualPayerName && isForListPreview && !isPreviewMessageForParentChatReport ? `${actualPayerName}:` : actualPayerName;
const payerDisplayName = isPreviewMessageForParentChatReport ? payerName : actualPayerName;
- return translateLocal(translatePhraseKey, {amount: formattedReimbursableAmount, payer: payerDisplayName ?? ''});
+ return translateLocal(translatePhraseKey, {
+ amount: formattedReimbursableAmount,
+ payer: payerDisplayName ?? '',
+ last4Digits: reportPolicy?.achAccount?.accountNumber?.slice(-4) ?? '',
+ });
}
if (report.isWaitingOnBankAccount) {
@@ -6140,9 +6159,22 @@ function getPolicyChangeMessage(action: ReportAction) {
* @param currency - IOU currency
* @param paymentType - IOU paymentMethodType. Can be oneOf(Elsewhere, Expensify)
* @param isSettlingUp - Whether we are settling up an IOU
+ * @param bankAccountID - Bank account ID
+ * @param payAsBusiness - Whether the payment is made as a business
*/
-function getIOUReportActionMessage(iouReportID: string, type: string, total: number, comment: string, currency: string, paymentType = '', isSettlingUp = false): Message[] {
+function getIOUReportActionMessage(
+ iouReportID: string,
+ type: string,
+ total: number,
+ comment: string,
+ currency: string,
+ paymentType = '',
+ isSettlingUp = false,
+ bankAccountID?: number | undefined,
+ payAsBusiness = false,
+): Message[] {
const report = getReportOrDraftReport(iouReportID);
+ const isInvoice = isInvoiceReport(report);
const amount =
type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !isEmptyObject(report)
? convertToDisplayString(getMoneyRequestSpendBreakdown(report).totalDisplaySpend, currency)
@@ -6183,7 +6215,14 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
iouMessage = `deleted the ${amount} expense${comment && ` for ${comment}`}`;
break;
case CONST.IOU.REPORT_ACTION_TYPE.PAY:
- iouMessage = isSettlingUp ? `paid ${amount}${paymentMethodMessage}` : `sent ${amount}${comment && ` for ${comment}`}${paymentMethodMessage}`;
+ if (isInvoice && isSettlingUp) {
+ iouMessage =
+ paymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE
+ ? translateLocal('iou.payElsewhere', {formattedAmount: amount})
+ : translateLocal(payAsBusiness ? 'iou.settleInvoiceBusiness' : 'iou.settleInvoicePersonal', {amount, last4Digits: String(bankAccountID).slice(-4)});
+ } else {
+ iouMessage = isSettlingUp ? `paid ${amount}${paymentMethodMessage}` : `sent ${amount}${comment && ` for ${comment}`}${paymentMethodMessage}`;
+ }
break;
case CONST.REPORT.ACTIONS.TYPE.SUBMITTED:
iouMessage = translateLocal('iou.expenseAmount', {formattedAmount: amount});
@@ -6234,6 +6273,8 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
created = DateUtils.getDBTime(),
linkedExpenseReportAction,
isPersonalTrackingExpense = false,
+ payAsBusiness,
+ bankAccountID,
} = params;
const IOUReportID = isPersonalTrackingExpense ? undefined : iouReportID || generateReportID();
@@ -6245,6 +6286,8 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
IOUTransactionID: transactionID,
IOUReportID,
type,
+ payAsBusiness,
+ bankAccountID,
};
const delegateAccountDetails = getPersonalDetailByEmail(delegateEmail);
@@ -6306,7 +6349,7 @@ function buildOptimisticIOUReportAction(params: BuildOptimisticIOUReportActionPa
},
],
avatar: getCurrentUserAvatar(),
- message: getIOUReportActionMessage(iouReportID, type, amount, comment, currency, paymentType, isSettlingUp),
+ message: getIOUReportActionMessage(iouReportID, type, amount, comment, currency, paymentType, isSettlingUp, bankAccountID, payAsBusiness),
};
const managerMcTestParticipant = participants.find((participant) => isSelectedManagerMcTest(participant.login));
@@ -9141,13 +9184,15 @@ function getTaskAssigneeChatOnyxData(
/**
* Return iou report action display message
*/
-function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, transaction?: OnyxEntry): string {
+function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, transaction?: OnyxEntry, report?: Report): string {
if (!isMoneyRequestAction(reportAction)) {
return '';
}
const originalMessage = getOriginalMessage(reportAction);
- const {IOUReportID, automaticAction} = originalMessage ?? {};
+ const {IOUReportID, automaticAction, payAsBusiness} = originalMessage ?? {};
const iouReport = getReportOrDraftReport(IOUReportID);
+ const isInvoice = isInvoiceReport(iouReport);
+
let translationKey: TranslationPaths;
if (originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
// The `REPORT_ACTION_TYPE.PAY` action type is used for both fulfilling existing requests and sending money. To
@@ -9156,13 +9201,21 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry,
const {amount, currency} = originalMessage?.IOUDetails ?? originalMessage ?? {};
const formattedAmount = convertToDisplayString(Math.abs(amount), currency) ?? '';
+ // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
+ // eslint-disable-next-line deprecation/deprecation
+ const reportPolicy = getPolicy(report?.policyID);
+ const last4Digits = reportPolicy?.achAccount?.accountNumber.slice(-4) ?? '';
+
switch (originalMessage.paymentType) {
case CONST.IOU.PAYMENT_TYPE.ELSEWHERE:
translationKey = hasMissingInvoiceBankAccount(IOUReportID) ? 'iou.payerSettledWithMissingBankAccount' : 'iou.paidElsewhere';
break;
case CONST.IOU.PAYMENT_TYPE.EXPENSIFY:
case CONST.IOU.PAYMENT_TYPE.VBBA:
- translationKey = 'iou.paidWithExpensify';
+ if (isInvoice) {
+ return translateLocal(payAsBusiness ? 'iou.settleInvoiceBusiness' : 'iou.settleInvoicePersonal', {amount: formattedAmount, last4Digits});
+ }
+ translationKey = 'iou.businessBankAccount';
if (automaticAction) {
translationKey = 'iou.automaticallyPaidWithExpensify';
}
@@ -9171,7 +9224,8 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry,
translationKey = 'iou.payerPaidAmount';
break;
}
- return translateLocal(translationKey, {amount: formattedAmount, payer: ''});
+
+ return translateLocal(translationKey, {amount: formattedAmount, payer: '', last4Digits});
}
const amount = getTransactionAmount(transaction, !isEmptyObject(iouReport) && isExpenseReport(iouReport)) ?? 0;
@@ -11434,6 +11488,7 @@ export {
generateReportName,
navigateToLinkedReportAction,
buildOptimisticUnreportedTransactionAction,
+ isBusinessInvoiceRoom,
buildOptimisticResolvedDuplicatesReportAction,
getTitleReportField,
getReportFieldsByPolicyID,
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index e7dce4c232cc..986467ff8453 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -501,7 +501,10 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata
// Use Map.get() for faster lookups with default values
const from = personalDetailsMap.get(transactionItem.accountID.toString()) ?? emptyPersonalDetails;
const to = transactionItem.managerID && !shouldShowBlankTo ? (personalDetailsMap.get(transactionItem.managerID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails;
+ // const isPolicyExpenseChat = !!reports.find((rp) => rp.policyID === transactionItem.policyID && rp.isPolicyExpenseChat);
+ // console.log({reports})
+ // console.log({isPolicyExpenseChat});
const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy);
const transactionSection: TransactionListItemType = {
@@ -522,6 +525,7 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata
isAmountColumnWide: shouldShowAmountInWideColumn,
isTaxAmountColumnWide: shouldShowTaxAmountInWideColumn,
violations: transactionViolations,
+ // isPolicyExpenseChat,
// Manually copying all the properties from transactionItem
transactionID: transactionItem.transactionID,
@@ -873,6 +877,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx
const allViolations = getViolations(data);
const reportIDToTransactions: Record = {};
+
for (const key in data) {
if (isReportEntry(key) && (data[key].type === CONST.REPORT.TYPE.IOU || data[key].type === CONST.REPORT.TYPE.EXPENSE || data[key].type === CONST.REPORT.TYPE.INVOICE)) {
const reportItem = {...data[key]};
@@ -904,6 +909,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx
const from = data.personalDetailsList?.[transactionItem.accountID];
const to = transactionItem.managerID && !shouldShowBlankTo ? (data.personalDetailsList?.[transactionItem.managerID] ?? emptyPersonalDetails) : emptyPersonalDetails;
+ // const isPolicyExpenseChat = !!reports.find((rp) => rp.policyID === transactionItem.policyID && rp.isPolicyExpenseChat);
const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy);
@@ -926,6 +932,7 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx
violations: transactionViolations,
isAmountColumnWide: shouldShowAmountInWideColumn,
isTaxAmountColumnWide: shouldShowTaxAmountInWideColumn,
+ // isPolicyExpenseChat,
};
if (reportIDToTransactions[reportKey]?.transactions) {
reportIDToTransactions[reportKey].transactions.push(transaction);
diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts
index 7775adf87862..5bd7d61393e0 100644
--- a/src/libs/actions/BankAccounts.ts
+++ b/src/libs/actions/BankAccounts.ts
@@ -17,8 +17,10 @@ import type {SaveCorpayOnboardingCompanyDetails} from '@libs/API/parameters/Save
import type SaveCorpayOnboardingDirectorInformationParams from '@libs/API/parameters/SaveCorpayOnboardingDirectorInformationParams';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
+import {getLastUsedPaymentMethod} from '@libs/IOUUtils';
import {translateLocal} from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
+import {getPersonalPolicy} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -26,6 +28,7 @@ import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {InternationalBankAccountForm, PersonalBankAccountForm} from '@src/types/form';
import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, ReimbursementAccountForm, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm';
+import type {LastPaymentMethod, LastPaymentMethodType} from '@src/types/onyx';
import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount';
import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount';
import type {OnyxData} from '@src/types/onyx/Request';
@@ -193,7 +196,27 @@ function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAcc
policyID,
};
- API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID, parameters, getVBBADataForOnyx());
+ const onyxData = getVBBADataForOnyx();
+ const lastUsedPaymentMethod = getLastUsedPaymentMethod(policyID);
+
+ if (!lastUsedPaymentMethod?.expense?.name) {
+ onyxData.successData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [policyID]: {
+ expense: {
+ name: CONST.IOU.PAYMENT_TYPE.VBBA,
+ },
+ lastUsed: {
+ name: lastUsedPaymentMethod?.lastUsed?.name ?? CONST.IOU.PAYMENT_TYPE.VBBA,
+ },
+ },
+ },
+ });
+ }
+
+ API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID, parameters, onyxData);
}
/**
@@ -219,6 +242,9 @@ function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, so
parameters.source = source;
}
+ const personalPolicy = getPersonalPolicy();
+ const lastUsedPaymentMethod = getLastUsedPaymentMethod(personalPolicy?.id);
+
const onyxData: OnyxData = {
optimisticData: [
{
@@ -261,12 +287,52 @@ function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, so
],
};
+ if (personalPolicy?.id && !lastUsedPaymentMethod) {
+ onyxData.optimisticData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [personalPolicy?.id]: {
+ iou: {
+ name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ lastUsed: {
+ name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ },
+ },
+ });
+ onyxData.successData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [personalPolicy?.id]: {
+ iou: {
+ name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ lastUsed: {
+ name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY,
+ },
+ },
+ },
+ });
+ onyxData.failureData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [personalPolicy?.id]: null,
+ },
+ });
+ }
+
API.write(WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT, parameters, onyxData);
}
-function deletePaymentBankAccount(bankAccountID: number) {
+function deletePaymentBankAccount(bankAccountID: number, lastUsedPaymentMethods?: LastPaymentMethod) {
const parameters: DeletePaymentBankAccountParams = {bankAccountID};
+ const personalPolicy = getPersonalPolicy();
+
const onyxData: OnyxData = {
optimisticData: [
{
@@ -287,6 +353,50 @@ function deletePaymentBankAccount(bankAccountID: number) {
],
};
+ Object.keys(lastUsedPaymentMethods ?? {}).forEach((paymentMethodID) => {
+ const lastUsedPaymentMethod = lastUsedPaymentMethods?.[paymentMethodID] as LastPaymentMethodType;
+
+ if (personalPolicy?.id === paymentMethodID && lastUsedPaymentMethod.iou.name === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) {
+ const revertedLastUsedPaymentMethod = lastUsedPaymentMethod.lastUsed.name !== CONST.IOU.PAYMENT_TYPE.EXPENSIFY ? lastUsedPaymentMethod.lastUsed.name : null;
+
+ onyxData.successData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [personalPolicy?.id]: revertedLastUsedPaymentMethod,
+ },
+ });
+
+ onyxData.failureData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [personalPolicy?.id]: lastUsedPaymentMethod.iou.name,
+ },
+ });
+ }
+
+ if (lastUsedPaymentMethod?.expense?.name === CONST.IOU.PAYMENT_TYPE.VBBA) {
+ const revertedLastUsedPaymentMethod = lastUsedPaymentMethod.lastUsed.name !== CONST.IOU.PAYMENT_TYPE.VBBA ? lastUsedPaymentMethod.lastUsed.name : null;
+
+ onyxData.successData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [paymentMethodID]: revertedLastUsedPaymentMethod,
+ },
+ });
+
+ onyxData.failureData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [paymentMethodID]: lastUsedPaymentMethod.expense.name,
+ },
+ });
+ }
+ });
+
API.write(WRITE_COMMANDS.DELETE_PAYMENT_BANK_ACCOUNT, parameters, onyxData);
}
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 7d14ac91ec87..c993d1af041e 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -837,6 +837,12 @@ Onyx.connect({
callback: (value) => (personalDetailsList = value),
});
+let lastUsedPaymentMethods: OnyxEntry;
+Onyx.connect({
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ callback: (value) => (lastUsedPaymentMethods = value),
+});
+
/**
* @private
* After finishing the action in RHP from the Inbox tab, besides dismissing the modal, we should open the report.
@@ -8776,6 +8782,8 @@ function getPayMoneyRequestParams(
paymentMethodType: PaymentMethodType,
full: boolean,
payAsBusiness?: boolean,
+ bankAccountID?: number,
+ paymentPolicyID?: string | undefined,
): PayMoneyRequestData {
const isInvoiceReport = isInvoiceReportReportUtils(iouReport);
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
@@ -8838,6 +8846,8 @@ function getPayMoneyRequestParams(
paymentType: paymentMethodType,
iouReportID: iouReport?.reportID,
isSettlingUp: true,
+ payAsBusiness,
+ bankAccountID,
});
// In some instances, the report preview action might not be available to the payer (only whispered to the requestor)
@@ -8910,12 +8920,23 @@ function getPayMoneyRequestParams(
);
if (iouReport?.policyID) {
+ const prevLastUsedPaymentMethod = (lastUsedPaymentMethods?.[iouReport.policyID] as OnyxTypes.LastPaymentMethodType)?.lastUsed?.name;
+ const usedPaymentOption = paymentPolicyID ?? paymentMethodType;
+
+ const optimisticLastPaymentMethod = {
+ [iouReport.policyID]: {
+ ...(iouReport.type ? {[iouReport.type]: {name: usedPaymentOption}} : {}),
+ ...(isInvoiceReport ? {invoice: {name: paymentMethodType, bankAccountID}} : {}),
+ lastUsed: {
+ name: prevLastUsedPaymentMethod !== usedPaymentOption && !!prevLastUsedPaymentMethod ? prevLastUsedPaymentMethod : usedPaymentOption,
+ },
+ },
+ };
+
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
- value: {
- [iouReport.policyID]: paymentMethodType,
- },
+ value: optimisticLastPaymentMethod,
});
}
@@ -10159,7 +10180,7 @@ function completePaymentOnboarding(paymentSelected: ValueOf, full = true) {
+function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.Report, iouReport: OnyxEntry, paymentPolicyID?: string, full = true) {
if (chatReport.policyID && shouldRestrictUserBillableActions(chatReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(chatReport.policyID));
return;
@@ -10169,7 +10190,7 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R
completePaymentOnboarding(paymentSelected);
const recipient = {accountID: iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID};
- const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full);
+ const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full, undefined, undefined, paymentPolicyID);
// For now, we need to call the PayMoneyRequestWithWallet API since PayMoneyRequest was not updated to work with
// Expensify Wallets.
@@ -10205,7 +10226,7 @@ function payInvoice(
ownerEmail,
policyName,
},
- } = getPayMoneyRequestParams(chatReport, invoiceReport, recipient, paymentMethodType, true, payAsBusiness);
+ } = getPayMoneyRequestParams(chatReport, invoiceReport, recipient, paymentMethodType, true, payAsBusiness, methodID);
const paymentSelected = paymentMethodType === CONST.IOU.PAYMENT_TYPE.VBBA ? CONST.IOU.PAYMENT_SELECTED.BBA : CONST.IOU.PAYMENT_SELECTED.PBA;
completePaymentOnboarding(paymentSelected);
@@ -10963,9 +10984,17 @@ function checkIfScanFileCanBeRead(
return readFileAsync(receiptPath.toString(), receiptFilename, onSuccess, onFailure, receiptType);
}
-/** Save the preferred payment method for a policy */
-function savePreferredPaymentMethod(policyID: string, paymentMethod: PaymentMethodType, type: ValueOf | undefined) {
- Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {[policyID]: type ? {[type]: paymentMethod, [CONST.LAST_PAYMENT_METHOD.LAST_USED]: {name: paymentMethod}} : paymentMethod});
+/** Save the preferred payment method for a policy or personal DM */
+function savePreferredPaymentMethod(policyID: string | undefined, paymentMethod: string, type: ValueOf | undefined) {
+ if (!policyID) {
+ return;
+ }
+
+ // to make it easier to revert to the previous last payment method, we will save it to this key
+ const prevPaymentMethod = lastUsedPaymentMethods?.[policyID] as OnyxTypes.LastPaymentMethodType;
+ Onyx.merge(`${ONYXKEYS.NVP_LAST_PAYMENT_METHOD}`, {
+ [policyID]: type ? {[type]: {name: paymentMethod}, [CONST.LAST_PAYMENT_METHOD.LAST_USED]: {name: prevPaymentMethod?.lastUsed?.name ?? paymentMethod}} : paymentMethod,
+ });
}
/** Get report policy id of IOU request */
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 65468e50c327..de9189315b23 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -68,6 +68,7 @@ import * as ErrorUtils from '@libs/ErrorUtils';
import {createFile} from '@libs/fileDownload/FileUtils';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import GoogleTagManager from '@libs/GoogleTagManager';
+import {getLastUsedPaymentMethod} from '@libs/IOUUtils';
import {translate, translateLocal} from '@libs/Localize';
import Log from '@libs/Log';
import * as NetworkStore from '@libs/Network/NetworkStore';
@@ -450,6 +451,33 @@ function deleteWorkspace(policyID: string, policyName: string) {
},
});
+ Object.values(allReports ?? {})
+ .filter((iouReport) => iouReport?.type === CONST.REPORT.TYPE.IOU)
+ .forEach((iouReport) => {
+ const lastUsedPaymentMethod = getLastUsedPaymentMethod(iouReport?.policyID);
+
+ if (!lastUsedPaymentMethod || !iouReport?.policyID) {
+ return;
+ }
+
+ if (lastUsedPaymentMethod?.iou?.name === policyID) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [iouReport?.policyID]: {
+ iou: {
+ name: policyID !== lastUsedPaymentMethod?.iou?.name ? lastUsedPaymentMethod?.iou?.name : '',
+ },
+ lastUsed: {
+ name: policyID !== lastUsedPaymentMethod?.iou?.name ? lastUsedPaymentMethod?.iou?.name : '',
+ },
+ },
+ },
+ });
+ }
+ });
+
if (report?.iouReportID) {
const reportTransactions = ReportUtils.getReportTransactions(report.iouReportID);
for (const transaction of reportTransactions) {
@@ -2137,6 +2165,34 @@ function buildPolicyData(
successData.push(...optimisticCategoriesData.successData);
}
+ if (getAdminPolicies().length === 0) {
+ Object.values(allReports ?? {})
+ .filter((iouReport) => iouReport?.type === CONST.REPORT.TYPE.IOU)
+ .forEach((iouReport) => {
+ const lastUsedPaymentMethod = getLastUsedPaymentMethod(iouReport?.policyID);
+
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ if (lastUsedPaymentMethod?.iou?.name || !iouReport?.policyID) {
+ return;
+ }
+
+ successData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [iouReport?.policyID]: {
+ iou: {
+ name: policyID,
+ },
+ lastUsed: {
+ name: policyID,
+ },
+ },
+ },
+ });
+ });
+ }
+
// We need to clone the file to prevent non-indexable errors.
const clonedFile = file ? (createFile(file) as File) : undefined;
diff --git a/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts
index e014db04386f..e88606bc1a80 100644
--- a/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts
+++ b/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts
@@ -2,10 +2,12 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import {WRITE_COMMANDS} from '@libs/API/types';
+import {getLastUsedPaymentMethod} from '@libs/IOUUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/ReimbursementAccountForm';
import type * as OnyxTypes from '@src/types/onyx';
+import type {OnyxData} from '@src/types/onyx/Request';
let allPolicies: OnyxCollection;
Onyx.connect({
@@ -26,120 +28,142 @@ function resetUSDBankAccount(bankAccountID: number | undefined, session: OnyxEnt
}
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? ({} as OnyxTypes.Policy);
+ const lastUsedPaymentMethod = getLastUsedPaymentMethod(policy.id);
+ const isLastUsedPaymentMethodBBA = lastUsedPaymentMethod?.expense?.name === CONST.IOU.PAYMENT_TYPE.VBBA;
+ const isPreviousLastUsedPaymentMethodBBA = lastUsedPaymentMethod?.lastUsed?.name === CONST.IOU.PAYMENT_TYPE.VBBA;
- API.write(
- WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP,
- {
- bankAccountID,
- ownerEmail: session.email,
- policyID,
- },
- {
- optimisticData: [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
- value: {
- shouldShowResetModal: false,
- isLoading: true,
- pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- achData: null,
- },
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
- value: {
- achAccount: null,
- },
- },
- ],
- successData: [
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.ONFIDO_TOKEN,
- value: '',
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ value: {
+ shouldShowResetModal: false,
+ isLoading: true,
+ pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+ achData: null,
},
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.ONFIDO_APPLICANT_ID,
- value: '',
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ achAccount: null,
},
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.PLAID_DATA,
- value: CONST.PLAID.DEFAULT_DATA,
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.ONFIDO_TOKEN,
+ value: '',
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.ONFIDO_APPLICANT_ID,
+ value: '',
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.PLAID_DATA,
+ value: CONST.PLAID.DEFAULT_DATA,
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.PLAID_LINK_TOKEN,
+ value: '',
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA,
+ },
+ {
+ onyxMethod: Onyx.METHOD.SET,
+ key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT,
+ value: {
+ [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.OWNS_MORE_THAN_25_PERCENT]: false,
+ [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.HAS_OTHER_BENEFICIAL_OWNERS]: false,
+ [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNERS]: '',
+ [INPUT_IDS.BANK_INFO_STEP.ACCOUNT_NUMBER]: '',
+ [INPUT_IDS.BANK_INFO_STEP.ROUTING_NUMBER]: '',
+ [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCOUNT_ID]: '',
+ [INPUT_IDS.BANK_INFO_STEP.PLAID_MASK]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_NAME]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.STREET]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.CITY]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.STATE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.ZIP_CODE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_PHONE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_WEBSITE]: undefined,
+ [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_TAX_ID]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_TYPE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_DATE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_STATE]: '',
+ [INPUT_IDS.BUSINESS_INFO_STEP.HAS_NO_CONNECTION_TO_CANNABIS]: false,
+ [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.IS_ONFIDO_SETUP_COMPLETE]: false,
+ [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: '',
+ [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: '',
+ [INPUT_IDS.COMPLETE_VERIFICATION.ACCEPT_TERMS_AND_CONDITIONS]: false,
+ [INPUT_IDS.COMPLETE_VERIFICATION.CERTIFY_TRUE_INFORMATION]: false,
+ [INPUT_IDS.COMPLETE_VERIFICATION.IS_AUTHORIZED_TO_USE_BANK_ACCOUNT]: false,
+ [INPUT_IDS.BANK_INFO_STEP.IS_SAVINGS]: false,
+ [INPUT_IDS.BANK_INFO_STEP.BANK_NAME]: '',
+ [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCESS_TOKEN]: '',
+ [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: '',
+ [INPUT_IDS.AMOUNT1]: '',
+ [INPUT_IDS.AMOUNT2]: '',
+ [INPUT_IDS.AMOUNT3]: '',
},
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.PLAID_LINK_TOKEN,
- value: '',
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
+ value: {isLoading: false, pendingAction: null},
+ },
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ achAccount: policy?.achAccount,
},
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
- value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA,
- },
- {
- onyxMethod: Onyx.METHOD.SET,
- key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT,
- value: {
- [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.OWNS_MORE_THAN_25_PERCENT]: false,
- [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.HAS_OTHER_BENEFICIAL_OWNERS]: false,
- [INPUT_IDS.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNERS]: '',
- [INPUT_IDS.BANK_INFO_STEP.ACCOUNT_NUMBER]: '',
- [INPUT_IDS.BANK_INFO_STEP.ROUTING_NUMBER]: '',
- [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCOUNT_ID]: '',
- [INPUT_IDS.BANK_INFO_STEP.PLAID_MASK]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_NAME]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.STREET]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.CITY]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.STATE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.ZIP_CODE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_PHONE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_WEBSITE]: undefined,
- [INPUT_IDS.BUSINESS_INFO_STEP.COMPANY_TAX_ID]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_TYPE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_DATE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.INCORPORATION_STATE]: '',
- [INPUT_IDS.BUSINESS_INFO_STEP.HAS_NO_CONNECTION_TO_CANNABIS]: false,
- [INPUT_IDS.PERSONAL_INFO_STEP.FIRST_NAME]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.LAST_NAME]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.STREET]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.CITY]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.STATE]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.ZIP_CODE]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.IS_ONFIDO_SETUP_COMPLETE]: false,
- [INPUT_IDS.PERSONAL_INFO_STEP.DOB]: '',
- [INPUT_IDS.PERSONAL_INFO_STEP.SSN_LAST_4]: '',
- [INPUT_IDS.COMPLETE_VERIFICATION.ACCEPT_TERMS_AND_CONDITIONS]: false,
- [INPUT_IDS.COMPLETE_VERIFICATION.CERTIFY_TRUE_INFORMATION]: false,
- [INPUT_IDS.COMPLETE_VERIFICATION.IS_AUTHORIZED_TO_USE_BANK_ACCOUNT]: false,
- [INPUT_IDS.BANK_INFO_STEP.IS_SAVINGS]: false,
- [INPUT_IDS.BANK_INFO_STEP.BANK_NAME]: '',
- [INPUT_IDS.BANK_INFO_STEP.PLAID_ACCESS_TOKEN]: '',
- [INPUT_IDS.BANK_INFO_STEP.SELECTED_PLAID_ACCOUNT_ID]: '',
- [INPUT_IDS.AMOUNT1]: '',
- [INPUT_IDS.AMOUNT2]: '',
- [INPUT_IDS.AMOUNT3]: '',
+ },
+ ],
+ };
+
+ if (isLastUsedPaymentMethodBBA && policyID) {
+ onyxData.successData?.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD,
+ value: {
+ [policyID]: {
+ expense: {
+ name: isPreviousLastUsedPaymentMethodBBA ? '' : lastUsedPaymentMethod?.lastUsed.name,
},
- },
- ],
- failureData: [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: ONYXKEYS.REIMBURSEMENT_ACCOUNT,
- value: {isLoading: false, pendingAction: null},
- },
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
- value: {
- achAccount: policy?.achAccount,
+ lastUsed: {
+ name: isPreviousLastUsedPaymentMethodBBA ? '' : lastUsedPaymentMethod?.lastUsed.name,
},
},
- ],
+ },
+ });
+ }
+
+ API.write(
+ WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP,
+ {
+ bankAccountID,
+ ownerEmail: session.email,
+ policyID,
},
+ onyxData,
);
}
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 5041ba68bdc4..7773d5e144ff 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -105,6 +105,7 @@ import {
buildOptimisticExportIntegrationAction,
buildOptimisticGroupChatReport,
buildOptimisticIOUReportAction,
+ buildOptimisticMovedReportAction,
buildOptimisticRenamedRoomReportAction,
buildOptimisticReportPreview,
buildOptimisticRoomDescriptionUpdatedReportAction,
@@ -4950,8 +4951,9 @@ function deleteAppReport(reportID: string | undefined) {
* Moves an IOU report to a policy by converting it to an expense report
* @param reportID - The ID of the IOU report to move
* @param policyID - The ID of the policy to move the report to
+ * @param isFromSettlementButton - Whether the action is from report preview
*/
-function moveIOUReportToPolicy(reportID: string, policyID: string) {
+function moveIOUReportToPolicy(reportID: string, policyID: string, isFromSettlementButton?: boolean) {
const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
@@ -4964,7 +4966,7 @@ function moveIOUReportToPolicy(reportID: string, policyID: string) {
const isReimbursed = isReportManuallyReimbursed(iouReport);
// We do not want to create negative amount expenses
- if (!isReimbursed && ReportActionsUtils.hasRequestFromCurrentAccount(reportID, iouReport.managerID ?? CONST.DEFAULT_NUMBER_ID)) {
+ if (!isReimbursed && ReportActionsUtils.hasRequestFromCurrentAccount(reportID, iouReport.managerID ?? CONST.DEFAULT_NUMBER_ID) && !isFromSettlementButton) {
return;
}
@@ -5109,10 +5111,24 @@ function moveIOUReportToPolicy(reportID: string, policyID: string) {
},
});
+ // Create the MOVED report action and add it to the DM chat which indicates to the user where the report has been moved
+ const movedReportAction = buildOptimisticMovedReportAction(iouReport.policyID, policyID, expenseChatReportId, iouReportID, policyName);
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChatReportID}`,
+ value: {[movedReportAction.reportActionID]: movedReportAction},
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChatReportID}`,
+ value: {[movedReportAction.reportActionID]: null},
+ });
+
const parameters: MoveIOUReportToExistingPolicyParams = {
iouReportID,
policyID,
changePolicyReportActionID: changePolicyReportAction.reportActionID,
+ dmMovedReportActionID: movedReportAction.reportActionID,
};
API.write(WRITE_COMMANDS.MOVE_IOU_REPORT_TO_EXISTING_POLICY, parameters, {optimisticData, successData, failureData});
@@ -5123,7 +5139,7 @@ function moveIOUReportToPolicy(reportID: string, policyID: string) {
* @param reportID - The ID of the IOU report to move
* @param policyID - The ID of the policy to move the report to
*/
-function moveIOUReportToPolicyAndInviteSubmitter(reportID: string, policyID: string) {
+function moveIOUReportToPolicyAndInviteSubmitter(reportID: string, policyID: string): {policyExpenseChatReportID?: string} | undefined {
const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
@@ -5137,6 +5153,7 @@ function moveIOUReportToPolicyAndInviteSubmitter(reportID: string, policyID: str
const submitterAccountID = iouReport.ownerAccountID;
const submitterEmail = PersonalDetailsUtils.getLoginByAccountID(submitterAccountID ?? CONST.DEFAULT_NUMBER_ID);
const submitterLogin = PhoneNumber.addSMSDomainIfPhoneNumber(submitterEmail);
+ const iouReportID = iouReport.reportID;
// This flow only works for admins moving an IOU report to a policy where the submitter is NOT yet a member of the policy
if (!isPolicyAdmin || !isIOUReportUsingReport(iouReport) || !submitterAccountID || !submitterEmail || isPolicyMember(submitterLogin, policyID)) {
@@ -5347,15 +5364,30 @@ function moveIOUReportToPolicyAndInviteSubmitter(reportID: string, policyID: str
},
});
+ // Create the MOVED report action and add it to the DM chat which indicates to the user where the report has been moved
+ const movedReportAction = buildOptimisticMovedReportAction(iouReport.policyID, policyID, optimisticPolicyExpenseChatReportID, iouReportID, policy.name);
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChatReportID}`,
+ value: {[movedReportAction.reportActionID]: movedReportAction},
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldChatReportID}`,
+ value: {[movedReportAction.reportActionID]: null},
+ });
+
const parameters: MoveIOUReportToPolicyAndInviteSubmitterParams = {
iouReportID: reportID,
policyID,
policyExpenseChatReportID: optimisticPolicyExpenseChatReportID ?? String(CONST.DEFAULT_NUMBER_ID),
policyExpenseCreatedReportActionID: optimisticPolicyExpenseChatCreatedReportActionID ?? String(CONST.DEFAULT_NUMBER_ID),
changePolicyReportActionID: changePolicyReportAction.reportActionID,
+ dmMovedReportActionID: movedReportAction.reportActionID,
};
API.write(WRITE_COMMANDS.MOVE_IOU_REPORT_TO_POLICY_AND_INVITE_SUBMITTER, parameters, {optimisticData, successData, failureData});
+ return {policyExpenseChatReportID: optimisticPolicyExpenseChatReportID};
}
/**
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 361b6c410b79..96a5e78abab6 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -12,7 +12,7 @@ import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import fileDownload from '@libs/fileDownload';
import enhanceParameters from '@libs/Network/enhanceParameters';
import {rand64} from '@libs/NumberUtils';
-import {getSubmitToAccountID} from '@libs/PolicyUtils';
+import {getPersonalPolicy, getSubmitToAccountID} from '@libs/PolicyUtils';
import {hasHeldExpenses} from '@libs/ReportUtils';
import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils';
import playSound, {SOUNDS} from '@libs/Sound';
@@ -20,6 +20,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm';
import type {LastPaymentMethod, LastPaymentMethodType, SearchResults} from '@src/types/onyx';
+import type {PaymentInformation} from '@src/types/onyx/LastPaymentMethod';
import type {SearchPolicy, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults';
import type Nullable from '@src/types/utils/Nullable';
@@ -69,18 +70,30 @@ function handleActionButtonPress(hash: number, item: TransactionListItemType | R
}
}
-function getLastPolicyPaymentMethod(policyID: string | undefined, lastPaymentMethods: OnyxEntry) {
+function getLastPolicyBankAccountID(policyID: string | undefined, reportType: keyof LastPaymentMethodType = 'lastUsed'): number | undefined {
if (!policyID) {
- return null;
+ return undefined;
}
- let lastPolicyPaymentMethod = null;
- if (typeof lastPaymentMethods?.[policyID] === 'string') {
- lastPolicyPaymentMethod = lastPaymentMethods?.[policyID] as ValueOf;
- } else {
- lastPolicyPaymentMethod = (lastPaymentMethods?.[policyID] as LastPaymentMethodType)?.lastUsed.name as ValueOf;
+ const lastPolicyPaymentMethod = lastPaymentMethod?.[policyID];
+ return typeof lastPolicyPaymentMethod === 'string' ? undefined : (lastPolicyPaymentMethod?.[reportType] as PaymentInformation)?.bankAccountID;
+}
+
+function getLastPolicyPaymentMethod(
+ policyID: string | undefined,
+ lastPaymentMethods: OnyxEntry,
+ reportType: keyof LastPaymentMethodType = 'lastUsed',
+ isIOUReport?: boolean,
+): ValueOf | undefined {
+ if (!policyID) {
+ return undefined;
}
- return lastPolicyPaymentMethod;
+ const personalPolicy = getPersonalPolicy();
+
+ const lastPolicyPaymentMethod = lastPaymentMethods?.[policyID] ?? (isIOUReport && personalPolicy ? lastPaymentMethods?.[personalPolicy.id] : undefined);
+ const result = typeof lastPolicyPaymentMethod === 'string' ? lastPolicyPaymentMethod : (lastPolicyPaymentMethod?.[reportType] as PaymentInformation)?.name;
+
+ return result as ValueOf | undefined;
}
function getPayActionCallback(hash: number, item: TransactionListItemType | ReportListItemType, goToItem: () => void) {
@@ -451,4 +464,5 @@ export {
openSearchFiltersCardPage,
openSearchPage as openSearch,
getLastPolicyPaymentMethod,
+ getLastPolicyBankAccountID,
};
diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx
index a3176b43150f..82dfe86cb2a6 100644
--- a/src/pages/home/report/ReportActionItemMessage.tsx
+++ b/src/pages/home/report/ReportActionItemMessage.tsx
@@ -49,8 +49,8 @@ type ReportActionItemMessageProps = {
function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHidden = false}: ReportActionItemMessageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
- const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getLinkedTransactionID(action)}`);
+ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: true});
+ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getLinkedTransactionID(action)}`, {canBeMissing: true});
const fragments = getReportActionMessageFragments(action);
const isIOUReport = isMoneyRequestAction(action);
@@ -91,7 +91,7 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid
const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? getOriginalMessage(action) : null;
const iouReportID = originalMessage?.IOUReportID;
if (iouReportID) {
- iouMessage = getIOUReportActionDisplayMessage(action, transaction);
+ iouMessage = getIOUReportActionDisplayMessage(action, transaction, report);
}
}
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
index b11bb29e8adf..be7d617f6946 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx
@@ -64,6 +64,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {initialValue: {}, canBeMissing: true});
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false});
const [userAccount] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
+ const [lastUsedPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true});
const isUserValidated = userAccount?.validated ?? false;
const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);
const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext);
@@ -293,11 +294,11 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) {
const bankAccountID = paymentMethod.selectedPaymentMethod.bankAccountID;
const fundID = paymentMethod.selectedPaymentMethod.fundID;
if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && bankAccountID) {
- deletePaymentBankAccount(bankAccountID);
+ deletePaymentBankAccount(bankAccountID, lastUsedPaymentMethods);
} else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && fundID) {
deletePaymentCard(fundID);
}
- }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType]);
+ }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethod.fundID, paymentMethod.selectedPaymentMethodType, lastUsedPaymentMethods]);
/**
* Navigate to the appropriate page after completing the KYC flow, depending on what initiated it
diff --git a/src/stories/TransactionPreviewContent.stories.tsx b/src/stories/TransactionPreviewContent.stories.tsx
index dec9767558ef..00c2e8e1b15a 100644
--- a/src/stories/TransactionPreviewContent.stories.tsx
+++ b/src/stories/TransactionPreviewContent.stories.tsx
@@ -2,6 +2,7 @@ import type {InputType} from '@storybook/csf';
import type {Meta, StoryFn} from '@storybook/react';
import React from 'react';
import {View} from 'react-native';
+import type {ValueOf} from 'type-fest';
import TransactionPreviewContent from '@components/ReportActionItem/TransactionPreview/TransactionPreviewContent';
import type {TransactionPreviewContentProps} from '@components/ReportActionItem/TransactionPreview/types';
import ThemeProvider from '@components/ThemeProvider';
@@ -27,7 +28,7 @@ const modifiedTransaction = ({category, tag, merchant = '', amount = 1000, hold
hold: hold ? 'true' : undefined,
},
});
-const iouReportWithModifiedType = (type: string) => ({...iouReportR14932, type});
+const iouReportWithModifiedType = (type: ValueOf) => ({...iouReportR14932, type});
const actionWithModifiedPendingAction = (pendingAction: PendingAction) => ({...actionR14932, pendingAction});
const disabledProperties = [
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 26989f245392..94648ec18a83 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -4902,6 +4902,12 @@ const styles = (theme: ThemeColors) =>
height: is2FARequired ? variables.modalTopIconHeight : variables.modalTopBigIconHeight,
}),
+ settlementButtonListContainer: {
+ maxHeight: 500,
+ paddingBottom: 0,
+ paddingTop: 0,
+ },
+
moneyRequestViewImage: {
...spacing.mh5,
overflow: 'hidden',
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 5cf3a0c4bbc8..5f7aa3d105fb 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1194,6 +1194,18 @@ function getItemBackgroundColorStyle(isSelected: boolean, isFocused: boolean, is
return {};
}
+function getOptionMargin(itemIndex: number, itemsLen: number) {
+ if (itemIndex === itemsLen && itemsLen > 5) {
+ return {marginBottom: 16};
+ }
+
+ if (itemIndex === 0 && itemsLen > 5) {
+ return {marginTop: 16};
+ }
+
+ return {};
+}
+
const staticStyleUtils = {
positioning,
searchHeaderDefaultOffset,
@@ -1276,6 +1288,7 @@ const staticStyleUtils = {
getItemBackgroundColorStyle,
getNavigationBarType,
getSuccessReportCardLostIllustrationStyle,
+ getOptionMargin,
};
const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
diff --git a/src/types/onyx/LastPaymentMethod.ts b/src/types/onyx/LastPaymentMethod.ts
index 00a4cd475415..0338c7c6a7ef 100644
--- a/src/types/onyx/LastPaymentMethod.ts
+++ b/src/types/onyx/LastPaymentMethod.ts
@@ -1,30 +1,28 @@
+/**
+ * PaymentInformation object
+ */
+type PaymentInformation = {
+ /** The name of the */
+ name: string;
+ /** The bank account id of the last payment method */
+ bankAccountID?: number;
+};
+
/**
* The new lastPaymentMethod object
*/
type LastPaymentMethodType = {
/** The default last payment method */
- lastUsed: {
- /** The name of the last payment method */
- name: string;
- };
+ lastUsed: PaymentInformation;
/** The lastPaymentMethod of an IOU */
- Iou: {
- /** The name of the last payment method */
- name: string;
- };
+ iou: PaymentInformation;
/** The lastPaymentMethod of an Expense */
- Expense: {
- /** The name of the last payment method */
- name: string;
- };
+ expense: PaymentInformation;
/** The lastPaymentMethod of an Invoice */
- Invoice: {
- /** The name of the last payment method */
- name: string;
- };
+ invoice: string | PaymentInformation;
};
/** Record of last payment methods, indexed by policy id */
type LastPaymentMethod = Record;
-export type {LastPaymentMethodType, LastPaymentMethod};
+export type {LastPaymentMethodType, LastPaymentMethod, PaymentInformation};
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index 1600062bbd2f..7ad909c36d5e 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -74,6 +74,12 @@ type OriginalMessageIOU = {
/** Collection of accountIDs of users mentioned in message */
whisperedTo?: number[];
+
+ /** Where the invoice is paid with business account or not */
+ payAsBusiness?: boolean;
+
+ /** The bank account id */
+ bankAccountID?: number;
};
/** Names of moderation decisions */
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index 919c01ec4f02..acfa254b7070 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -134,7 +134,7 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
writeCapability?: WriteCapability;
/** The report type */
- type?: string;
+ type?: ValueOf | ValueOf | ValueOf;
/** The report visibility */
visibility?: RoomVisibility;
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index 5667284fdba1..417de849917a 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -80,6 +80,12 @@ type Message = {
/** The time this report action was deleted */
deleted?: string;
+
+ /** The bank account id that was used to pay the invoice */
+ bankAccountID?: number | undefined;
+
+ /** Whether the invoice was paid with business account or not */
+ payAsBusiness?: boolean;
};
/** Model of image */
diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts
index 8fce5ca0fb90..fd48068f6fe2 100644
--- a/src/types/onyx/SearchResults.ts
+++ b/src/types/onyx/SearchResults.ts
@@ -224,6 +224,9 @@ type SearchReportAction = {
/** The name of the report */
reportName: string;
+
+ /** Whether the report is policyExpenseChat */
+ isPolicyExpenseChat?: boolean;
};
/** Model of policy search result */
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index 42e256892f10..25df19d7bc45 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -2471,7 +2471,7 @@ describe('actions/IOU', () => {
.then(() => {
mockFetch?.pause?.();
if (chatReport && iouReport) {
- payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
+ payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, undefined);
}
return waitForBatchedUpdates();
})
@@ -2646,7 +2646,7 @@ describe('actions/IOU', () => {
)
.then(() => {
if (chatReport && expenseReport) {
- payMoneyRequest(CONST.IOU.PAYMENT_TYPE.VBBA, chatReport, expenseReport);
+ payMoneyRequest(CONST.IOU.PAYMENT_TYPE.VBBA, chatReport, expenseReport, undefined);
}
return waitForBatchedUpdates();
})
@@ -2775,7 +2775,7 @@ describe('actions/IOU', () => {
.then(() => {
mockFetch?.fail?.();
if (chatReport && expenseReport) {
- payMoneyRequest('ACH', chatReport, expenseReport);
+ payMoneyRequest('ACH', chatReport, expenseReport, undefined);
}
return waitForBatchedUpdates();
})
@@ -2816,7 +2816,7 @@ describe('actions/IOU', () => {
jest.advanceTimersByTime(10);
// When paying the IOU report
- payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
+ payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, undefined);
await waitForBatchedUpdates();
@@ -2915,7 +2915,7 @@ describe('actions/IOU', () => {
})
.then(() => {
// When partially paying an iou report from the chat report via the report preview
- payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, {reportID: topMostReportID}, iouReport, false);
+ payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, {reportID: topMostReportID}, iouReport, undefined, false);
return waitForBatchedUpdates();
})
.then(() => {
@@ -2990,7 +2990,7 @@ describe('actions/IOU', () => {
.then(() => {
// When the expense report is paid elsewhere (but really, any payment option would work)
if (chatReport && expenseReport) {
- payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, expenseReport);
+ payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, expenseReport, undefined);
}
return waitForBatchedUpdates();
})
diff --git a/tests/unit/OnyxDerivedTest.ts b/tests/unit/OnyxDerivedTest.ts
index 9940b363f28d..4e2f736a2b69 100644
--- a/tests/unit/OnyxDerivedTest.ts
+++ b/tests/unit/OnyxDerivedTest.ts
@@ -4,6 +4,7 @@ import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
import initOnyxDerivedValues from '@userActions/OnyxDerived';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {Report} from '@src/types/onyx';
import type {ReportActions} from '@src/types/onyx/ReportAction';
import createRandomReport from '../utils/collections/reports';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
@@ -19,7 +20,7 @@ describe('OnyxDerived', () => {
});
describe('reportAttributes', () => {
- const mockReport = {
+ const mockReport: Report = {
reportID: `test_1`,
reportName: 'Test Report',
type: 'chat',