diff --git a/src/CONST.js b/src/CONST.js
index 3161aed8fa85..28485036da96 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -63,6 +63,7 @@ const CONST = {
IBAN: /^[A-Za-z0-9]{2,30}$/,
SWIFT_BIC: /^[A-Za-z0-9]{8,11}$/,
},
+ VERIFICATION_MAX_ATTEMPTS: 7,
},
INCORPORATION_TYPES: {
LLC: 'LLC',
@@ -191,6 +192,7 @@ const CONST = {
TIMEZONE: 'timeZone',
FREE_PLAN_BANK_ACCOUNT_ID: 'expensify_freePlanBankAccountID',
ACH_DATA_THROTTLED: 'expensify_ACHData_throttled',
+ FAILED_BANK_ACCOUNT_VALIDATIONS_PREFIX: 'private_failedBankValidations_',
BANK_ACCOUNT_GET_THROTTLED: 'private_throttledHistory_BankAccount_Get',
},
DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
diff --git a/src/languages/en.js b/src/languages/en.js
index 073a3a69b177..fe3879154b05 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -379,6 +379,14 @@ export default {
ssnLast4: 'Last 4 Digits of SSN',
isAuthorized: 'I am authorized to use my company bank account for business spend',
},
+ validationStep: {
+ headerTitle: 'Validate',
+ buttonText: 'Finish Setup',
+ maxAttemptError: 'Validation for this bank account has been disabled due to too many incorrect attempts. Please contact us.',
+ description: 'A day or two after you add your account to Expensify we send three (3) transactions to your account. They have a merchant line like "Expensify, Inc. Validation"',
+ descriptionCTA: 'Please enter each transaction amount in the fields below. Example: 1.51',
+ verifyingDescription: 'We\'re taking a look at your information and will have you onto next steps in just a few seconds.',
+ },
beneficialOwnersStep: {
beneficialOwners: 'Beneficial Owners',
additionalInformation: 'Additional Information',
diff --git a/src/languages/es.js b/src/languages/es.js
index 2c1e0cedcf1e..0604364f6577 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -368,6 +368,14 @@ export default {
incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)',
companyPhonePlaceholder: '10 dígitos, sin guiones',
},
+ validationStep: {
+ headerTitle: 'Validar',
+ buttonText: 'Finalizar Configuración',
+ maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.',
+ description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"',
+ descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51',
+ verifyingDescription: 'Estamos revisando su información y lo llevaremos a los siguientes pasos en solo unos segundos.',
+ },
requestCallPage: {
requestACall: 'Llámame por teléfono',
description: '¿Necesitas ayuda configurando tu cuenta? Nuestro equipo de guías puede ayudarte.',
diff --git a/src/libs/API.js b/src/libs/API.js
index c97e708f4e41..f4b7feace7a5 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -853,6 +853,12 @@ function BankAccount_Create(parameters) {
return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
}
+function BankAccount_Validate(parameters) {
+ const commandName = 'ValidateBankAccount';
+ requireParameters(['bankAccountID', 'validateCode'], parameters, commandName);
+ return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST);
+}
+
/**
* @param {*} parameters
* @returns {Promise}
@@ -960,6 +966,7 @@ export {
BankAccount_Create,
BankAccount_Get,
BankAccount_SetupWithdrawal,
+ BankAccount_Validate,
ChangePassword,
CreateChatReport,
CreateLogin,
diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js
index a7529b1059cf..ea499937d5eb 100644
--- a/src/libs/actions/BankAccounts.js
+++ b/src/libs/actions/BankAccounts.js
@@ -9,6 +9,7 @@ import * as API from '../API';
import BankAccount from '../models/BankAccount';
import promiseAllSettled from '../promiseAllSettled';
import Growl from '../Growl';
+import Navigation from '../Navigation/Navigation';
import {translateLocal} from '../translate';
/**
@@ -329,121 +330,147 @@ function fetchFreePlanVerifiedBankAccount() {
// We are using set here since we will rely on data from the server (not local data) to populate the VBA flow
// and determine which step to navigate to.
Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
- promiseAllSettled([
- API.Get({
- returnValueList: 'nameValuePairs',
- name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
- }),
- API.Get({
- returnValueList: 'nameValuePairs',
- name: 'expensify_migration_2020_04_28_RunKycVerifications',
- }),
- API.Get({
- returnValueList: 'nameValuePairs',
- name: CONST.NVP.ACH_DATA_THROTTLED,
- }),
- API.Get({returnValueList: 'bankAccountList'}),
- API.Get({
- returnValueList: 'nameValuePairs',
- name: CONST.NVP.BANK_ACCOUNT_GET_THROTTLED,
- }),
- ])
- .then(([
- freePlanBankAccountIDResponse,
- kycVerificationsMigrationResponse,
- achDataThrottledResponse,
- bankAccountListResponse,
- throttledBankAccountGetResponse,
- ]) => {
- const bankAccountID = lodashGet(freePlanBankAccountIDResponse, [
- 'value', 'nameValuePairs', CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
- ], '');
- const kycVerificationsMigration = lodashGet(kycVerificationsMigrationResponse, [
- 'value', 'nameValuePairs', 'expensify_migration_2020_04_28_RunKycVerifications',
- ], '');
- const throttledDate = lodashGet(achDataThrottledResponse, [
- 'value', 'nameValuePairs', CONST.NVP.ACH_DATA_THROTTLED,
+ let bankAccountID;
+
+ API.Get({
+ returnValueList: 'nameValuePairs',
+ name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
+ })
+ .then((response) => {
+ bankAccountID = lodashGet(response, ['nameValuePairs', CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID,
], '');
- const bankAccountJSON = _.find(
- lodashGet(bankAccountListResponse, ['value', 'bankAccountList'], []), account => (
- account.bankAccountID === bankAccountID
- ),
- );
- const bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
- const throttledHistoryCount = lodashGet(throttledBankAccountGetResponse, [
- 'value', 'nameValuePairs', CONST.NVP.BANK_ACCOUNT_GET_THROTTLED,
- ], 0);
- const isPlaidDisabled = throttledHistoryCount > CONST.BANK_ACCOUNT.PLAID.ALLOWED_THROTTLED_COUNT;
-
- // Next we'll build the achData and save it to Onyx
- // If the user is already setting up a bank account we will continue the flow for them
- let currentStep = reimbursementAccountInSetup.currentStep;
- const achData = bankAccount ? bankAccount.toACHData() : {};
- achData.useOnfido = true;
- achData.policyID = '';
- achData.isInSetup = !bankAccount || bankAccount.isInSetup();
- achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
- achData.domainLimit = 0;
-
- // If the bank account has already been created in the db and is not yet open let's show the manual form
- // with the previously added values
- achData.subStep = bankAccount && bankAccount.isInSetup() && CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
-
- // If we're not in setup, it means we already have a withdrawal account and we're upgrading it to a business
- // bank account. So let the user review all steps with all info prefilled and editable, unless a specific
- // step was passed.
- if (!achData.isInSetup) {
- // @TODO Not sure if we need to do this since for NewDot none of the accounts are pre-existing ones
- currentStep = '';
- }
+ const failedValidationAttemptsName = CONST.NVP.FAILED_BANK_ACCOUNT_VALIDATIONS_PREFIX + bankAccountID;
+
+ // Now that we have the bank account. Lets grab the rest of the bank info we need
+ promiseAllSettled([
+ API.Get({
+ returnValueList: 'nameValuePairs',
+ name: failedValidationAttemptsName,
+ }),
+ API.Get({
+ returnValueList: 'nameValuePairs',
+ name: 'expensify_migration_2020_04_28_RunKycVerifications',
+ }),
+ API.Get({
+ returnValueList: 'nameValuePairs',
+ name: CONST.NVP.ACH_DATA_THROTTLED,
+ }),
+ API.Get({returnValueList: 'bankAccountList'}),
+ API.Get({
+ returnValueList: 'nameValuePairs',
+ name: CONST.NVP.BANK_ACCOUNT_GET_THROTTLED,
+ }),
+ ])
+ .then(([
+ failedValidationAttemptsResponse,
+ kycVerificationsMigrationResponse,
+ achDataThrottledResponse,
+ bankAccountListResponse,
+ throttledBankAccountGetResponse,
+ ]) => {
+ // Users have a limited amount of attempts to get the validations amounts correct.
+ // Once exceeded, we need to block them from attempting to validate.
+ const failedValidationAttempts = lodashGet(failedValidationAttemptsResponse, [
+ 'value', 'nameValuePairs', failedValidationAttemptsName,
+ ], 0);
+ const maxAttemptsReached = failedValidationAttempts > CONST.BANK_ACCOUNT.VERIFICATION_MAX_ATTEMPTS;
+
+ const kycVerificationsMigration = lodashGet(kycVerificationsMigrationResponse, [
+ 'value', 'nameValuePairs', 'expensify_migration_2020_04_28_RunKycVerifications',
+ ], '');
+ const throttledDate = lodashGet(achDataThrottledResponse, [
+ 'value', 'nameValuePairs', CONST.NVP.ACH_DATA_THROTTLED,
+ ], '');
+ const bankAccountJSON = _.find(
+ lodashGet(bankAccountListResponse, ['value', 'bankAccountList'], []), account => (
+ account.bankAccountID === bankAccountID
+ ),
+ );
+ const bankAccount = bankAccountJSON ? new BankAccount(bankAccountJSON) : null;
+ const throttledHistoryCount = lodashGet(throttledBankAccountGetResponse, [
+ 'value', 'nameValuePairs', CONST.NVP.BANK_ACCOUNT_GET_THROTTLED,
+ ], 0);
+ const isPlaidDisabled = throttledHistoryCount > CONST.BANK_ACCOUNT.PLAID.ALLOWED_THROTTLED_COUNT;
+
+ // Next we'll build the achData and save it to Onyx
+ // If the user is already setting up a bank account we will continue the flow for them
+ let currentStep = reimbursementAccountInSetup.currentStep;
+ const achData = bankAccount ? bankAccount.toACHData() : {};
+ achData.useOnfido = true;
+ achData.policyID = '';
+ achData.isInSetup = !bankAccount || bankAccount.isInSetup();
+ achData.bankAccountInReview = bankAccount && bankAccount.isVerifying();
+ achData.domainLimit = 0;
+
+ // If the bank account has already been created in the db and is not yet open
+ // let's show the manual form with the previously added values
+ achData.subStep = bankAccount && bankAccount.isInSetup()
+ && CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL;
+
+ // If we're not in setup, it means we already have a withdrawal account
+ // and we're upgrading it to a business bank account. So let the user
+ // review all steps with all info prefilled and editable, unless a specific step was passed.
+ if (!achData.isInSetup) {
+ // @TODO Not sure if we need to do this since for
+ // NewDot none of the accounts are pre-existing ones
+ currentStep = '';
+ }
- // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020.
- // @TODO not sure if we still need this or what this is about, but seems like maybe yes...
- if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && achData.useOnfido) {
- const onfidoResponse = lodashGet(achData, CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO);
- const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
- if (sdkToken && !achData.isOnfidoSetupComplete
- && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
- ) {
- currentStep = CONST.BANK_ACCOUNT.STEP.REQUESTOR;
- }
- }
+ // Temporary fix for Onfido flow. Can be removed by nkuoch after Sept 1 2020.
+ // @TODO not sure if we still need this or what this is about, but seems like maybe yes...
+ if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && achData.useOnfido) {
+ const onfidoResponse = lodashGet(
+ achData,
+ CONST.BANK_ACCOUNT.VERIFICATIONS.REQUESTOR_IDENTITY_ONFIDO,
+ );
+ const sdkToken = lodashGet(onfidoResponse, CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.SDK_TOKEN);
+ if (sdkToken && !achData.isOnfidoSetupComplete
+ && onfidoResponse.status !== CONST.BANK_ACCOUNT.ONFIDO_RESPONSE.PASS
+ ) {
+ currentStep = CONST.BANK_ACCOUNT.STEP.REQUESTOR;
+ }
+ }
- // Ensure we route the user to the correct step based on the status of their bank account
- if (bankAccount && !currentStep) {
- currentStep = bankAccount.isPending() || bankAccount.isVerifying()
- ? CONST.BANK_ACCOUNT.STEP.VALIDATION
- : CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
-
- // @TODO Again, not sure how much of this logic is needed right now as we shouldn't be handling any
- // open accounts in E.cash yet that need to pass any more checks or can be upgraded, but leaving in for
- // possible future compatibility.
- if (bankAccount.isOpen()) {
- if (bankAccount.needsToPassLatestChecks()) {
- const hasTriedToUpgrade = bankAccount.getDateSigned()
- > (kycVerificationsMigration || '2020-01-13');
- currentStep = hasTriedToUpgrade
- ? CONST.BANK_ACCOUNT.STEP.VALIDATION : CONST.BANK_ACCOUNT.STEP.COMPANY;
- achData.bankAccountInReview = hasTriedToUpgrade;
- } else {
- // In Expensify.cash we do not show a specific view for the EnableStep since we will enable the
- // Expensify card automatically. However, we will still handle that step and show the Validate
- // view.
- currentStep = CONST.BANK_ACCOUNT.STEP.ENABLE;
+ // Ensure we route the user to the correct step based on the status of their bank account
+ if (bankAccount && !currentStep) {
+ currentStep = bankAccount.isPending() || bankAccount.isVerifying()
+ ? CONST.BANK_ACCOUNT.STEP.VALIDATION
+ : CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
+
+ // @TODO Again, not sure how much of this logic is needed right now
+ // as we shouldn't be handling any open accounts in E.cash yet that need to pass any more
+ // checks or can be upgraded, but leaving in for possible future compatibility.
+ if (bankAccount.isOpen()) {
+ if (bankAccount.needsToPassLatestChecks()) {
+ const hasTriedToUpgrade = bankAccount.getDateSigned()
+ > (kycVerificationsMigration || '2020-01-13');
+ currentStep = hasTriedToUpgrade
+ ? CONST.BANK_ACCOUNT.STEP.VALIDATION : CONST.BANK_ACCOUNT.STEP.COMPANY;
+ achData.bankAccountInReview = hasTriedToUpgrade;
+ } else {
+ // In Expensify.cash we do not show a specific view for the EnableStep since we
+ // will enable the Expensify card automatically. However, we will still handle
+ // that step and show the Validate view.
+ currentStep = CONST.BANK_ACCOUNT.STEP.ENABLE;
+ }
+ }
}
- }
- }
- // If at this point we still don't have a current step, default to the BankAccountStep
- if (!currentStep) {
- currentStep = CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
- }
+ // If at this point we still don't have a current step, default to the BankAccountStep
+ if (!currentStep) {
+ currentStep = CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT;
+ }
- Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {throttledDate, isPlaidDisabled});
- goToWithdrawalAccountSetupStep(currentStep, achData);
- })
- .finally(() => {
- Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
+ // 'error' displays any string set as an error encountered during the add Verified BBA flow.
+ // If we are fetching a bank account, clear the error to reset.
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {
+ throttledDate, maxAttemptsReached, error: '', isPlaidDisabled,
+ });
+ goToWithdrawalAccountSetupStep(currentStep, achData);
+ })
+ .finally(() => {
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
+ });
});
}
@@ -504,6 +531,25 @@ function setFreePlanVerifiedBankAccountID(bankAccountID) {
API.SetNameValuePair({name: CONST.NVP.FREE_PLAN_BANK_ACCOUNT_ID, value: bankAccountID});
}
+/**
+ * @param {Number} bankAccountID
+ * @param {String} validateCode
+ */
+function validateBankAccount(bankAccountID, validateCode) {
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true});
+
+ API.BankAccount_Validate({bankAccountID, validateCode})
+ .then((response) => {
+ if (response.jsonCode === 200) {
+ Growl.show('Bank Account successfully validated!', CONST.GROWL.SUCCESS, 3000);
+ Navigation.dismissModal();
+ return;
+ }
+
+ Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, error: response.message});
+ });
+}
+
/**
* Create or update the bank account in db with the updated data.
*
@@ -657,14 +703,15 @@ function setupWithdrawalAccount(data) {
}
export {
- fetchPlaidLinkToken,
+ activateWallet,
addPersonalBankAccount,
- getPlaidBankAccounts,
clearPlaidBankAccountsAndToken,
+ fetchFreePlanVerifiedBankAccount,
fetchOnfidoToken,
- activateWallet,
+ fetchPlaidLinkToken,
fetchUserWallet,
- fetchFreePlanVerifiedBankAccount,
- setupWithdrawalAccount,
+ getPlaidBankAccounts,
goToWithdrawalAccountSetupStep,
+ setupWithdrawalAccount,
+ validateBankAccount,
};
diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
index d03b2f385177..fda32c64912d 100644
--- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
+++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js
@@ -44,6 +44,9 @@ const propTypes = {
/** Step of the setup flow that we are on. Determines which view is presented. */
currentStep: PropTypes.string,
}),
+
+ /** Disable validation button if max attempts exceeded */
+ maxAttemptsReached: PropTypes.bool,
}),
/** Current session for the user */
@@ -111,11 +114,11 @@ class ReimbursementAccountPage extends React.Component {
return ;
}
- let error;
+ let errorComponent;
const userHasPhonePrimaryEmail = Str.endsWith(this.props.session.email, CONST.SMS.DOMAIN);
if (userHasPhonePrimaryEmail) {
- error = (
+ errorComponent = (
{this.props.translate('bankAccount.hasPhoneLoginError')}
@@ -126,7 +129,7 @@ class ReimbursementAccountPage extends React.Component {
if (throttledDate) {
const throttledEnd = moment().add(24, 'hours');
if (moment() < throttledEnd) {
- error = (
+ errorComponent = (
{this.props.translate('bankAccount.hasBeenThrottledError', {
@@ -138,18 +141,21 @@ class ReimbursementAccountPage extends React.Component {
}
}
- if (error) {
+ if (errorComponent) {
return (
- {error}
+ {errorComponent}
);
}
+ const error = lodashGet(this.props, 'reimbursementAccount.error');
+ const maxAttemptsReached = lodashGet(this.props, 'reimbursementAccount.maxAttemptsReached');
+
// We grab the currentStep from the achData to determine which view to display. The SetupWithdrawalAccount flow
// allows us to continue the flow from various points depending on where the user left off. We can also
// specify a specific step to navigate to by using route params.
@@ -174,7 +180,11 @@ class ReimbursementAccountPage extends React.Component {
)}
{currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && (
-
+
)}
diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js
index 43628ea79677..bfb0a4822d30 100644
--- a/src/pages/ReimbursementAccount/ValidationStep.js
+++ b/src/pages/ReimbursementAccount/ValidationStep.js
@@ -1,5 +1,181 @@
import React from 'react';
-import {View} from 'react-native';
+import {Image, View} from 'react-native';
+import PropTypes from 'prop-types';
+import Str from 'expensify-common/lib/str';
+import _ from 'underscore';
+import styles from '../../styles/styles';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
-const ValidationStep = () => ;
-export default ValidationStep;
+import {validateBankAccount} from '../../libs/actions/BankAccounts';
+
+import Button from '../../components/Button';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import Navigation from '../../libs/Navigation/Navigation';
+import TextInputWithLabel from '../../components/TextInputWithLabel';
+import Text from '../../components/Text';
+import BankAccount from '../../libs/models/BankAccount';
+import CONST from '../../CONST';
+
+const propTypes = {
+ ...withLocalizePropTypes,
+
+ /** Additional data for the account in setup */
+ achData: PropTypes.shape({
+
+ /** Bank account ID of the VBA that we are validating is required */
+ bankAccountID: PropTypes.number.isRequired,
+
+ /** State of bank account */
+ state: PropTypes.string,
+ }).isRequired,
+
+ /** Error message to display to user */
+ error: PropTypes.string,
+
+ /** Disable validation button if max attempts exceeded */
+ maxAttemptsReached: PropTypes.bool,
+};
+
+const defaultProps = {
+ error: '',
+ maxAttemptsReached: false,
+};
+
+class ValidationStep extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submit = this.submit.bind(this);
+
+ this.verifyingUrl = `${CONST.CLOUDFRONT_URL}/images/icons/emptystates/emptystate_reviewing.gif`;
+
+ this.state = {
+ amount1: '',
+ amount2: '',
+ amount3: '',
+ error: '',
+ };
+ }
+
+ submit() {
+ const amount1 = this.filterInput(this.state.amount1);
+ const amount2 = this.filterInput(this.state.amount2);
+ const amount3 = this.filterInput(this.state.amount3);
+
+ // If amounts are all non-zeros, submit amounts to API
+ if (amount1 && amount2 && amount3) {
+ const validateCode = [amount1, amount2, amount3].join(',');
+
+ // Send valid amounts to BankAccountAPI::validateBankAccount in Web-Expensify
+ validateBankAccount(this.props.achData.bankAccountID, validateCode);
+ return;
+ }
+
+ // If any values are falsey, indicate to user that inputs are invalid
+ this.setState({error: 'Invalid amounts'});
+ }
+
+ /**
+ * Filter input for validation amount
+ * Anything that isn't a number is returned as an empty string
+ * Any dollar amount (e.g. 1.12) will be returned as 112
+ *
+ * @param {String} amount field input
+ *
+ * @returns {String}
+ */
+ filterInput(amount) {
+ let value = amount.trim();
+ if (value === '' || !Math.abs(Str.fromUSDToNumber(value)) || _.isNaN(Number(value))) {
+ return '';
+ }
+
+ // If the user enters the values in dollars, convert it to the respective cents amount
+ if (_.contains(value, '.')) {
+ value = Str.fromUSDToNumber(value);
+ }
+
+ return value;
+ }
+
+ render() {
+ let errorMessage = this.state.error ? this.state.error : this.props.error;
+ if (this.props.maxAttemptsReached) {
+ errorMessage = this.props.translate('validationStep.maxAttemptError');
+ }
+
+ const state = this.props.achData.state;
+ return (
+
+
+ {state === BankAccount.STATE.PENDING && (
+
+
+
+ {this.props.translate('validationStep.description')}
+
+
+ {this.props.translate('validationStep.descriptionCTA')}
+
+
+
+ this.setState({amount1})}
+ />
+ this.setState({amount2})}
+ />
+ this.setState({amount3})}
+ />
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+
+ )}
+ {state === BankAccount.STATE.VERIFYING && (
+
+
+
+ {this.props.translate('validationStep.verifyingDescription')}
+
+
+ )}
+
+ );
+ }
+}
+
+ValidationStep.propTypes = propTypes;
+ValidationStep.defaultProps = defaultProps;
+
+export default withLocalize(ValidationStep);
diff --git a/src/styles/styles.js b/src/styles/styles.js
index a97f35d25238..e064c024056c 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -122,6 +122,10 @@ const styles = {
color: themeColors.heading,
},
+ textDanger: {
+ color: colors.red,
+ },
+
button: {
backgroundColor: themeColors.buttonDefaultBG,
borderRadius: variables.componentBorderRadiusNormal,