diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index a085385589a3..fa4195a8673e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -77,6 +77,7 @@ import DelegateNoAccessModal from './DelegateNoAccessModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import type {PaymentMethod} from './KYCWall/types'; import LoadingBar from './LoadingBar'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; @@ -241,7 +242,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; const confirmPayment = useCallback( - (type?: PaymentMethodType | undefined, payAsBusiness?: boolean) => { + (type?: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => { if (!type || !chatReport) { return; } @@ -253,7 +254,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea setIsHoldMenuVisible(true); } else if (isInvoiceReport(moneyRequestReport)) { startAnimation(); - payInvoice(type, chatReport, moneyRequestReport, payAsBusiness); + payInvoice(type, chatReport, moneyRequestReport, payAsBusiness, methodID, paymentMethod); } else { startAnimation(); payMoneyRequest(type, chatReport, moneyRequestReport, true); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 65526aa0f269..1135a7469d5a 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -9,6 +9,7 @@ import {getButtonRole} from '@components/Button/utils'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import type {PaymentMethod} from '@components/KYCWall/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import type {ActionHandledType} from '@components/ProcessMoneyReportHoldMenu'; @@ -262,7 +263,7 @@ function ReportPreview({ const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); const confirmPayment = useCallback( - (type: PaymentMethodType | undefined, payAsBusiness?: boolean) => { + (type: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => { if (!type) { return; } @@ -275,7 +276,7 @@ function ReportPreview({ } else if (chatReport && iouReport) { startAnimation(); if (isInvoiceReportUtils(iouReport)) { - payInvoice(type, chatReport, iouReport, payAsBusiness); + payInvoice(type, chatReport, iouReport, payAsBusiness, methodID, paymentMethod); } else { payMoneyRequest(type, chatReport, iouReport); } diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index bca6765d099f..1c2af1dd181a 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -7,7 +7,10 @@ import * as Expensicons from '@components/Icon/Expensicons'; import KYCWall from '@components/KYCWall'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {isCurrencySupportedForDirectReimbursement} from '@libs/actions/Policy/Policy'; import Navigation from '@libs/Navigation/Navigation'; +import {formatPaymentMethods} from '@libs/PaymentUtils'; import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils'; import { doesReportBelongToWorkspace, @@ -67,6 +70,7 @@ function SettlementButton({ onlyShowPayElsewhere, wrapperStyle, }: 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. @@ -86,6 +90,9 @@ function SettlementButton({ const isLoadingLastPaymentMethod = isLoadingOnyxValue(lastPaymentMethodResult); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [bankAccountList = {}] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [fundList = {}] = useOnyx(ONYXKEYS.FUND_LIST); + const isInvoiceReport = (!isEmptyObject(iouReport) && isInvoiceReportUtil(iouReport)) || false; const shouldShowPaywithExpensifyOption = !shouldHidePaymentOptions; const shouldShowPayElsewhereOption = !shouldHidePaymentOptions && !isInvoiceReport; @@ -138,6 +145,16 @@ function SettlementButton({ } if (isInvoiceReport) { + const formattedPaymentMethods = formatPaymentMethods(bankAccountList, fundList, styles); + const isCurrencySupported = isCurrencySupportedForDirectReimbursement(currency); + const getPaymentSubitems = (payAsBusiness: boolean) => + formattedPaymentMethods.map((formattedPaymentMethod) => ({ + text: formattedPaymentMethod?.title ?? '', + description: formattedPaymentMethod?.description ?? '', + icon: formattedPaymentMethod?.icon, + onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, payAsBusiness, formattedPaymentMethod.methodID, formattedPaymentMethod.accountType), + })); + if (isIndividualInvoiceRoomUtil(chatReport)) { buttonOptions.push({ text: translate('iou.settlePersonal', {formattedAmount}), @@ -145,6 +162,17 @@ function SettlementButton({ value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, backButtonText: translate('iou.individual'), subMenuItems: [ + ...(isCurrencySupported ? getPaymentSubitems(false) : []), + { + text: translate('workspace.invoices.paymentMethods.addBankAccount'), + icon: Expensicons.Bank, + onSelected: () => Navigation.navigate(addBankAccountRoute), + }, + { + text: translate('workspace.invoices.paymentMethods.addDebitOrCreditCard'), + icon: Expensicons.CreditCard, + onSelected: () => Navigation.navigate(addDebitCardRoute), + }, { text: translate('iou.payElsewhere', {formattedAmount: ''}), icon: Expensicons.Cash, @@ -161,6 +189,17 @@ function SettlementButton({ value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, backButtonText: translate('iou.business'), subMenuItems: [ + ...(isCurrencySupported ? getPaymentSubitems(true) : []), + { + text: translate('workspace.invoices.paymentMethods.addBankAccount'), + icon: Expensicons.Bank, + onSelected: () => Navigation.navigate(addBankAccountRoute), + }, + { + text: translate('workspace.invoices.paymentMethods.addCorporateCard'), + icon: Expensicons.CreditCard, + onSelected: () => Navigation.navigate(addDebitCardRoute), + }, { text: translate('iou.payElsewhere', {formattedAmount: ''}), icon: Expensicons.Cash, diff --git a/src/components/SettlementButton/types.ts b/src/components/SettlementButton/types.ts index df8fdedc512e..02c944c148f7 100644 --- a/src/components/SettlementButton/types.ts +++ b/src/components/SettlementButton/types.ts @@ -1,5 +1,6 @@ import type {StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {PaymentMethod} from '@components/KYCWall/types'; import type ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {ButtonSizeValue} from '@src/styles/utils/types'; @@ -11,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) => void; + onPress: (paymentType?: PaymentMethodType, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => void; /** Callback when the payment options popover is shown */ onPaymentOptionsShow?: () => void; diff --git a/src/languages/en.ts b/src/languages/en.ts index 9386978f77c4..2d8bdc2aad1a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4418,6 +4418,8 @@ const translations = { business: 'Business', chooseInvoiceMethod: 'Choose a payment method below:', addBankAccount: 'Add bank account', + addDebitOrCreditCard: 'Add debit or credit card', + addCorporateCard: 'Add corporate card', payingAsIndividual: 'Paying as an individual', payingAsBusiness: 'Paying as a business', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index e568c3012d34..0d8c392c51aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4464,6 +4464,8 @@ const translations = { business: 'Empresas', chooseInvoiceMethod: 'Elija un método de pago:', addBankAccount: 'Añadir cuenta bancaria', + addDebitOrCreditCard: 'Agrega una tarjeta de débito o crédito', + addCorporateCard: 'Agrega una tarjeta corporativa', payingAsIndividual: 'Pago individual', payingAsBusiness: 'Pagar como una empresa', }, diff --git a/src/libs/API/parameters/PayInvoiceParams.ts b/src/libs/API/parameters/PayInvoiceParams.ts index aaf1d906d143..ac9da22104bc 100644 --- a/src/libs/API/parameters/PayInvoiceParams.ts +++ b/src/libs/API/parameters/PayInvoiceParams.ts @@ -6,6 +6,8 @@ type PayInvoiceParams = Partial & { reportActionID: string; paymentMethodType: PaymentMethodType; payAsBusiness: boolean; + bankAccountID?: number; + fundID?: number; }; export default PayInvoiceParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fc4c70ac538a..571b5ec68f4a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1513,7 +1513,15 @@ function isPublicAnnounceRoom(report: OnyxEntry): boolean { * else since the report is a personal IOU, the route should be for personal bank account. */ function getBankAccountRoute(report: OnyxEntry): Route { - return isPolicyExpenseChat(report) ? ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(report?.policyID) : ROUTES.SETTINGS_ADD_BANK_ACCOUNT; + if (isPolicyExpenseChat(report)) { + return ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(report?.policyID); + } + + if (isInvoiceRoom(report) && report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) { + return ROUTES.WORKSPACE_INVOICES.getRoute(report?.invoiceReceiver?.policyID); + } + + return ROUTES.SETTINGS_ADD_BANK_ACCOUNT; } /** diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8418d2150c9f..fec5d058fc44 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5,6 +5,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxUpdate} import Onyx from 'react-native-onyx'; import type {PartialDeep, SetRequired, ValueOf} from 'type-fest'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; +import type {PaymentMethod} from '@components/KYCWall/types'; import * as API from '@libs/API'; import type { ApproveMoneyRequestParams, @@ -9089,7 +9090,14 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R notifyNewAction(Navigation.getTopmostReportId() ?? iouReport?.reportID, userAccountID); } -function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes.Report, invoiceReport: OnyxEntry, payAsBusiness = false) { +function payInvoice( + paymentMethodType: PaymentMethodType, + chatReport: OnyxTypes.Report, + invoiceReport: OnyxEntry, + payAsBusiness = false, + methodID?: number, + paymentMethod?: PaymentMethod, +) { const recipient = {accountID: invoiceReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID}; const { optimisticData, @@ -9119,6 +9127,14 @@ function payInvoice(paymentMethodType: PaymentMethodType, chatReport: OnyxTypes. payAsBusiness, }; + if (paymentMethod === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { + params.bankAccountID = methodID; + } + + if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) { + params.fundID = methodID; + } + if (policyID) { params = { ...params,