Skip to content

Commit

Permalink
Merge pull request #39038 from koko57/refactor/36648-wallet-enablemen…
Browse files Browse the repository at this point in the history
…t-flow

Refactor/36648 wallet enablement flow
  • Loading branch information
mountiny authored Apr 4, 2024
2 parents a1f9cec + 12c690c commit 5cb1d90
Show file tree
Hide file tree
Showing 20 changed files with 583 additions and 54 deletions.
18 changes: 18 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,24 @@ const CONST = {
TERMS: 'TermsStep',
ACTIVATE: 'ActivateStep',
},
STEP_REFACTOR: {
ADD_BANK_ACCOUNT: 'AddBankAccountStep',
ADDITIONAL_DETAILS: 'AdditionalDetailsStep',
VERIFY_IDENTITY: 'VerifyIdentityStep',
TERMS_AND_FEES: 'TermsAndFeesStep',
},
STEP_NAMES: ['1', '2', '3', '4'],
SUBSTEP_INDEXES: {
BANK_ACCOUNT: {
ACCOUNT_NUMBERS: 0,
},
PERSONAL_INFO: {
LEGAL_NAME: 0,
DATE_OF_BIRTH: 1,
SSN: 2,
ADDRESS: 3,
},
},
TIER_NAME: {
PLATINUM: 'PLATINUM',
GOLD: 'GOLD',
Expand Down
6 changes: 3 additions & 3 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,8 @@ const ONYXKEYS = {
REPORT_FIELD_EDIT_FORM_DRAFT: 'reportFieldEditFormDraft',
REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount',
REIMBURSEMENT_ACCOUNT_FORM_DRAFT: 'reimbursementAccountDraft',
PERSONAL_BANK_ACCOUNT: 'personalBankAccountForm',
PERSONAL_BANK_ACCOUNT_DRAFT: 'personalBankAccountFormDraft',
PERSONAL_BANK_ACCOUNT_FORM: 'personalBankAccountForm',
PERSONAL_BANK_ACCOUNT_FORM_DRAFT: 'personalBankAccountFormDraft',
EXIT_SURVEY_REASON_FORM: 'exitSurveyReasonForm',
EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft',
EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm',
Expand Down Expand Up @@ -491,7 +491,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: FormTypes.GetPhysicalCardForm;
[ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM]: FormTypes.ReportFieldEditForm;
[ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm;
[ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm;
[ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM]: FormTypes.PersonalBankAccountForm;
[ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm;
[ONYXKEYS.FORMS.WALLET_ADDITIONAL_DETAILS]: FormTypes.AdditionalDetailStepForm;
[ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm;
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const ROUTES = {
},
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
SETTINGS_ADD_BANK_ACCOUNT_REFACTOR: 'settings/wallet/add-bank-account-refactor',
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: {
route: 'settings/wallet/card/:domain/digital-details/update-address',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const SCREENS = {
APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links',
ADD_DEBIT_CARD: 'Settings_Add_Debit_Card',
ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account',
ADD_BANK_ACCOUNT_REFACTOR: 'Settings_Add_Bank_Account_Refactor',
CLOSE: 'Settings_Close',
TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth',
REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged',
Expand Down
8 changes: 6 additions & 2 deletions src/components/AddPlaidBankAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ type AddPlaidBankAccountProps = AddPlaidBankAccountOnyxProps & {
/** Is displayed in new VBBA */
isDisplayedInNewVBBA?: boolean;

/** Is displayed in new enable wallet flow */
isDisplayedInWalletFlow?: boolean;

/** Text to display on error message */
errorText?: string;

Expand All @@ -84,6 +87,7 @@ function AddPlaidBankAccount({
isDisplayedInNewVBBA = false,
errorText = '',
onInputChange = () => {},
isDisplayedInWalletFlow = false,
}: AddPlaidBankAccountProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -255,10 +259,10 @@ function AddPlaidBankAccount({
return <FullPageOfflineBlockingView>{renderPlaidLink()}</FullPageOfflineBlockingView>;
}

if (isDisplayedInNewVBBA) {
if (isDisplayedInNewVBBA || isDisplayedInWalletFlow) {
return (
<FullPageOfflineBlockingView>
<Text style={[styles.mb3, styles.textHeadline]}>{translate('bankAccount.chooseAnAccount')}</Text>
<Text style={[styles.mb3, styles.textHeadlineLineHeightXXL]}>{translate(isDisplayedInWalletFlow ? 'walletPage.chooseYourBankAccount' : 'bankAccount.chooseAnAccount')}</Text>
{!!text && <Text style={[styles.mb6, styles.textSupporting]}>{text}</Text>}
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mb6]}>
<Icon
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,11 @@ export default {
expensifyCard: 'Expensify Card',
walletActivationPending: "We're reviewing your information, please check back in a few minutes!",
walletActivationFailed: 'Unfortunately your wallet cannot be enabled at this time. Please chat with Concierge for further assistance.',
addYourBankAccount: 'Add your bank account.',
addBankAccountBody: "Let's connect your bank account to Expensify so it’s easier than ever to send and receive payments directly in the app.",
chooseYourBankAccount: 'Choose your bank account.',
chooseAccountBody: 'Make sure that you select the right one.',
confirmYourBankAccount: 'Confirm your bank account.',
},
cardPage: {
expensifyCard: 'Expensify Card',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,11 @@ export default {
expensifyCard: 'Tarjeta Expensify',
walletActivationPending: 'Estamos revisando su información, por favor vuelve en unos minutos.',
walletActivationFailed: 'Lamentablemente, no podemos activar tu billetera en este momento. Chatea con Concierge para obtener más ayuda.',
addYourBankAccount: 'Añadir tu cuenta bancaria.',
addBankAccountBody: 'Conectemos tu cuenta bancaria a Expensify para que sea más fácil que nunca enviar y recibir pagos directamente en la aplicación.',
chooseYourBankAccount: 'Elige tu cuenta bancaria.',
chooseAccountBody: 'Asegúrese de elegir el adecuado.',
confirmYourBankAccount: 'Confirma tu cuenta bancaria.',
},
cardPage: {
expensifyCard: 'Tarjeta Expensify',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType,
[SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType,
[SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default as React.ComponentType,
[SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: () => require('../../../../pages/EnablePayments/AddBankAccount/AddBankAccount').default as React.ComponentType,
[SCREENS.SETTINGS.PROFILE.STATUS]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusPage').default as React.ComponentType,
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER]: () => require('../../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default as React.ComponentType,
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: () => require('../../../../pages/settings/Profile/CustomStatus/SetDatePage').default as React.ComponentType,
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT,
exact: true,
},
[SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: {
path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT_REFACTOR,
exact: true,
},
[SCREENS.SETTINGS.PROFILE.PRONOUNS]: {
path: ROUTES.SETTINGS_PRONOUNS,
exact: true,
Expand Down
41 changes: 41 additions & 0 deletions src/libs/actions/BankAccounts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Onyx from 'react-native-onyx';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import type {OnfidoDataWithApplicantID} from '@components/Onfido/types';
import * as API from '@libs/API';
import type {
Expand All @@ -18,6 +19,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {PersonalBankAccountForm} from '@src/types/form';
import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm';
import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount';
import type {BankAccountStep, BankAccountSubStep} from '@src/types/onyx/ReimbursementAccount';
Expand All @@ -42,6 +44,8 @@ type ReimbursementAccountStep = BankAccountStep | '';

type ReimbursementAccountSubStep = BankAccountSubStep | '';

type AccountFormValues = typeof ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM | typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM;

type BusinessAddress = {
addressStreet?: string;
addressCity?: string;
Expand Down Expand Up @@ -82,6 +86,23 @@ function openPersonalBankAccountSetupView(exitReportID?: string) {
});
}

/**
* TODO: remove the previous function and rename this function to openPersonalBankAccountSetupView after migrating to the new flow
* Open the personal bank account setup flow, with an optional exitReportID to redirect to once the flow is finished.
*/
function openPersonalBankAccountSetupViewRefactor(exitReportID?: string) {
clearPlaid().then(() => {
if (exitReportID) {
Onyx.merge(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {exitReportID});
}
Onyx.merge(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, {setupType: CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID});
});
}

function clearPersonalBankAccountSetupType() {
Onyx.merge(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, {setupType: null});
}

/**
* Whether after adding a bank account we should continue with the KYC flow. If so, we must specify the fallback route.
*/
Expand All @@ -92,13 +113,19 @@ function setPersonalBankAccountContinueKYCOnSuccess(onSuccessFallbackRoute: Rout
function clearPersonalBankAccount() {
clearPlaid();
Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {});
Onyx.set(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, null);
clearPersonalBankAccountSetupType();
}

function clearOnfidoToken() {
Onyx.merge(ONYXKEYS.ONFIDO_TOKEN, '');
Onyx.merge(ONYXKEYS.ONFIDO_APPLICANT_ID, '');
}

function updateAddPersonalBankAccountDraft(bankData: Partial<PersonalBankAccountForm>) {
Onyx.merge(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, bankData);
}

/**
* Helper method to build the Onyx data required during setup of a Verified Business Bank Account
*/
Expand Down Expand Up @@ -503,6 +530,16 @@ function setReimbursementAccountLoading(isLoading: boolean) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isLoading});
}

function validatePlaidSelection(values: FormOnyxValues<AccountFormValues>): FormInputErrors<AccountFormValues> {
const errorFields: FormInputErrors<AccountFormValues> = {};

if (!values.selectedPlaidAccountID) {
errorFields.selectedPlaidAccountID = 'bankAccount.error.youNeedToSelectAnOption';
}

return errorFields;
}

export {
acceptACHContractForBankAccount,
addBusinessWebsiteForDraft,
Expand All @@ -527,6 +564,10 @@ export {
validateBankAccount,
verifyIdentityForBankAccount,
setReimbursementAccountLoading,
openPersonalBankAccountSetupViewRefactor,
updateAddPersonalBankAccountDraft,
clearPersonalBankAccountSetupType,
validatePlaidSelection,
};

export type {BusinessAddress, PersonalAddress};
4 changes: 3 additions & 1 deletion src/libs/getPlaidDesktopMessage/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type GetPlaidDesktopMessage = () => string | undefined;
import type {TranslationPaths} from '@src/languages/types';

type GetPlaidDesktopMessage = () => TranslationPaths | undefined;

export default GetPlaidDesktopMessage;
2 changes: 1 addition & 1 deletion src/pages/AddPersonalBankAccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function AddPersonalBankAccountPage({personalBankAccount, plaidData}: AddPersona
/>
) : (
<FormProvider
formID={ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT}
formID={ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM}
isSubmitButtonVisible={Boolean(selectedPlaidAccountId)}
submitButtonText={translate('common.saveAndContinue')}
scrollContextEnabled
Expand Down
128 changes: 128 additions & 0 deletions src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, {useCallback, useEffect} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useSubStep from '@hooks/useSubStep';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@navigation/Navigation';
import * as BankAccounts from '@userActions/BankAccounts';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalBankAccountForm} from '@src/types/form';
import type {PersonalBankAccount, PlaidData} from '@src/types/onyx';
import SetupMethod from './SetupMethod';
import Confirmation from './substeps/ConfirmationStep';
import Plaid from './substeps/PlaidStep';

type AddPersonalBankAccountPageWithOnyxProps = {
/** Contains plaid data */
plaidData: OnyxEntry<PlaidData>;

/** The details about the Personal bank account we are adding saved in Onyx */
personalBankAccount: OnyxEntry<PersonalBankAccount>;

/** The draft values of the bank account being setup */
personalBankAccountDraft: OnyxEntry<PersonalBankAccountForm>;
};

const plaidSubsteps: Array<React.ComponentType<SubStepProps>> = [Plaid, Confirmation];

function AddBankAccount({personalBankAccount, plaidData, personalBankAccountDraft}: AddPersonalBankAccountPageWithOnyxProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const submit = useCallback(() => {
const bankAccounts = plaidData?.bankAccounts ?? [];
const selectedPlaidBankAccount = bankAccounts.find((bankAccount) => bankAccount.plaidAccountID === personalBankAccountDraft?.plaidAccountID);

if (selectedPlaidBankAccount) {
BankAccounts.addPersonalBankAccount(selectedPlaidBankAccount);
Navigation.navigate(ROUTES.SETTINGS_ENABLE_PAYMENTS);
}
}, [personalBankAccountDraft?.plaidAccountID, plaidData?.bankAccounts]);

const isSetupTypeChosen = personalBankAccountDraft?.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID;

const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent: plaidSubsteps, startFrom: 0, onFinished: submit});

const exitFlow = (shouldContinue = false) => {
const exitReportID = personalBankAccount?.exitReportID;
const onSuccessFallbackRoute = personalBankAccount?.onSuccessFallbackRoute ?? '';

if (exitReportID) {
Navigation.dismissModal(exitReportID);
return;
}
if (shouldContinue && onSuccessFallbackRoute) {
PaymentMethods.continueSetup(onSuccessFallbackRoute);
return;
}
Navigation.goBack(ROUTES.SETTINGS_WALLET);
};

const handleBackButtonPress = () => {
if (!isSetupTypeChosen) {
exitFlow();
return;
}
if (screenIndex === 0) {
BankAccounts.clearPersonalBankAccount();
return;
}
prevScreen();
};

useEffect(() => BankAccounts.clearPersonalBankAccount, []);

return (
<ScreenWrapper
testID={AddBankAccount.displayName}
includeSafeAreaPaddingBottom={false}
shouldEnablePickerAvoiding={false}
>
<HeaderWithBackButton
shouldShowBackButton
onBackButtonPress={handleBackButtonPress}
title={translate('bankAccount.addBankAccount')}
/>
{isSetupTypeChosen ? (
<>
<View style={[styles.ph5, styles.mb5, styles.mt3, {height: CONST.BANK_ACCOUNT.STEPS_HEADER_HEIGHT}]}>
<InteractiveStepSubHeader
startStepIndex={0}
stepNames={CONST.WALLET.STEP_NAMES}
/>
</View>
<SubStep
isEditing={isEditing}
onNext={nextScreen}
onMove={moveTo}
/>
</>
) : (
<SetupMethod />
)}
</ScreenWrapper>
);
}

AddBankAccount.displayName = 'AddBankAccountPage';

export default withOnyx<AddPersonalBankAccountPageWithOnyxProps, AddPersonalBankAccountPageWithOnyxProps>({
plaidData: {
key: ONYXKEYS.PLAID_DATA,
},
personalBankAccount: {
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
},
personalBankAccountDraft: {
key: ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT,
},
})(AddBankAccount);
Loading

0 comments on commit 5cb1d90

Please sign in to comment.