Skip to content

Commit

Permalink
feat(simple buy): set up 3ds handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Philip London committed Apr 22, 2020
1 parent ad2afca commit a8f3d34
Show file tree
Hide file tree
Showing 21 changed files with 333 additions and 117 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
"@storybook/storybook-deployer": "2.8.1",
"@types/axios": "0.14.0",
"@types/hoist-non-react-statics": "3.3.1",
"@types/moment": "2.13.0",
"@types/node": "13.9.0",
"@types/ramda": "0.26.39",
"@types/react": "16.9.34",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const CARD_TYPES = [
cvcLength: DEFAULT_CVC_LENGTH,
logo:
'data:image/svg+xml;base64,PHN2ZyBmb2N1c2FibGU9ImZhbHNlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAyMSI+ICA8ZyBpZD0iUGFnZS0xIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgICA8ZyBpZD0ibWFzdGVyY2FyZCI+ICAgICAgPGcgaWQ9ImNhcmQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMikiPiAgICAgICAgPHBhdGggaWQ9InNoYXBlIiBmaWxsPSIjMDAzNjYzIiBkPSJNMjYuNTggMTlIMi40MkEyLjQgMi40IDAgMCAxIDAgMTYuNjJWMi4zOEEyLjQgMi40IDAgMCAxIDIuNDIgMGgyNC4xNkEyLjQgMi40IDAgMCAxIDI5IDIuMzh2MTQuMjVBMi40IDIuNCAwIDAgMSAyNi41OCAxOXoiLz4gICAgICAgIDxjaXJjbGUgaWQ9InNoYXBlIiBjeD0iMTAuNSIgY3k9IjkuNSIgcj0iNi41IiBmaWxsPSIjRUIxQzI2Ii8+ICAgICAgICA8Y2lyY2xlIGlkPSJzaGFwZSIgY3g9IjE4LjUiIGN5PSI5LjUiIHI9IjYuNSIgZmlsbD0iI0Y5OUYxQiIvPiAgICAgICAgPHBhdGggaWQ9InNoYXBlIiBmaWxsPSIjRUY1RDIwIiBkPSJNMTQuNSA0LjM4YTYuNDkgNi40OSAwIDAgMCAwIDEwLjI0IDYuNDkgNi40OSAwIDAgMCAwLTEwLjI0eiIvPiAgICAgIDwvZz4gICAgPC9nPiAgPC9nPjwvc3ZnPg==',
supported: false
supported: true
},
{
type: 'unionpay',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export const FETCH_SB_CARDS_FAILURE = '@EVENT.FETCH_SB_CARDS_FAILURE'
export const FETCH_SB_CARDS_LOADING = '@EVENT.FETCH_SB_CARDS_LOADING'
export const FETCH_SB_CARDS_SUCCESS = '@EVENT.FETCH_SB_CARDS_SUCCESS'

export const FETCH_EVERYPAY_3DS_DETAILS = '@EVENT.FETCH_EVERYPAY_3DS_DETAILS'
export const FETCH_EVERYPAY_3DS_DETAILS_FAILURE =
'@EVENT.FETCH_EVERYPAY_3DS_DETAILS_FAILURE'
export const FETCH_EVERYPAY_3DS_DETAILS_LOADING =
'@EVENT.FETCH_EVERYPAY_3DS_DETAILS_LOADING'
export const FETCH_EVERYPAY_3DS_DETAILS_SUCCESS =
'@EVENT.FETCH_EVERYPAY_3DS_DETAILS_SUCCESS'

export const FETCH_SB_FIAT_ELIGIBLE = '@EVENT.FETCH_SB_FIAT_ELIGIBLE'
export const FETCH_SB_FIAT_ELIGIBLE_FAILURE =
'@EVENT.FETCH_SB_FIAT_ELIGIBLE_FAILURE'
Expand Down Expand Up @@ -79,12 +87,3 @@ export const INITIALIZE_CHECKOUT = '@EVENT.INITIALIZE_SB_CHECKOUT'
export const SET_STEP = '@EVENT.SET_SB_STEP'

export const SHOW_MODAL = '@EVENT.SHOW_SB_MODAL'

export const SUBMIT_CARD_DETAILS_TO_EVERYPAY =
'@EVENT.SUBMIT_CARD_DETAILS_TO_EVERYPAY'
export const SUBMIT_CARD_DETAILS_TO_EVERYPAY_FAILURE =
'@EVENT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_FAILURE'
export const SUBMIT_CARD_DETAILS_TO_EVERYPAY_LOADING =
'@EVENT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_LOADING'
export const SUBMIT_CARD_DETAILS_TO_EVERYPAY_SUCCESS =
'@EVENT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_SUCCESS'
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ export const destroyCheckout = () => ({
type: AT.DESTROY_CHECKOUT
})

export const fetchEverypay3DSDetails = () => ({
type: AT.FETCH_EVERYPAY_3DS_DETAILS
})
export const fetchEverypay3DSDetailsFailure = (
error: string
): SimpleBuyActionTypes => ({
type: AT.FETCH_EVERYPAY_3DS_DETAILS_FAILURE,
payload: {
error
}
})

export const fetchEverypay3DSDetailsLoading = (): SimpleBuyActionTypes => ({
type: AT.FETCH_EVERYPAY_3DS_DETAILS_LOADING
})

export const fetchEverypay3DSDetailsSuccess = (
everypay3DS: Everypay3DSResponseType
): SimpleBuyActionTypes => ({
type: AT.FETCH_EVERYPAY_3DS_DETAILS_SUCCESS,
payload: {
everypay3DS
}
})

export const fetchSBBalances = (currency?: CoinType) => ({
type: AT.FETCH_SB_BALANCES,
currency
Expand Down Expand Up @@ -346,7 +371,7 @@ export const setStep = (
step: 'ENTER_AMOUNT'
}
| { cardId?: string; step: 'ADD_CARD' }
| { step: 'CURRENCY_SELECTION' }
| { step: 'CURRENCY_SELECTION' | '3DS_HANDLER' }
): SimpleBuyActionTypes => ({
type: AT.SET_STEP,
payload:
Expand Down Expand Up @@ -384,28 +409,3 @@ export const showModal = (
cryptoCurrency
}
})

export const submitCardDetailsToEverypay = () => ({
type: AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY
})
export const submitCardDetailsToEverypayFailure = (
error: string
): SimpleBuyActionTypes => ({
type: AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_FAILURE,
payload: {
error
}
})

export const submitCardDetailsToEverypayLoading = (): SimpleBuyActionTypes => ({
type: AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_LOADING
})

export const submitCardDetailsToEverypaySuccess = (
everypay3ds: Everypay3DSResponseType
): SimpleBuyActionTypes => ({
type: AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_SUCCESS,
payload: {
everypay3ds
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const INITIAL_STATE: SimpleBuyState = {
cardId: undefined,
cards: Remote.NotAsked,
cryptoCurrency: undefined,
everypay3ds: Remote.NotAsked,
everypay3DS: Remote.NotAsked,
fiatCurrency: undefined,
fiatEligible: Remote.NotAsked,
methods: Remote.NotAsked,
Expand Down Expand Up @@ -58,6 +58,22 @@ export function simpleBuyReducer (
quote: Remote.NotAsked,
suggestedAmounts: Remote.NotAsked
}
case AT.FETCH_EVERYPAY_3DS_DETAILS_FAILURE: {
return {
...state,
everypay3DS: Remote.Failure(action.payload.error)
}
}
case AT.FETCH_EVERYPAY_3DS_DETAILS_LOADING:
return {
...state,
everypay3DS: Remote.Loading
}
case AT.FETCH_EVERYPAY_3DS_DETAILS_SUCCESS:
return {
...state,
everypay3DS: Remote.Success(action.payload.everypay3DS)
}
case AT.FETCH_SB_BALANCES_FAILURE: {
return {
...state,
Expand Down Expand Up @@ -249,22 +265,6 @@ export function simpleBuyReducer (
}
}
}
case AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_FAILURE: {
return {
...state,
everypay3ds: Remote.Failure(action.payload.error)
}
}
case AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_LOADING:
return {
...state,
everypay3ds: Remote.Loading
}
case AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY_SUCCESS:
return {
...state,
everypay3ds: Remote.Success(action.payload.everypay3ds)
}
default:
return state
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export default ({ api, coreSagas, networks }) => {
yield takeLatest(AT.INITIALIZE_CHECKOUT, simpleBuySagas.initializeCheckout)
yield takeLatest(AT.SHOW_MODAL, simpleBuySagas.showModal)
yield takeLatest(
AT.SUBMIT_CARD_DETAILS_TO_EVERYPAY,
simpleBuySagas.submitCardDetailsToEverypay
AT.FETCH_EVERYPAY_3DS_DETAILS,
simpleBuySagas.fetchEverypay3DSDetails
)
// Fetch balances when profile/user is fetched
yield takeLatest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as A from './actions'
import * as S from './selectors'
import { actions, selectors } from 'data'
import { APIType } from 'core/network/api'
import { call, put, select } from 'redux-saga/effects'
import { call, delay, put, select } from 'redux-saga/effects'
import {
convertBaseToStandard,
convertStandardToBase
Expand All @@ -25,6 +25,7 @@ import {
NO_PAIR_SELECTED
} from './model'
import { SBAddCardFormValuesType, SBCheckoutFormValuesType } from './types'
import moment from 'moment'
import profileSagas from '../../modules/profile/sagas'

export default ({
Expand Down Expand Up @@ -188,6 +189,7 @@ export default ({

const fetchSBCards = function * () {
try {
yield call(waitForUserData)
yield put(A.fetchSBCardsLoading())
const cards = yield call(api.getSBCards)
yield put(A.fetchSBCardsSuccess(cards))
Expand Down Expand Up @@ -270,6 +272,7 @@ export default ({
currency
}: ReturnType<typeof A.fetchSBPaymentMethods>) {
try {
yield call(waitForUserData)
yield put(A.fetchSBPaymentMethodsLoading())
const methods = yield call(api.getSBPaymentMethods, currency)
yield put(A.fetchSBPaymentMethodsSuccess(methods))
Expand Down Expand Up @@ -368,35 +371,68 @@ export default ({
}
}

const submitCardDetailsToEverypay = function * () {
const fetchEverypay3DSDetails = function * () {
try {
yield put(actions.form.startSubmit('addCCForm'))
yield put(A.submitCardDetailsToEverypayLoading())
yield put(A.fetchEverypay3DSDetailsLoading())
const formValues: SBAddCardFormValuesType = yield select(
selectors.form.getFormValues('addCCForm')
)
const providerDetailsR = S.getSBProviderDetails(yield select())
const providerDetails = providerDetailsR.getOrFail('NO_PROVIDER_DETAILS')
const [nonce] = yield call(api.generateUUIDs, 1)

const response: Everypay3DSResponseType = yield call(
const response: { data: Everypay3DSResponseType } = yield call(
api.submitSBCardDetailsToEverypay,
{
ccNumber: formValues['card-number'].replace(/[^\d]/g, ''),
cvc: formValues['cvc'],
month: formValues['expiry-date'].split('/')[0],
year: formValues['expiry-date'].split('/')[1],
expirationDate: moment(formValues['expiry-date'], 'MM YY'),
holderName: formValues['name-on-card'],
accessToken: providerDetails.everypay.mobileToken,
apiUserName: providerDetails.everypay.apiUsername,
nonce: nonce
}
)
yield put(actions.form.stopSubmit('addCCForm'))
yield put(A.submitCardDetailsToEverypaySuccess(response))
yield put(A.fetchEverypay3DSDetailsSuccess(response.data))
yield put(
A.setStep({
step: '3DS_HANDLER'
})
)
yield call(pollSBCard)
} catch (e) {
const error = errorHandler(e)
yield put(actions.form.stopSubmit('addCCForm', { _error: error }))
yield put(A.submitCardDetailsToEverypayFailure(error))
yield put(A.fetchEverypay3DSDetailsFailure(error))
}
}

const pollSBCard = function * () {
const MAX_RETRY_ATTEMPTS = 15
let attempts = 0
let card: SBCardType = S.getSBCard(yield select()).getOrFail(
'NO_CARD_DETAILS'
)

while (
(card.state === 'CREATED' || card.state === 'PENDING') &&
attempts < MAX_RETRY_ATTEMPTS
) {
attempts += 1
card = yield call(api.getSBCard, card.id)
yield delay(5000)
}

yield put(A.fetchSBCardSuccess(card))

switch (card.state) {
case 'ACTIVE':
return yield put(A.createSBOrder())
case 'BLOCKED':
return yield put(A.setStep({ step: 'ADD_CARD' }))
default:
}
}

Expand All @@ -418,6 +454,6 @@ export default ({
handleSBSuggestedAmountClick,
initializeCheckout,
showModal,
submitCardDetailsToEverypay
fetchEverypay3DSDetails
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { RootState } from 'data/rootReducer'

export const getEverypay3DSDetails = (state: RootState) =>
state.components.simpleBuy.everypay3DS

export const getSBAccount = (state: RootState) =>
state.components.simpleBuy.account

Expand Down

0 comments on commit a8f3d34

Please sign in to comment.