Skip to content

Commit

Permalink
Handle Wallet Onfido flow
Browse files Browse the repository at this point in the history
  • Loading branch information
nkuoch committed Mar 17, 2022
1 parent 3696074 commit 7d67d8b
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 172 deletions.
3 changes: 2 additions & 1 deletion src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ const CONST = {
FULL_SSN_NOT_FOUND: 'Full SSN not found',
MISSING_FIELD: 'Missing required additional details fields',
WRONG_ANSWERS: 'Wrong answers',
ONFIDO_FIXABLE_ERROR: 'Onfido returned a fixable error',

// KBA stands for Knowledge Based Answers (requiring us to show Idology questions)
KBA_NEEDED: 'KBA needed',
Expand All @@ -442,8 +443,8 @@ const CONST = {
},
STEP: {
// In the order they appear in the Wallet flow
ONFIDO: 'OnfidoStep',
ADDITIONAL_DETAILS: 'AdditionalDetailsStep',
ONFIDO: 'OnfidoStep',
TERMS: 'TermsStep',
ACTIVATE: 'ActivateStep',
},
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,12 @@ export default {
genericError: 'There was an error while processing this step. Please try again.',
cameraPermissionsNotGranted: 'Camera permissions not granted',
cameraRequestMessage: 'You have not granted us camera access. We need access to complete verification.',
originalDocumentNeeded: 'Please upload an original image of your ID rather than a screenshot or scanned image.',
documentNeedsBetterQuality: 'Your ID appears to be damaged or has missing security features. Please upload an original image of your undamaged ID it\'s entirely visible.',
imageNeedsBetterQuality: 'There\'s an issue with the image quality of your ID. Please upload a new image where your entire ID can be seen clearly.',
selfieIssue: 'There\'s an issue with your selfie/video. Please upload a new selfie/video in real time.',
selfieNotMatching: 'Your selfie/video doesn\'t match your ID. Please upload a new selfie/video where your face can be clearly seen.',
selfieNotLive: 'Your selfie/video doesn\'t appear to be a live photo/video. Please upload a live selfie/video.',
},
additionalDetailsStep: {
headerTitle: 'Additional details',
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,12 @@ export default {
genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.',
cameraPermissionsNotGranted: 'No has habilitado los permisos para acceder a la cámara',
cameraRequestMessage: 'No has habilitado los permisos para acceder a la cámara. Necesitamos acceso para completar la verificaciôn.',
originalDocumentNeeded: 'Por favor, sube una imagen original de tu ID en lugar de una captura de pantalla o imagen escaneada.',
documentNeedsBetterQuality: 'Parece que tu ID esta dañado o le faltan características de seguridad. Por favor, sube una imagen donde tu ID intacto se vea completamente.',
imageNeedsBetterQuality: 'Hay un problema con la calidad de la imagen de tu ID. Por favor, sube una nueva imagen donde el ID se vea con claridad.',
selfieIssue: 'Hay un problema con tu selfie/video. Por favor, sube un nuevo selfie/video a tiempo real.',
selfieNotMatching: 'Tu selfie/video no concuerda con tu ID. Por favor, sube un nuevo selfie/video donde se vea tu cara con claridad.',
selfieNotLive: 'Tu selfie/video no parece ser un selfie/video en vivo. Por favor, sube un selfie/video a tiempo real.',
},
additionalDetailsStep: {
headerTitle: 'Detalles adicionales',
Expand Down
8 changes: 7 additions & 1 deletion src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -898,14 +898,20 @@ function CreateIOUSplit(parameters) {
}

/**
* @param {String} firstName
* @param {String} lastName
* @param {String} dob
* @returns {Promise}
*/
function Wallet_GetOnfidoSDKToken() {
function Wallet_GetOnfidoSDKToken(firstName, lastName, dob) {
return Network.post('Wallet_GetOnfidoSDKToken', {
// We need to pass this so we can request a token with the correct referrer
// This value comes from a cross-platform module which returns true for native
// platforms and false for non-native platforms.
isViaExpensifyCashNative,
firstName,
lastName,
dob,
}, CONST.NETWORK.METHOD.POST, true);
}

Expand Down
37 changes: 37 additions & 0 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,42 @@ function fetchPersonalDetails() {
});
}

/**
* Gets the first and last name from the user's personal details.
* If the login is the same as the displayName, then they don't exist,
* so we return empty strings instead.
* @param {String} login
* @param {String} displayName
* @param {String} firstName
* @param {String} lastName
*
* @returns {Object}
*/
function getFirstAndLastName({
login,
displayName,
firstName,
lastName,
}) {
if (firstName || lastName) {
return {firstName: firstName || '', lastName: lastName || ''};
}
if (Str.removeSMSDomain(login) === displayName) {
return {firstName: '', lastName: ''};
}

const firstSpaceIndex = displayName.indexOf(' ');
const lastSpaceIndex = displayName.lastIndexOf(' ');
if (firstSpaceIndex === -1) {
return {firstName: displayName, lastName: ''};
}

return {
firstName: displayName.substring(0, firstSpaceIndex).trim(),
lastName: displayName.substring(lastSpaceIndex).trim(),
};
}

/**
* Get personal details from report participants.
*
Expand Down Expand Up @@ -350,4 +386,5 @@ export {
fetchLocalCurrency,
getCurrencyList,
getMaxCharacterError,
getFirstAndLastName,
};
35 changes: 26 additions & 9 deletions src/libs/actions/Wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import * as Localize from '../Localize';
* - The sdkToken is used to initialize the Onfido SDK client
* - The applicantID is combined with the data returned from the Onfido SDK as we need both to create an
* identity check. Note: This happens in Web-Secure when we call Activate_Wallet during the OnfidoStep.
* @param {String} firstName
* @param {String} lastName
* @param {String} dob
*/
function fetchOnfidoToken() {
function fetchOnfidoToken(firstName, lastName, dob) {
// Use Onyx.set() since we are resetting the Onfido flow completely.
Onyx.set(ONYXKEYS.WALLET_ONFIDO, {loading: true});
API.Wallet_GetOnfidoSDKToken()
API.Wallet_GetOnfidoSDKToken(firstName, lastName, dob)
.then((response) => {
const apiResult = lodashGet(response, ['requestorIdentityOnfido', 'apiResult'], {});
Onyx.merge(ONYXKEYS.WALLET_ONFIDO, {
Expand Down Expand Up @@ -71,8 +74,8 @@ function setAdditionalDetailsShouldAskForFullSSN(shouldAskForFullSSN) {
/**
* @param {Boolean} shouldShowFailedKYC
*/
function setAdditionalDetailsShouldShowFailedKYC(shouldShowFailedKYC) {
Onyx.merge(ONYXKEYS.WALLET_ADDITIONAL_DETAILS, {shouldShowFailedKYC});
function setWalletShouldShowFailedKYC(shouldShowFailedKYC) {
Onyx.merge(ONYXKEYS.USER_WALLET, {shouldShowFailedKYC});
}

/**
Expand Down Expand Up @@ -175,6 +178,7 @@ function activateWallet(currentStep, parameters) {
throw new Error('Invalid currentStep passed to activateWallet()');
}

setWalletShouldShowFailedKYC(false);
if (currentStep === CONST.WALLET.STEP.ONFIDO) {
onfidoData = parameters.onfidoData;
Onyx.merge(ONYXKEYS.WALLET_ONFIDO, {error: '', loading: true});
Expand All @@ -183,7 +187,6 @@ function activateWallet(currentStep, parameters) {
setAdditionalDetailsLoading(true);
setAdditionalDetailsErrors(null);
setAdditionalDetailsErrorMessage('');
setAdditionalDetailsShouldShowFailedKYC(false);
personalDetails = JSON.stringify(parameters.personalDetails);
}
if (parameters.idologyAnswers) {
Expand Down Expand Up @@ -212,7 +215,12 @@ function activateWallet(currentStep, parameters) {

if (response.jsonCode !== 200) {
if (currentStep === CONST.WALLET.STEP.ONFIDO) {
Onyx.merge(ONYXKEYS.WALLET_ONFIDO, {error: response.message, loading: false});
Onyx.merge(ONYXKEYS.WALLET_ONFIDO, {loading: false});
if (response.title === CONST.WALLET.ERROR.ONFIDO_FIXABLE_ERROR) {
Onyx.merge(ONYXKEYS.WALLET_ONFIDO, {fixableErrors: lodashGet(response, 'data.fixableErrors', [])});
return;
}
setWalletShouldShowFailedKYC(true);
return;
}

Expand Down Expand Up @@ -257,7 +265,7 @@ function activateWallet(currentStep, parameters) {
'resultcode.pa.dob.does.not.match',
];
if (_.some(hardFailures, hardFailure => _.contains(idologyErrors, hardFailure))) {
setAdditionalDetailsShouldShowFailedKYC(true);
setWalletShouldShowFailedKYC(true);
return;
}

Expand All @@ -270,7 +278,7 @@ function activateWallet(currentStep, parameters) {

if (lodashGet(response, 'data.requestorIdentityID.apiResult.results.key') === 'result.no.match'
|| response.title === CONST.WALLET.ERROR.WRONG_ANSWERS) {
setAdditionalDetailsShouldShowFailedKYC(true);
setWalletShouldShowFailedKYC(true);
return;
}
if (Str.endsWith(response.type, 'AutoVerifyFailure')) {
Expand Down Expand Up @@ -318,7 +326,8 @@ function fetchUserWallet() {
return;
}

Onyx.merge(ONYXKEYS.USER_WALLET, response.userWallet);
// When refreshing the wallet, we should not show the failed KYC page anymore, as we should allow them to retry.
Onyx.merge(ONYXKEYS.USER_WALLET, {...response.userWallet, shouldShowFailedKYC: false});
});
}

Expand All @@ -329,6 +338,13 @@ function updateAdditionalDetailsDraft(keyValuePair) {
Onyx.merge(ONYXKEYS.WALLET_ADDITIONAL_DETAILS_DRAFT, keyValuePair);
}

/**
* @param {string} currentStep
*/
function updateCurrentStep(currentStep) {
Onyx.merge(ONYXKEYS.USER_WALLET, {currentStep});
}

export {
fetchOnfidoToken,
activateWallet,
Expand All @@ -338,4 +354,5 @@ export {
setAdditionalDetailsErrorMessage,
setAdditionalDetailsQuestions,
buildIdologyError,
updateCurrentStep,
};
46 changes: 12 additions & 34 deletions src/pages/EnablePayments/AdditionalDetailsStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import * as ValidationUtils from '../../libs/ValidationUtils';
import AddressSearch from '../../components/AddressSearch';
import DatePicker from '../../components/DatePicker';
import FormHelper from '../../libs/FormHelper';
import FailedKYC from './FailedKYC';
import walletAdditionalDetailsDraftPropTypes from './walletAdditionalDetailsDraftPropTypes';
import personalDetailsPropType from '../personalDetailsPropType';
import * as PersonalDetails from '../../libs/actions/PersonalDetails';

const propTypes = {
...withLocalizePropTypes,
Expand All @@ -52,25 +54,15 @@ const propTypes = {
/** ExpectID ID number related to those questions */
idNumber: PropTypes.string,

/** If we should show the FailedKYC view after the user submitted the form with a non fixable error */
shouldShowFailedKYC: PropTypes.bool,

/** If we should ask for the full SSN (when LexisNexis failed retrieving the first 5 from the last 4) */
shouldAskForFullSSN: PropTypes.bool,
}),

/** Stores the personal details typed by the user */
walletAdditionalDetailsDraft: PropTypes.shape({
legalFirstName: PropTypes.string,
legalLastName: PropTypes.string,
addressStreet: PropTypes.string,
addressCity: PropTypes.string,
addressState: PropTypes.string,
addressZip: PropTypes.string,
phoneNumber: PropTypes.string,
dob: PropTypes.string,
ssn: PropTypes.string,
}),
walletAdditionalDetailsDraft: walletAdditionalDetailsDraftPropTypes,

/** The personal details of the person who is logged in */
myPersonalDetails: personalDetailsPropType.isRequired,
};

const defaultProps = {
Expand All @@ -80,7 +72,6 @@ const defaultProps = {
additionalErrorMessage: '',
questions: [],
idNumber: '',
shouldShowFailedKYC: false,
shouldAskForFullSSN: false,
},
walletAdditionalDetailsDraft: {
Expand Down Expand Up @@ -212,20 +203,6 @@ class AdditionalDetailsStep extends React.Component {
}

render() {
if (this.props.walletAdditionalDetails.shouldShowFailedKYC) {
return (
<ScreenWrapper>
<KeyboardAvoidingView style={[styles.flex1]} behavior="height">
<HeaderWithCloseButton
title={this.props.translate('additionalDetailsStep.headerTitle')}
onCloseButtonPress={() => Navigation.dismissModal()}
/>
<FailedKYC />
</KeyboardAvoidingView>
</ScreenWrapper>
);
}

if (!_.isEmpty(this.props.walletAdditionalDetails.questions)) {
return (
<ScreenWrapper>
Expand All @@ -248,6 +225,7 @@ class AdditionalDetailsStep extends React.Component {
const isErrorVisible = _.size(this.getErrors()) > 0
|| lodashGet(this.props, 'walletAdditionalDetails.additionalErrorMessage', '').length > 0;
const shouldAskForFullSSN = this.props.walletAdditionalDetails.shouldAskForFullSSN;
const {firstName, lastName} = PersonalDetails.getFirstAndLastName(this.props.myPersonalDetails);

return (
<ScreenWrapper>
Expand All @@ -273,14 +251,14 @@ class AdditionalDetailsStep extends React.Component {
containerStyles={[styles.mt4]}
label={this.props.translate(this.fieldNameTranslationKeys.legalFirstName)}
onChangeText={val => this.clearErrorAndSetValue('legalFirstName', val)}
value={this.props.walletAdditionalDetailsDraft.legalFirstName || ''}
value={this.props.walletAdditionalDetailsDraft.legalFirstName || firstName}
errorText={this.getErrorText('legalFirstName')}
/>
<TextInput
containerStyles={[styles.mt4]}
label={this.props.translate(this.fieldNameTranslationKeys.legalLastName)}
onChangeText={val => this.clearErrorAndSetValue('legalLastName', val)}
value={this.props.walletAdditionalDetailsDraft.legalLastName || ''}
value={this.props.walletAdditionalDetailsDraft.legalLastName || lastName}
errorText={this.getErrorText('legalLastName')}
/>
<AddressSearch
Expand Down Expand Up @@ -385,8 +363,8 @@ export default compose(
key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS,
initWithStoredValues: false,
},
walletAdditionalDetailsDraft: {
key: ONYXKEYS.WALLET_ADDITIONAL_DETAILS_DRAFT,
myPersonalDetails: {
key: ONYXKEYS.MY_PERSONAL_DETAILS,
},
}),
)(AdditionalDetailsStep);
Loading

0 comments on commit 7d67d8b

Please sign in to comment.