diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js
index d50fad0bd2f0..dbe7e46ff6aa 100644
--- a/src/components/AddPlaidBankAccount.js
+++ b/src/components/AddPlaidBankAccount.js
@@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import Log from '../libs/Log';
import PlaidLink from './PlaidLink';
+import * as App from '../libs/actions/App';
import * as BankAccounts from '../libs/actions/BankAccounts';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
@@ -22,6 +23,9 @@ import useLocalize from '../hooks/useLocalize';
import useNetwork from '../hooks/useNetwork';
const propTypes = {
+ /** If the user has been throttled from Plaid */
+ isPlaidDisabled: PropTypes.bool,
+
/** Contains plaid data */
plaidData: plaidDataPropTypes.isRequired,
@@ -63,9 +67,22 @@ const defaultProps = {
plaidLinkOAuthToken: '',
allowDebit: false,
bankAccountID: 0,
+ isPlaidDisabled: false,
};
-function AddPlaidBankAccount({plaidData, selectedPlaidAccountID, plaidLinkToken, onExitPlaid, onSelect, text, receivedRedirectURI, plaidLinkOAuthToken, bankAccountID, allowDebit}) {
+function AddPlaidBankAccount({
+ plaidData,
+ selectedPlaidAccountID,
+ plaidLinkToken,
+ onExitPlaid,
+ onSelect,
+ text,
+ receivedRedirectURI,
+ plaidLinkOAuthToken,
+ bankAccountID,
+ allowDebit,
+ isPlaidDisabled,
+}) {
const subscribedKeyboardShortcuts = useRef([]);
const previousNetworkState = useRef();
@@ -154,6 +171,14 @@ function AddPlaidBankAccount({plaidData, selectedPlaidAccountID, plaidLinkToken,
const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : '';
const bankName = lodashGet(plaidData, 'bankName');
+ if (isPlaidDisabled) {
+ return (
+
+ {translate('bankAccount.error.tooManyAttempts')}
+
+ );
+ }
+
// Plaid Link view
if (!plaidBankAccounts.length) {
return (
@@ -177,6 +202,20 @@ function AddPlaidBankAccount({plaidData, selectedPlaidAccountID, plaidLinkToken,
onError={(error) => {
Log.hmmm('[PlaidLink] Error: ', error.message);
}}
+ onEvent={(event, metadata) => {
+ // Handle Plaid login errors (will potentially reset plaid token and item depending on the error)
+ if (event === 'ERROR') {
+ Log.hmmm('[PlaidLink] Error: ', metadata);
+ if (bankAccountID && metadata.error_code) {
+ BankAccounts.handlePlaidError(bankAccountID, metadata.error_code, metadata.error_message, metadata.request_id);
+ }
+ }
+
+ // Limit the number of times a user can submit Plaid credentials
+ if (event === 'SUBMIT_CREDENTIALS') {
+ App.handleRestrictedEvent(event);
+ }
+ }}
// User prematurely exited the Plaid flow
// eslint-disable-next-line react/jsx-props-no-multi-spaces
onExit={onExitPlaid}
@@ -224,4 +263,7 @@ export default withOnyx({
key: ONYXKEYS.PLAID_LINK_TOKEN,
initWithStoredValues: false,
},
+ isPlaidDisabled: {
+ key: ONYXKEYS.IS_PLAID_DISABLED,
+ },
})(AddPlaidBankAccount);
diff --git a/src/components/PlaidLink/index.js b/src/components/PlaidLink/index.js
index f1227bd15c68..319d13923996 100644
--- a/src/components/PlaidLink/index.js
+++ b/src/components/PlaidLink/index.js
@@ -23,6 +23,7 @@ function PlaidLink(props) {
},
onEvent: (event, metadata) => {
Log.info('[PlaidLink] Event: ', false, {event, metadata});
+ props.onEvent(event, metadata);
},
onLoad: () => setIsPlaidLoaded(true),
diff --git a/src/components/PlaidLink/index.native.js b/src/components/PlaidLink/index.native.js
index 402fbe22d64c..cd17453820cf 100644
--- a/src/components/PlaidLink/index.native.js
+++ b/src/components/PlaidLink/index.native.js
@@ -20,6 +20,9 @@ function PlaidLink(props) {
onSuccess: ({publicToken, metadata}) => {
props.onSuccess({publicToken, metadata});
},
+ onEvent: (event, metadata) => {
+ props.onEvent(event, metadata);
+ },
onExit: (exitError, metadata) => {
Log.info('[PlaidLink] Exit: ', false, {exitError, metadata});
props.onExit();
diff --git a/src/components/PlaidLink/plaidLinkPropTypes.js b/src/components/PlaidLink/plaidLinkPropTypes.js
index 6bcd103fc336..6d647d26f17e 100644
--- a/src/components/PlaidLink/plaidLinkPropTypes.js
+++ b/src/components/PlaidLink/plaidLinkPropTypes.js
@@ -13,6 +13,9 @@ const plaidLinkPropTypes = {
// Callback to execute when the user leaves the Plaid widget flow without entering any information
onExit: PropTypes.func,
+ // Callback to execute whenever a Plaid event occurs
+ onEvent: PropTypes.func,
+
// The redirect URI with an OAuth state ID. Needed to re-initialize the PlaidLink after directing the
// user to their respective bank platform
receivedRedirectURI: PropTypes.string,
diff --git a/src/languages/es.js b/src/languages/es.js
index fd6fcd9b767f..31c170a8dc0e 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -981,7 +981,7 @@ export default {
routingAndAccountNumberCannotBeSame: 'El número de ruta y el número de cuenta no pueden ser iguales',
companyType: 'Por favor, selecciona un tipo de compañía válido',
tooManyAttempts:
- 'Debido a la gran cantidad de intentos de inicio de sesión, esta opción se ha desactivado temporalmente durante 24 horas. Vuelve a intentarlo más tarde o introduce los detalles manualmente.',
+ 'Debido a la gran cantidad de intentos de inicio de sesión, esta opción ha sido desactivada temporalmente durante 24 horas. Por favor, inténtalo de nuevo más tarde.',
address: 'Por favor, introduce una dirección válida',
dob: 'Por favor, selecciona una fecha de nacimiento válida',
age: 'Debe ser mayor de 18 años',
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index 46d21d50cc3e..6028e0468696 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -464,6 +464,12 @@ function beginDeepLinkRedirectAfterTransition(shouldAuthenticateWithCurrentAccou
waitForSignOnTransitionToFinish().then(() => beginDeepLinkRedirect(shouldAuthenticateWithCurrentAccount));
}
+function handleRestrictedEvent(eventName) {
+ API.write('HandleRestrictedEvent', {
+ eventName,
+ });
+}
+
export {
setLocale,
setLocaleAndNavigate,
@@ -474,6 +480,7 @@ export {
openApp,
reconnectApp,
confirmReadyToOpenApp,
+ handleRestrictedEvent,
beginDeepLinkRedirect,
beginDeepLinkRedirectAfterTransition,
createWorkspaceAndNavigateToIt,
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index dad3afea82c5..b1cb09a8a5e2 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -401,6 +401,15 @@ function openWorkspaceView() {
API.read('OpenWorkspaceView');
}
+function handlePlaidError(bankAccountID, error, error_description, plaidRequestID) {
+ API.write('BankAccount_HandlePlaidError', {
+ bankAccountID,
+ error,
+ error_description,
+ plaidRequestID,
+ });
+}
+
/**
* Set the reimbursement account loading so that it happens right away, instead of when the API command is processed.
*
@@ -419,6 +428,7 @@ export {
connectBankAccountManually,
connectBankAccountWithPlaid,
deletePaymentBankAccount,
+ handlePlaidError,
openPersonalBankAccountSetupView,
openReimbursementAccountPage,
updateBeneficialOwnersForBankAccount,
diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js
index a9171157eac0..8e718e193efe 100644
--- a/src/pages/ReimbursementAccount/BankAccountStep.js
+++ b/src/pages/ReimbursementAccount/BankAccountStep.js
@@ -117,8 +117,13 @@ function BankAccountStep(props) {