Skip to content

Commit

Permalink
feat(KYC): high/low flows
Browse files Browse the repository at this point in the history
  • Loading branch information
tony-blockchain committed Nov 30, 2018
1 parent d08fa57 commit 42b3e6f
Show file tree
Hide file tree
Showing 20 changed files with 4,584 additions and 52 deletions.
3,985 changes: 3,985 additions & 0 deletions packages/blockchain-wallet-v4-frontend/src/assets/locales/defaultMessages.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,8 @@
"identityverification.personal.year": "Year",
"identityverification.personal.zipcode": "Zip Code",
"identityverification.verify.driverslicense": "Driver’s License",
"identityverification.verify.header": "Verify Your Identity",
"identityverification.verify.message": "Last step! We need to confirm your identity with a government issued ID. Before proceeding, make sure you have one of the following forms of ID handy.",
"identityverification.verify.header": "Last Step. Verify Your ID",
"identityverification.verify.message": "We need to confirm your identity with a government issued ID. Before proceeding, make sure you have one of the following forms of ID handy.",
"identityverification.verify.natitionalidcard": "National Identity Card",
"identityverification.verify.passport": "Government Issued Passport",
"layouts.public.footer.about": "About",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components'
import media from 'services/ResponsiveService'
import { Button, Text } from 'blockchain-info-components'
import { Button, Image, Text } from 'blockchain-info-components'
import { FaqMessage, FormGroup } from 'components/Form'

export const Form = styled.form`
Expand Down Expand Up @@ -187,3 +187,8 @@ export const BackButton = styled(Button)`
background-color: ${props => props.theme['gray-2']};
border-color: ${props => props.theme['gray-2']};
`

export const IdentityVerificationImage = styled(Image)`
margin-top: 40px;
margin-bottom: 40px;
`
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ export const RESEND_SMS_CODE = '@EVENT.KYC.RESEND_SMS_CODE'

export const CREATE_REGISTER_USER_CAMPAIGN =
'@EVENT.KYC.CREATE_REGISTER_USER_CAMPAIGN'

export const CHECK_KYC_FLOW = '@EVENT.KYC.CHECK_KYC_FLOW'
export const SET_KYCFLOW = '@DATA.KYC.SET_KYCFLOW'

export const RESEND_DEEP_LINK = '@EVENT.RESEND_DEEP_LINK'
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,14 @@ export const createRegisterUserCampaign = (
type: AT.CREATE_REGISTER_USER_CAMPAIGN,
payload: { campaignName, needsIdVerification }
})

export const checkKycFlow = () => ({
type: AT.CHECK_KYC_FLOW
})
export const setKycFlow = flowType => ({
type: AT.SET_KYCFLOW,
payload: { flowType }
})
export const resendDeeplink = () => ({
type: AT.RESEND_DEEP_LINK
})
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ export const isStateSupported = compose(
contains('KYC'),
propOr([], 'scopes')
)

export const FLOW_TYPES = {
HIGH: 'HIGH',
LOW: 'LOW'
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const INITIAL_STATE = {
supportedCountries: Remote.NotAsked,
supportedDocuments: Remote.NotAsked,
states: Remote.NotAsked,
possibleAddresses: []
possibleAddresses: [],
flowType: Remote.NotAsked
}

export default (state = INITIAL_STATE, action) => {
Expand Down Expand Up @@ -37,6 +38,9 @@ export default (state = INITIAL_STATE, action) => {
case AT.SET_POSSIBLE_ADDRESSES: {
return assoc('possibleAddresses', payload.addresses, state)
}
case AT.SET_KYCFLOW: {
return assoc('flowType', payload.flowType, state)
}
default:
return state
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,35 @@ import * as AT from './actionTypes'
import sagas from './sagas'

export default ({ api, coreSagas }) => {
const {
verifyIdentity,
initializeStep,
updateSmsStep,
updateSmsNumber,
verifySmsNumber,
resendSmsCode,
savePersonalData,
selectAddress,
fetchSupportedCountries,
fetchSupportedDocuments,
fetchStates,
fetchPossibleAddresses,
createRegisterUserCampaign
} = sagas({ api, coreSagas })
const exchange = sagas({ api, coreSagas })

return function*() {
yield takeLatest(AT.VERIFY_IDENTITY, verifyIdentity)
yield takeLatest(AT.INITIALIZE_STEP, initializeStep)
yield takeLatest(AT.UPDATE_SMS_STEP, updateSmsStep)
yield takeLatest(AT.UPDATE_SMS_NUMBER, updateSmsNumber)
yield takeLatest(AT.VERIFY_SMS_NUMBER, verifySmsNumber)
yield takeLatest(AT.RESEND_SMS_CODE, resendSmsCode)
yield takeLatest(AT.SAVE_PERSONAL_DATA, savePersonalData)
yield takeLatest(AT.FETCH_SUPPORTED_COUNTRIES, fetchSupportedCountries)
yield takeLatest(AT.FETCH_SUPPORTED_DOCUMENTS, fetchSupportedDocuments)
yield takeLatest(AT.FETCH_STATES, fetchStates)
yield takeLatest(AT.FETCH_POSSIBLE_ADDRESSES, fetchPossibleAddresses)
yield takeLatest(AT.SELECT_ADDRESS, selectAddress)
yield takeLatest(AT.VERIFY_IDENTITY, exchange.verifyIdentity)
yield takeLatest(AT.INITIALIZE_STEP, exchange.initializeStep)
yield takeLatest(AT.UPDATE_SMS_STEP, exchange.updateSmsStep)
yield takeLatest(AT.UPDATE_SMS_NUMBER, exchange.updateSmsNumber)
yield takeLatest(AT.VERIFY_SMS_NUMBER, exchange.verifySmsNumber)
yield takeLatest(AT.RESEND_SMS_CODE, exchange.resendSmsCode)
yield takeLatest(AT.SAVE_PERSONAL_DATA, exchange.savePersonalData)
yield takeLatest(
AT.FETCH_SUPPORTED_COUNTRIES,
exchange.fetchSupportedCountries
)
yield takeLatest(
AT.FETCH_SUPPORTED_DOCUMENTS,
exchange.fetchSupportedDocuments
)
yield takeLatest(AT.FETCH_STATES, exchange.fetchStates)
yield takeLatest(
AT.FETCH_POSSIBLE_ADDRESSES,
exchange.fetchPossibleAddresses
)
yield takeLatest(AT.SELECT_ADDRESS, exchange.selectAddress)
yield takeLatest(
AT.CREATE_REGISTER_USER_CAMPAIGN,
createRegisterUserCampaign
exchange.createRegisterUserCampaign
)
yield takeLatest(AT.CHECK_KYC_FLOW, exchange.checkKycFlow)
yield takeLatest(AT.RESEND_DEEP_LINK, exchange.resendDeeplink)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join, put, select, call, spawn } from 'redux-saga/effects'
import { isEmpty, prop } from 'ramda'
import { isEmpty, prop, toUpper } from 'ramda'

import { callLatest } from 'utils/effects'
import { actions, selectors, model } from 'data'
Expand All @@ -17,7 +17,8 @@ import {
PHONE_EXISTS_ERROR,
UPDATE_FAILURE,
KYC_MODAL,
USER_EXISTS_MODAL
USER_EXISTS_MODAL,
FLOW_TYPES
} from './model'

export const logLocation = 'components/identityVerification/sagas'
Expand All @@ -29,6 +30,7 @@ export const invalidNumberError = 'Failed to update mobile number'
export const mobileVerifiedError = 'Failed to verify mobile number'
export const failedResendError = 'Failed to resend the code'
export const userExistsError = 'User already exists'
export const wrongFlowTypeError = 'Wrong flow type'

export default ({ api, coreSagas }) => {
const { USER_ACTIVATION_STATES } = model.profile
Expand Down Expand Up @@ -370,6 +372,27 @@ export default ({ api, coreSagas }) => {
}
}

const checkKycFlow = function*() {
try {
yield put(A.setKycFlow(Remote.Loading))
const { flowType } = yield call(api.fetchKycConfig)
const type = FLOW_TYPES[toUpper(flowType)]
if (!type) throw wrongFlowTypeError

yield put(A.setKycFlow(Remote.of(type)))
} catch (e) {
yield put(A.setKycFlow(Remote.Failure(e)))
}
}

const resendDeeplink = function*() {
try {
yield call(api.resendDeeplink)
} catch (e) {
yield put(actions.logs.logErrorMessage(logLocation, 'resendDeeplink', e))
}
}

return {
verifyIdentity,
initializeStep,
Expand All @@ -383,6 +406,8 @@ export default ({ api, coreSagas }) => {
selectAddress,
updateSmsStep,
updateSmsNumber,
verifySmsNumber
verifySmsNumber,
checkKycFlow,
resendDeeplink
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { expectSaga } from 'redux-saga-test-plan'

import { Remote } from 'blockchain-wallet-v4/src'
import * as A from './actions'
import { FLOW_TYPES } from './model'
import sagas, { wrongFlowTypeError } from './sagas'

const api = {
fetchKycConfig: jest.fn(),
resendDeeplink: jest.fn()
}

const coreSagas = {}

const { checkKycFlow } = sagas({ api, coreSagas })

describe('checkKycFlow saga', () => {
it('should set flow type', () => {
const flowType = FLOW_TYPES.LOW
api.fetchKycConfig.mockResolvedValue({ flowType })
return expectSaga(checkKycFlow)
.put(A.setKycFlow(Remote.Loading))
.call(api.fetchKycConfig)
.put(A.setKycFlow(Remote.of(flowType)))
.run()
})

it('should set wrong type error if type is not in FLOW_TYPES', () => {
const flowType = FLOW_TYPES.LOW + '1'
api.fetchKycConfig.mockResolvedValue({ flowType })
return expectSaga(checkKycFlow)
.put(A.setKycFlow(Remote.Loading))
.call(api.fetchKycConfig)
.put(A.setKycFlow(Remote.Failure(wrongFlowTypeError)))
.run()
})

it('should set error if flow type endpoint rejects', () => {
const error = {}
api.fetchKycConfig.mockRejectedValue(error)
return expectSaga(checkKycFlow)
.put(A.setKycFlow(Remote.Loading))
.call(api.fetchKycConfig)
.put(A.setKycFlow(Remote.Failure(error)))
.run()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ export const isAddressRefetchVisible = path([
'identityVerification',
'addressRefetchVisible'
])

export const getKycFLowType = path([
'components',
'identityVerification',
'flowType'
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`HighFlow should render correctly 1`] = `
<IdentityVerification__IdentityVerificationForm>
<FooterShadowWrapper
fields={
<templatehighflow__VerifyWrapper>
<IdentityVerification__ColLeft>
<IdentityVerification__InputWrapper>
<IdentityVerification__IdentityVerificationHeader>
<FormattedMessage
defaultMessage="Last Step. Continue your verification on mobile"
id="identityverification.highflow.header"
values={Object {}}
/>
</IdentityVerification__IdentityVerificationHeader>
<IdentityVerification__IdentityVerificationImage
name="identity-verification"
/>
<IdentityVerification__IdentityVerificationSubHeader>
<Text
altFont={false}
capitalize={false}
color="gray-5"
cursor="inherit"
italic={false}
opacity={1}
size="16px"
uppercase={false}
weight={400}
>
<FormattedMessage
defaultMessage="We need to confirm your identity by taking a selfie video"
id="identityverification.highflow.message"
values={Object {}}
/>
</Text>
<br />
<Text
altFont={false}
capitalize={false}
color="gray-5"
cursor="inherit"
italic={false}
opacity={1}
size="16px"
uppercase={false}
weight={400}
>
<FormattedMessage
defaultMessage="- We just sent you an SMS to {mobile} with a link to complete KYC on your mobile device"
id="identityverification.highflow.sentlink"
values={
Object {
"mobile": "1234567890",
}
}
/>
</Text>
<Text
altFont={false}
capitalize={false}
color="gray-5"
cursor="inherit"
italic={false}
opacity={1}
size="16px"
uppercase={false}
weight={400}
>
<FormattedMessage
defaultMessage="- Get your ID or Passport ready"
id="identityverification.highflow.getidready"
values={Object {}}
/>
</Text>
<br />
<Link
bold={false}
capitalize={false}
color="brand-secondary"
onClick={[MockFunction]}
size="16px"
uppercase={false}
weight={400}
>
<FormattedMessage
defaultMessage="Resend link"
id="identityverification.highflow.resend"
values={Object {}}
/>
</Link>
</IdentityVerification__IdentityVerificationSubHeader>
</IdentityVerification__InputWrapper>
</IdentityVerification__ColLeft>
</templatehighflow__VerifyWrapper>
}
footer={
<templatehighflow__Footer>
<IdentityVerification__BackButton
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Back"
id="identityverification.personal.back"
values={Object {}}
/>
</IdentityVerification__BackButton>
</templatehighflow__Footer>
}
/>
</IdentityVerification__IdentityVerificationForm>
`;

0 comments on commit 42b3e6f

Please sign in to comment.