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

Create Validation Step #3587

Merged
merged 35 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cbbb536
Add header and make sure things are loading
ctkochan22 Jun 15, 2021
68d9829
get props and state
ctkochan22 Jun 15, 2021
3bbb98b
get methods talking
ctkochan22 Jun 15, 2021
ebcc2e4
Add amound 2 and 3 and concatoate them
ctkochan22 Jun 15, 2021
af09ae8
Add a call to WebAPI
ctkochan22 Jun 15, 2021
ea09cfa
remove debugging logs
ctkochan22 Jun 15, 2021
94ef391
export validate method
ctkochan22 Jun 15, 2021
0bb78c3
Validate amounts
ctkochan22 Jun 16, 2021
37433a4
Add clarification
ctkochan22 Jun 16, 2021
adb440f
Merge branch 'main' into ckt_validationStep
ctkochan22 Jun 16, 2021
c0fd259
grab failed reimbursement
ctkochan22 Jun 17, 2021
4af47ae
Pull bankaccountID first
ctkochan22 Jun 17, 2021
794a1ae
Nest all validation in PENDING
ctkochan22 Jun 17, 2021
fb6458a
Add images
ctkochan22 Jun 17, 2021
1fab732
fix image url
ctkochan22 Jun 17, 2021
a4b0c47
merge master
ctkochan22 Jun 17, 2021
1ac0380
Start getting all the copy lined up
ctkochan22 Jun 17, 2021
b1ff46b
Fix error
ctkochan22 Jun 18, 2021
6b4a465
dismiss modal upon success
ctkochan22 Jun 18, 2021
5591738
merge main
ctkochan22 Jun 19, 2021
ea1d45b
Fix merge errors
ctkochan22 Jun 19, 2021
cebc4a3
remove debuggint logs
ctkochan22 Jun 19, 2021
6054889
Pass maxAttemptsReached
ctkochan22 Jun 19, 2021
ef83c5d
Fix more feedback and style
ctkochan22 Jun 21, 2021
71ad002
Merge branch 'ckt_validationStep' of github.com:Expensify/Expensify.c…
ctkochan22 Jun 21, 2021
723f02c
adjust spacing and add copy to constant
ctkochan22 Jun 21, 2021
7335f35
Add spanish
ctkochan22 Jun 21, 2021
7cd6489
Fix extra padding
ctkochan22 Jun 21, 2021
43cd571
Adjust sizing of verifying gif
ctkochan22 Jun 21, 2021
8c2cfb4
fix lint and alphabetize export
ctkochan22 Jun 21, 2021
4bf2b81
Update copy and add red text
ctkochan22 Jun 22, 2021
da55c9b
travis style
ctkochan22 Jun 22, 2021
adaf7fb
fix final review
ctkochan22 Jun 22, 2021
1bed83b
Merge and fix conflicts
ctkochan22 Jun 22, 2021
e0cecca
fix conflicts
marcaaron Jun 22, 2021
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
2 changes: 2 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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',
Expand Down Expand Up @@ -170,6 +171,7 @@ const CONST = {
TIMEZONE: 'timeZone',
FREE_PLAN_BANK_ACCOUNT_ID: 'expensify_freePlanBankAccountID',
ACH_DATA_THROTTLED: 'expensify_ACHData_throttled',
FAILED_BANK_ACCOUNT_VALIDATIONS_PREFACE: 'private_failedBankValidations_',
ctkochan22 marked this conversation as resolved.
Show resolved Hide resolved
},
DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
DEFAULT_ACCOUNT_DATA: {error: '', success: '', loading: false},
Expand Down
8 changes: 8 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,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"',
desriptionCTA: 'Please enter each transaction amount in the fields below. Example: 1.51',
Copy link
Contributor

Choose a reason for hiding this comment

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

missing c in descriptionCTA

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',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,12 @@ export default {
confirmCompanyIsNot: 'Confirmo que esta empresa no está en el',
listOfRestrictedBusinesses: 'lista de negocios restringidos',
},
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"',
desriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51',
ctkochan22 marked this conversation as resolved.
Show resolved Hide resolved
verifyingDescription: 'Estamos revisando su información y lo llevaremos a los siguientes pasos en solo unos segundos.',
},
};
7 changes: 7 additions & 0 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -945,6 +951,7 @@ export {
BankAccount_Create,
BankAccount_Get,
BankAccount_SetupWithdrawal,
BankAccount_Validate,
ChangePassword,
CreateChatReport,
CreateLogin,
Expand Down
255 changes: 150 additions & 105 deletions src/libs/actions/BankAccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
* Gets the Plaid Link token used to initialize the Plaid SDK
Expand Down Expand Up @@ -316,112 +317,136 @@ 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'}),
])
.then(([
freePlanBankAccountIDResponse,
kycVerificationsMigrationResponse,
achDataThrottledResponse,
bankAccountListResponse,
]) => {
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;

// 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_PREFACE + 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'}),
])
.then(([
failedValidationAttemptsResponse,
kycVerificationsMigrationResponse,
achDataThrottledResponse,
bankAccountListResponse,
]) => {
// 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;

// 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});
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: ''});
goToWithdrawalAccountSetupStep(currentStep, achData);
})
.finally(() => {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
});
});
}

Expand Down Expand Up @@ -482,6 +507,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();
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, because I think this works for now. But just leaving a mental note that we might want to show some kind of "success" screen. Since the plan is to enable Expensify cards automatically and there won't be anything to block the user from returning to this flow necessarily and I was thinking it would show a permanent view for the EnableStep just saying something like "there's no more actions to take" or whatever.

return;
}

Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, error: response.message});
});
}

/**
* Create or update the bank account in db with the updated data.
*
Expand Down Expand Up @@ -619,14 +663,15 @@ function setupWithdrawalAccount(data) {
}

export {
fetchPlaidLinkToken,
activateWallet,
addPersonalBankAccount,
getPlaidBankAccounts,
clearPlaidBankAccountsAndToken,
fetchFreePlanVerifiedBankAccount,
fetchOnfidoToken,
activateWallet,
fetchPlaidLinkToken,
fetchUserWallet,
fetchFreePlanVerifiedBankAccount,
setupWithdrawalAccount,
getPlaidBankAccounts,
goToWithdrawalAccountSetupStep,
setupWithdrawalAccount,
validateBankAccount,
};
12 changes: 11 additions & 1 deletion src/pages/ReimbursementAccount/ReimbursementAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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 */
Expand Down Expand Up @@ -135,6 +138,9 @@ class ReimbursementAccountPage extends React.Component {
}
}

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.
Expand All @@ -156,7 +162,11 @@ class ReimbursementAccountPage extends React.Component {
<BeneficialOwnersStep companyName={achData.companyName} />
)}
{currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && (
<ValidationStep />
<ValidationStep
achData={this.props.reimbursementAccount.achData}
maxAttemptsReached={maxAttemptsReached}
error={error}
/>
)}
</KeyboardAvoidingView>
</ScreenWrapper>
Expand Down
Loading