Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor VBBA setup flow with new API ConnectBankAccountWithPlaid #11162

Merged
merged 8 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here will conflict with https://github.com/Expensify/App/pull/10953/files#diff-2a35d342f7be1cc5c1a8f02937f635a5c7083ec66b64b400027d65087d400e09 👀

cc: @ctkochan22

I do like the idea of using the plaidAccountID instead of the index, though.

*/
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
29 changes: 12 additions & 17 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,20 @@ function setPersonalBankAccountFormValidationErrorFields(errorFields) {
/**
* Set the current fields with errors.
*
* @param {String} errors
* @param {Object} errorFields
*/
function setBankAccountFormValidationErrors(errors) {
function setBankAccountFormValidationErrors(errorFields) {
// We set 'errors' to null first because we don't have a way yet to replace a specific property like 'errors' without merging it
MariaHCD marked this conversation as resolved.
Show resolved Hide resolved
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors: null});
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {errors});
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 +37,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
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
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