Skip to content

Commit

Permalink
Merge pull request #11162 from Expensify/nat-VBAnewAPIs
Browse files Browse the repository at this point in the history
Refactor VBBA setup flow with new API ConnectBankAccountWithPlaid
  • Loading branch information
nkuoch authored Sep 26, 2022
2 parents 3d7ff97 + dc8ba42 commit 6bd5304
Show file tree
Hide file tree
Showing 29 changed files with 648 additions and 546 deletions.
6 changes: 5 additions & 1 deletion src/components/AddPaymentMethodMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CONST from '../CONST';
import withWindowDimensions from './withWindowDimensions';
import Permissions from '../libs/Permissions';
import PopoverMenu from './PopoverMenu';
import * as BankAccounts from '../libs/actions/BankAccounts';

const propTypes = {
isVisible: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -47,7 +48,10 @@ const AddPaymentMethodMenu = props => (
{
text: props.translate('common.bankAccount'),
icon: Expensicons.Bank,
onSelected: () => props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT),
onSelected: () => {
BankAccounts.clearPlaid();
props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT);
},
},
...(Permissions.canUseWallet(props.betas) ? [{
text: props.translate('common.debitCard'),
Expand Down
43 changes: 18 additions & 25 deletions src/components/AddPlaidBankAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const defaultProps = {
bankName: '',
plaidAccessToken: '',
bankAccounts: [],
loading: false,
isLoading: false,
error: '',
},
plaidLinkToken: '',
Expand All @@ -77,21 +77,14 @@ class AddPlaidBankAccount extends React.Component {

this.selectAccount = this.selectAccount.bind(this);
this.getPlaidLinkToken = this.getPlaidLinkToken.bind(this);

this.state = {
selectedIndex: undefined,
institution: {},
};
}

componentDidMount() {
// If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken
// Otherwise, clear the existing token and fetch a new one
if (this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) {
if ((this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) || !_.isEmpty(this.props.plaidData)) {
return;
}

BankAccounts.clearPlaid();
BankAccounts.openPlaidBankLogin(this.props.allowDebit, this.props.bankAccountID);
}

Expand Down Expand Up @@ -119,30 +112,30 @@ class AddPlaidBankAccount extends React.Component {

/**
* Triggered when user selects a Plaid bank account.
* @param {String} index
* @param {String} plaidAccountID
*/
selectAccount(index) {
this.setState({selectedIndex: Number(index)}, () => {
const selectedPlaidBankAccount = this.getPlaidBankAccounts()[this.state.selectedIndex];
selectedPlaidBankAccount.bankName = this.props.plaidData.bankName;
selectedPlaidBankAccount.plaidAccessToken = this.props.plaidData.plaidAccessToken;
this.props.onSelect({selectedPlaidBankAccount});
});
selectAccount(plaidAccountID) {
const selectedPlaidBankAccount = _.findWhere(this.getPlaidBankAccounts(), {plaidAccountID});
selectedPlaidBankAccount.bankName = this.props.plaidData.bankName;
selectedPlaidBankAccount.plaidAccessToken = this.props.plaidData.plaidAccessToken;
this.props.onSelect({selectedPlaidBankAccount});
}

render() {
const plaidBankAccounts = this.getPlaidBankAccounts();
const token = this.getPlaidLinkToken();
const options = _.map(plaidBankAccounts, (account, index) => ({
value: index, label: `${account.addressName} ${account.mask}`,
const options = _.map(plaidBankAccounts, account => ({
value: account.plaidAccountID, label: `${account.addressName} ${account.mask}`,
}));
const {icon, iconSize} = getBankIcon(this.state.institution.name);
const institutionName = lodashGet(this.props, 'plaidData.institution.name', '');
const selectedPlaidBankAccount = lodashGet(this.props, 'plaidData.selectedPlaidBankAccount', {});
const {icon, iconSize} = getBankIcon();

// Plaid Link view
if (!plaidBankAccounts.length) {
return (
<FullPageOfflineBlockingView>
{(!token || this.props.plaidData.loading)
{(!token || this.props.plaidData.isLoading)
&& (
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter]}>
<ActivityIndicator color={themeColors.spinner} size="large" />
Expand All @@ -159,7 +152,7 @@ class AddPlaidBankAccount extends React.Component {
onSuccess={({publicToken, metadata}) => {
Log.info('[PlaidLink] Success!');
BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, this.props.allowDebit);
this.setState({institution: metadata.institution});
BankAccounts.updatePlaidData({institution: metadata.institution});
}}
onError={(error) => {
Log.hmmm('[PlaidLink] Error: ', error.message);
Expand Down Expand Up @@ -187,18 +180,18 @@ class AddPlaidBankAccount extends React.Component {
height={iconSize}
width={iconSize}
/>
<Text style={[styles.ml3, styles.textStrong]}>{this.state.institution.name}</Text>
<Text style={[styles.ml3, styles.textStrong]}>{institutionName}</Text>
</View>
<View style={[styles.mb5]}>
<Picker
label={this.props.translate('addPersonalBankAccountPage.chooseAccountLabel')}
onInputChange={this.selectAccount}
items={options}
placeholder={_.isUndefined(this.state.selectedIndex) ? {
placeholder={_.isUndefined(this.props.plaidData.selectedPlaidBankAccount) ? {
value: '',
label: this.props.translate('bankAccount.chooseAnAccount'),
} : {}}
value={this.state.selectedIndex}
value={selectedPlaidBankAccount.plaidAccountID}
/>
</View>
</View>
Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReimbursementAccountUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as BankAccounts from './actions/BankAccounts';
import FormHelper from './FormHelper';

const formHelper = new FormHelper({
errorPath: 'reimbursementAccount.errors',
errorPath: 'reimbursementAccount.errorFields',
setErrors: BankAccounts.setBankAccountFormValidationErrors,
});

Expand Down Expand Up @@ -32,7 +32,7 @@ function getDefaultStateForField(props, fieldName, defaultValue = '') {
* @returns {String}
*/
function getErrorText(props, errorTranslationKeys, inputKey) {
const errors = getErrors(props);
const errors = getErrors(props) || {};
return errors[inputKey] ? props.translate(errorTranslationKeys[inputKey]) : '';
}

Expand Down
4 changes: 2 additions & 2 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ function validateIdentity(identity) {
*/
function isValidUSPhone(phoneNumber = '', isCountryCodeOptional) {
// Remove non alphanumeric characters from the phone number
const sanitizedPhone = phoneNumber.replace(CONST.REGEX.NON_ALPHA_NUMERIC, '');
const sanitizedPhone = (phoneNumber || '').replace(CONST.REGEX.NON_ALPHA_NUMERIC, '');
const isUsPhone = isCountryCodeOptional
? CONST.REGEX.US_PHONE_WITH_OPTIONAL_COUNTRY_CODE.test(sanitizedPhone) : CONST.REGEX.US_PHONE.test(sanitizedPhone);

Expand Down Expand Up @@ -370,7 +370,7 @@ function isExistingRoomName(roomName, reports, policyID) {
* @returns {Boolean}
*/
function isValidTaxID(taxID) {
return CONST.REGEX.TAX_ID.test(taxID.replace(CONST.REGEX.NON_NUMERIC, ''));
return taxID && CONST.REGEX.TAX_ID.test(taxID.replace(CONST.REGEX.NON_NUMERIC, ''));
}

export {
Expand Down
35 changes: 30 additions & 5 deletions src/libs/actions/BankAccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export {
setupWithdrawalAccount,
fetchFreePlanVerifiedBankAccount,
goToWithdrawalAccountSetupStep,
showBankAccountErrorModal,
showBankAccountFormValidationError,
setBankAccountFormValidationErrors,
resetReimbursementAccount,
resetFreePlanBankAccount,
Expand Down Expand Up @@ -42,6 +40,10 @@ function clearPlaid() {
Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, '');
}

function updatePlaidData(plaidData) {
Onyx.merge(ONYXKEYS.PLAID_DATA, plaidData);
}

/**
* Helper method to build the Onyx data required during setup of a Verified Business Bank Account
*
Expand Down Expand Up @@ -86,6 +88,27 @@ function getVBBADataForOnyx() {
};
}

/**
* Submit Bank Account step with Plaid data so php can perform some checks.
*
* @param {Number} bankAccountID
* @param {Object} selectedPlaidBankAccount
*/
function connectBankAccountWithPlaid(bankAccountID, selectedPlaidBankAccount) {
const commandName = 'ConnectBankAccountWithPlaid';

const parameters = {
bankAccountID,
routingNumber: selectedPlaidBankAccount.routingNumber,
accountNumber: selectedPlaidBankAccount.accountNumber,
bank: selectedPlaidBankAccount.bankName,
plaidAccountID: selectedPlaidBankAccount.plaidAccountID,
plaidAccessToken: selectedPlaidBankAccount.plaidAccessToken,
};

API.write(commandName, parameters, getVBBADataForOnyx());
}

/**
* Adds a bank account via Plaid
*
Expand Down Expand Up @@ -114,7 +137,7 @@ function addPersonalBankAccount(account, password) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: true,
isLoading: true,
error: '',
},
},
Expand All @@ -124,7 +147,7 @@ function addPersonalBankAccount(account, password) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: false,
isLoading: false,
error: '',
shouldShowSuccess: true,
},
Expand All @@ -135,7 +158,7 @@ function addPersonalBankAccount(account, password) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_BANK_ACCOUNT,
value: {
loading: false,
isLoading: false,
error: Localize.translateLocal('paymentsPage.addBankAccountFailure'),
},
},
Expand Down Expand Up @@ -199,4 +222,6 @@ export {
clearPersonalBankAccount,
clearPlaid,
validateBankAccount,
connectBankAccountWithPlaid,
updatePlaidData,
};
6 changes: 3 additions & 3 deletions src/libs/actions/Plaid.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function openPlaidBankAccountSelector(publicToken, bankName, allowDebit) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: true,
isLoading: true,
error: '',
bankName,
},
Expand All @@ -40,15 +40,15 @@ function openPlaidBankAccountSelector(publicToken, bankName, allowDebit) {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: false,
isLoading: false,
error: '',
},
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PLAID_DATA,
value: {
loading: false,
isLoading: false,
error: Localize.translateLocal('bankAccount.error.noBankAccountAvailable'),
},
}],
Expand Down
30 changes: 12 additions & 18 deletions src/libs/actions/ReimbursementAccount/errors.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../../ONYXKEYS';

/**
* Show error modal and optionally a specific error message
*
* @param {String} errorModalMessage The error message to be displayed in the modal's body.
* @param {Boolean} isErrorModalMessageHtml if @errorModalMessage is in html format or not
*/
function showBankAccountErrorModal(errorModalMessage = null, isErrorModalMessageHtml = false) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorModalMessage, isErrorModalMessageHtml});
}
import DateUtils from '../../DateUtils';

/**
* Set the current fields with errors.
Expand All @@ -24,20 +15,19 @@ function setPersonalBankAccountFormValidationErrorFields(errorFields) {
/**
* Set the current fields with errors.
*
* @param {String} errors
* @param {Object} errorFields
*/
function setBankAccountFormValidationErrors(errors) {
// We set 'errors' to null first because we don't have a way yet to replace a specific property like 'errors' without merging it
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors});
function setBankAccountFormValidationErrors(errorFields) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields: null});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errorFields});
}

/**
* Clear validation messages from reimbursement account
*/
function resetReimbursementAccount() {
setBankAccountFormValidationErrors({});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {successRoute: null});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null});
}

/**
Expand All @@ -46,11 +36,15 @@ function resetReimbursementAccount() {
* @param {String} error
*/
function showBankAccountFormValidationError(error) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {
// eslint-disable-next-line rulesdir/prefer-localization
errors: {
[DateUtils.getMicroseconds()]: error,
},
});
}

export {
showBankAccountErrorModal,
setBankAccountFormValidationErrors,
setPersonalBankAccountFormValidationErrorFields,
showBankAccountFormValidationError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import BankAccount from '../../models/BankAccount';
* @returns {Object}
*/
function getInitialData(localBankAccountState) {
const initialData = {loading: true, error: ''};
const initialData = {isLoading: true, error: ''};

// Some UI needs to know the bank account state during the loading process, so we are keeping it in Onyx if passed
if (localBankAccountState) {
Expand Down Expand Up @@ -73,7 +73,7 @@ function fetchNameValuePairsAndBankAccount() {
};
})
.finally(() => {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {isLoading: false});
});
}

Expand Down Expand Up @@ -187,7 +187,12 @@ function fetchFreePlanVerifiedBankAccount(stepToOpen, localBankAccountState) {
throttledDate,
maxAttemptsReached,
error: '',
isLoading: false,
});
Onyx.merge(ONYXKEYS.PLAID_DATA, {
isPlaidDisabled,
error: '',
isLoading: false,
});
});
}
Expand Down
1 change: 0 additions & 1 deletion src/libs/actions/ReimbursementAccount/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import deleteFromBankAccountList from './deleteFromBankAccountList';

export {goToWithdrawalAccountSetupStep, navigateToBankAccountRoute} from './navigation';
export {
showBankAccountErrorModal,
setBankAccountFormValidationErrors,
setPersonalBankAccountFormValidationErrorFields,
resetReimbursementAccount,
Expand Down
Loading

0 comments on commit 6bd5304

Please sign in to comment.