Skip to content
Merged
8 changes: 4 additions & 4 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ const ROUTES = {
return `bank-account/enter-signer-info?policyID=${policyID}&bankAccountID=${bankAccountID}&isCompleted=${isCompleted}` as const;
},
},
BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT: {
route: 'bank-account/connect-existing-business-bank-account',
getRoute: (policyID: string) => `bank-account/connect-existing-business-bank-account?policyID=${policyID}` as const,
},
PUBLIC_CONSOLE_DEBUG: {
route: 'troubleshoot/console',

Expand Down Expand Up @@ -1730,10 +1734,6 @@ const ROUTES = {
return `workspaces/${policyID}/workflows` as const;
},
},
WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: {
route: 'workspaces/:policyID/workflows/connect-account',
getRoute: (policyID: string) => `workspaces/${policyID}/workflows/connect-account` as const,
},
WORKSPACE_WORKFLOWS_APPROVALS_NEW: {
route: 'workspaces/:policyID/workflows/approvals/new',
getRoute: (policyID: string) => `workspaces/${policyID}/workflows/approvals/new` as const,
Expand Down
2 changes: 1 addition & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,6 @@ const SCREENS = {
WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: 'Workspace_Workflows_Approvals_Over_Limit_Approver',
WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency',
WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset',
WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: 'Workspace_Workflows_Connect_Existing_Bank_Account',
DESCRIPTION: 'Workspace_Overview_Description',
SHARE: 'Workspace_Overview_Share',
NAME: 'Workspace_Overview_Name',
Expand Down Expand Up @@ -799,6 +798,7 @@ const SCREENS = {
ADD_PERSONAL_BANK_ACCOUNT_ROOT: 'AddPersonalBankAccount_Root',
REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root',
REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT: 'Reimbursement_Account_Verify_Account',
CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT: 'Connect_Existing_Business_Bank_Account_Root',
WALLET_STATEMENT_ROOT: 'WalletStatement_Root',
SIGN_IN_ROOT: 'SignIn_Root',
DETAILS_ROOT: 'Details_Root',
Expand Down
35 changes: 31 additions & 4 deletions src/components/KYCWall/BaseKYCWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import useOnyx from '@hooks/useOnyx';
import useParentReportAction from '@hooks/useParentReportAction';
import {openPersonalBankAccountSetupView} from '@libs/actions/BankAccounts';
import {completePaymentOnboarding, savePreferredPaymentMethod} from '@libs/actions/IOU';
import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount';
import {moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report';
import {isBankAccountPartiallySetup} from '@libs/BankAccountUtils';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import Log from '@libs/Log';
import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue';
import Navigation from '@libs/Navigation/Navigation';
import {hasExpensifyPaymentMethod} from '@libs/PaymentUtils';
import {getBankAccountRoute, isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils';
import {getEligibleExistingBusinessBankAccounts, getOpenConnectedToPolicyBusinessBankAccounts} from '@libs/WorkflowUtils';
import {createWorkspaceFromIOUPayment} from '@userActions/Policy/Policy';
import {setKYCWallSource} from '@userActions/Wallet';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -51,15 +54,17 @@ function KYCWall({
source,
shouldShowPersonalBankAccountOption = false,
ref,
currency,
}: KYCWallProps) {
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 = getEmptyObject<BankAccountList>()] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, {canBeMissing: true});
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});

const {formatPhoneNumber} = useLocalize();
const currentUserDetails = useCurrentUserPersonalDetails();
const currentUserEmail = currentUserDetails.email ?? '';
Expand Down Expand Up @@ -115,6 +120,8 @@ function KYCWall({
setPositionAddPaymentMenu(position);
}, [getAnchorPosition]);

const canLinkExistingBusinessBankAccount = getEligibleExistingBusinessBankAccounts(bankAccountList, currency, true).length > 0;

const selectPaymentMethod = useCallback(
(paymentMethod?: PaymentMethod, policy?: Policy) => {
if (paymentMethod) {
Expand Down Expand Up @@ -169,6 +176,22 @@ function KYCWall({
Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID));
return;
}

// If user has a setup in progress for we redirect to the flow where setup can be finished
// Setup is in progress in 2 cases:
// - account already present on policy is partially setup
// - account is being connected 'on the spot' while trying to pay for an expense (it won't be linked to policy yet but will appear as reimbursementAccount)
if (policy !== undefined && (isBankAccountPartiallySetup(policy?.achAccount?.state) || isBankAccountPartiallySetup(reimbursementAccount?.achData?.state))) {
navigateToBankAccountRoute(policy.id);
return;
}

// If user has existing bank accounts that he can connect we show the list of these accounts
if (policy !== undefined && canLinkExistingBusinessBankAccount) {
Navigation.navigate(ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.getRoute(policy?.id));
return;
}

const bankAccountRoute = addBankAccountRoute ?? getBankAccountRoute(chatReport);
Navigation.navigate(bankAccountRoute);
}
Expand All @@ -177,6 +200,8 @@ function KYCWall({
onSelectPaymentMethod,
iouReport,
addDebitCardRoute,
reimbursementAccount?.achData?.state,
canLinkExistingBusinessBankAccount,
addBankAccountRoute,
chatReport,
policies,
Expand Down Expand Up @@ -218,11 +243,14 @@ function KYCWall({

const isExpenseReport = isExpenseReportReportUtils(iouReport);
const paymentCardList = fundList ?? {};
const hasOpenConnectedBusinessBankAccount = getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList, policy).length > 0;
const hasValidPaymentMethod = hasExpensifyPaymentMethod(paymentCardList, bankAccountList, shouldIncludeDebitCard);
const isFromWalletPage = source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET || source === CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE;

// Check to see if user has a valid payment method on file and display the add payment popover if they don't
if ((isExpenseReport && reimbursementAccount?.achData?.state !== CONST.BANK_ACCOUNT.STATE.OPEN) || (!isExpenseReport && bankAccountList !== null && !hasValidPaymentMethod)) {
// Check if the user needs to add or select a payment method before continuing.
// - For expense reports: Proceeds if no accounts that are connected are valid and usable (`OPEN`)
// - For other expenses: Proceeds if the user lacks a valid personal bank account or debit card
if ((isExpenseReport && !hasOpenConnectedBusinessBankAccount) || (!isExpenseReport && bankAccountList !== null && !hasValidPaymentMethod)) {
Log.info('[KYC Wallet] User does not have valid payment method');

if (!shouldIncludeDebitCard || (isFromWalletPage && !hasValidPaymentMethod)) {
Expand Down Expand Up @@ -286,7 +314,6 @@ function KYCWall({
getAnchorPosition,
iouReport,
onSuccessfulKYC,
reimbursementAccount?.achData?.state,
selectPaymentMethod,
shouldIncludeDebitCard,
shouldShowAddPaymentMenu,
Expand Down
3 changes: 3 additions & 0 deletions src/components/KYCWall/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type KYCWallProps = {

/** Reference to the KYCWall component */
ref: ForwardedRef<KYCWallRef>;

/** Currency associated with the payment */
currency?: string;
};

type KYCWallRef = {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,14 @@ function Search({
}, [isSmallScreenWidth]);

useEffect(() => {
openSearch();
openSearch({includePartiallySetupBankAccounts: true});
}, []);

useEffect(() => {
if (!prevIsOffline || isOffline) {
return;
}
openSearch();
openSearch({includePartiallySetupBankAccounts: true});
}, [isOffline, prevIsOffline]);

const {newSearchResultKeys, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({
Expand Down
21 changes: 15 additions & 6 deletions src/components/SettlementButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function SettlementButton({
const hasSinglePolicy = !isExpenseReport && activeAdminPolicies.length === 1;
const hasMultiplePolicies = !isExpenseReport && activeAdminPolicies.length > 1;
const formattedPaymentMethods = formatPaymentMethods(bankAccountList ?? {}, fundList ?? {}, styles, translate);
const hasIntentToPay = ((formattedPaymentMethods.length === 1 && isIOUReport(iouReport)) || !!policy?.achAccount) && !lastPaymentMethod;
const hasIntentToPay = ((formattedPaymentMethods.length === 1 && isIOUReport(iouReport)) || policy?.achAccount?.state === CONST.BANK_ACCOUNT.STATE.OPEN) && !lastPaymentMethod;
const {isBetaEnabled} = usePermissions();
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
Expand All @@ -165,7 +165,9 @@ function SettlementButton({
if (!policy?.achAccount?.bankAccountID) {
return;
}
const policyBankAccounts = formattedPaymentMethods.filter((method) => method.methodID === policy?.achAccount?.bankAccountID);
const policyBankAccounts = formattedPaymentMethods.filter(
(method) => method.methodID === policy?.achAccount?.bankAccountID && (method.accountData as AccountData)?.state === CONST.BANK_ACCOUNT.STATE.OPEN,
);

return policyBankAccounts.map((formattedPaymentMethod) => {
const {icon, iconStyles, iconSize, title, description, methodID} = formattedPaymentMethod ?? {};
Expand Down Expand Up @@ -513,7 +515,9 @@ function SettlementButton({
return lastPaymentPolicy.name;
}

const bankAccountToDisplay = hasIntentToPay ? (formattedPaymentMethods.at(0) as BankAccount) : bankAccount;
const bankAccountToDisplay = hasIntentToPay
? ((formattedPaymentMethods.find((method) => method.methodID === policy?.achAccount?.bankAccountID) ?? formattedPaymentMethods.at(0)) as BankAccount)
: bankAccount;

if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || (hasIntentToPay && (isExpenseReport || isInvoiceReport))) {
if (isInvoiceReport) {
Expand All @@ -539,7 +543,7 @@ function SettlementButton({
return translate('paymentMethodList.bankAccountLastFour', bankAccountToDisplay?.accountData?.accountNumber?.slice(-4));
}

if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && isExpenseReportUtil(iouReport)) {
if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.methodID === policy?.achAccount?.bankAccountID && isExpenseReportUtil(iouReport)) {
return translate('paymentMethodList.bankAccountLastFour', bankAccount?.accountData?.accountNumber?.slice(-4) ?? '');
}

Expand All @@ -551,9 +555,13 @@ function SettlementButton({
return;
}

const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItem);
const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItem, policyIDKey);

if (!!selectedPolicy || shouldSelectPaymentMethod) {
// Payment type for 'Pay via workspace' option is "Elsewhere" but selected option points to one of workspaces where user is admin
const isPayingViaWorkspace = paymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE && activeAdminPolicies.find((activeAdminPolicy) => activeAdminPolicy.id === selectedOption);
const isPayingWithMethod = paymentType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE;

if ((!!selectedPolicy || shouldSelectPaymentMethod) && (isPayingWithMethod || isPayingViaWorkspace)) {
selectPaymentMethod(event, paymentType, triggerKYCFlow, selectedOption as PaymentMethod, selectedPolicy);
return;
}
Expand Down Expand Up @@ -597,6 +605,7 @@ function SettlementButton({
policy={lastPaymentPolicy}
anchorAlignment={kycWallAnchorAlignment}
shouldShowPersonalBankAccountOption={shouldShowPersonalBankAccountOption}
currency={currency}
>
{(triggerKYCFlow, buttonRef) => (
<ButtonWithDropdownMenu<string>
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/OpenSearchPageParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type OpenSearchPageParams = {
includePartiallySetupBankAccounts: boolean;
};

export default OpenSearchPageParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ export type {default as SetSamlIdentityParams} from './SetSamlIdentityParams';
export type {default as UpdateSamlEnabledParams} from './UpdateSamlEnabledParams';
export type {default as AddAdminToDomainParams} from './AddAdminToDomainParams';
export type {default as UpdateSamlRequiredParams} from './UpdateSamlRequiredParams';
export type {default as OpenSearchPageParams} from './OpenSearchPageParams';
export type {default as SetPolicyRequireCompanyCardsEnabledParams} from './SetPolicyRequireCompanyCardsEnabled';
export type {default as SetTechnicalContactEmailParams} from './SetTechnicalContactEmailParams';
export type {default as ToggleConsolidatedDomainBillingParams} from './ToggleConsolidatedDomainBillingParams';
2 changes: 1 addition & 1 deletion src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_ONFIDO_FLOW]: null;
[READ_COMMANDS.OPEN_INITIAL_SETTINGS_PAGE]: null;
[READ_COMMANDS.OPEN_ENABLE_PAYMENTS_PAGE]: null;
[READ_COMMANDS.OPEN_SEARCH_PAGE]: null;
[READ_COMMANDS.OPEN_SEARCH_PAGE]: Parameters.OpenSearchPageParams;
[READ_COMMANDS.SEARCH]: Parameters.SearchParams;
[READ_COMMANDS.BEGIN_SIGNIN]: Parameters.BeginSignInParams;
[READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN]: Parameters.SignInWithShortLivedAuthTokenParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_CUSTOMERS_DISPLAYED_AS]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersDisplayedAsPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ITEMS]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage').default,
[SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: () =>
require<ReactComponentModule>('../../../../pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage').default,
[SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: () => require<ReactComponentModule>('@pages/workspace/ConnectExistingBusinessBankAccountPage').default,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/ReimbursementAccountPage').default,
[SCREENS.REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/ReimbursementAccountVerifyAccountPage').default,
[SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]: () => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/EnterSignerInfo').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ const WORKSPACE_TO_RHP: Partial<Record<keyof WorkspaceSplitNavigatorParamList, s
SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY,
SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET,
SCREENS.WORKSPACE.WORKFLOWS_PAYER,
SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT,
SCREENS.WORKSPACE.RULES_AUTO_APPROVE_REPORTS_UNDER,
SCREENS.WORKSPACE.RULES_RANDOM_REPORT_AUDIT,
SCREENS.WORKSPACE.RULES_AUTO_PAY_REPORTS_UNDER,
Expand Down
4 changes: 2 additions & 2 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1032,8 +1032,8 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
[SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: {
path: ROUTES.WORKSPACE_EDIT_REPORT_FIELDS_INITIAL_VALUE.route,
},
[SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: {
path: ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.route,
[SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: {
path: ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.route,
exact: true,
},
[SCREENS.REIMBURSEMENT_ACCOUNT]: {
Expand Down
10 changes: 7 additions & 3 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,12 @@ type ReimbursementAccountEnterSignerInfoNavigatorParamList = {
};
};

type ConnectExistingBankAccountNavigatorParamList = {
[SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: {
policyID: string;
};
};

type WalletStatementNavigatorParamList = {
[SCREENS.WALLET_STATEMENT_ROOT]: {
/** The statement year and month as one string, i.e. 202110 */
Expand Down Expand Up @@ -2409,9 +2415,6 @@ type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: {
policyID: string;
};
[SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: {
policyID: string;
};
[SCREENS.WORKSPACE.INVOICES]: {
policyID: string;
};
Expand Down Expand Up @@ -2941,6 +2944,7 @@ export type {
ReportVerifyAccountNavigatorParamList,
ReimbursementAccountNavigatorParamList,
ReimbursementAccountEnterSignerInfoNavigatorParamList,
ConnectExistingBankAccountNavigatorParamList,
NewReportWorkspaceSelectionNavigatorParamList,
ReportDescriptionNavigatorParamList,
ReportDetailsNavigatorParamList,
Expand Down
5 changes: 3 additions & 2 deletions src/libs/PaymentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,11 @@ const isSecondaryActionAPaymentOption = (item: PopoverMenuItem): item is Payment
* Get the appropriate payment type, selected policy, and whether a payment method should be selected
* based on the provided payment method, active admin policies, and latest bank items.
*/
function getActivePaymentType(paymentMethod: string | undefined, activeAdminPolicies: Policy[], latestBankItems: BankAccountMenuItem[] | undefined) {
function getActivePaymentType(paymentMethod: string | undefined, activeAdminPolicies: Policy[], latestBankItems: BankAccountMenuItem[] | undefined, policyID?: string | undefined) {
const isPaymentMethod = Object.values(CONST.PAYMENT_METHODS).includes(paymentMethod as ValueOf<typeof CONST.PAYMENT_METHODS>);
const shouldSelectPaymentMethod = isPaymentMethod || !isEmpty(latestBankItems);
const selectedPolicy = activeAdminPolicies.find((activePolicy) => activePolicy.id === paymentMethod);
// payment method is equal to policyID when user selects "Pay via workspace" option
const selectedPolicy = activeAdminPolicies.find((activePolicy) => activePolicy.id === policyID || activePolicy.id === paymentMethod);

let paymentType;
switch (paymentMethod) {
Expand Down
Loading
Loading