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 9cb91261cdd5..e44db0be1824 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -978,7 +978,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) {