Skip to content

Commit

Permalink
feat(simple buy): credit card form validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Philip London committed Apr 21, 2020
1 parent d7c5eb0 commit 45dde34
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,14 @@ type MessagesType = {
'exchange-side-nav-tooltip-connected': 'connected'
'faq.tooltip.description': 'Frequently Asked Questions'
'formhelper.ageovereighteen': 'Must be 18 or older'
'formhelper.card_expired': 'Card Expired'
'formhelper.card_type_unsupported': 'Card type not supported'
'formhelper.exchange.learnmore': 'Learn more.'
'formhelper.exchange.require2fa': 'Exchange deposits require 2FA.'
'formhelper.incorrectpassword': 'Incorrect password'
'formhelper.invalid_card_number': 'Invalid card number'
'formhelper.invalid_ethereum_address': 'Invalid Ethereum address'
'formhelper.invalid_expiry_date': 'Invalid Expiry Date'
'formhelper.invalidbchaddress': 'Invalid Bitcoin Cash address'
'formhelper.invalidbitcoinaddressandkey': 'Not a valid Bitcoin address or private key'
'formhelper.invalidbitcoinprivatekey': 'Invalid Bitcoin Private Key'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { normalizeCreditCard } from './index.tsx'
describe('normalizeCreditCard', () => {
it('should normalize a cc number', () => {
expect(normalizeCreditCard('123456')).toBe('1234 56')
expect(normalizeCreditCard('12345678')).toBe('1234 5678 ')
expect(normalizeCreditCard('12345678')).toBe('1234 5678')
expect(normalizeCreditCard('12345678901')).toBe('1234 5678 901')
expect(normalizeCreditCard('123456789012')).toBe('1234 5678 9012 ')
expect(normalizeCreditCard('-123456789012-')).toBe('1234 5678 9012 ')
expect(normalizeCreditCard('123456789012')).toBe('1234 5678 9012')
expect(normalizeCreditCard('invalid_characters!@#$%^&&*()+')).toBe('')
})
})
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
import { CommonFieldProps, WrappedFieldMetaProps } from 'redux-form'
import { DEFAULT_CARD_FORMAT, getCardTypeByValue } from './model'
import { FormattedMessage } from 'react-intl'
import { TextBox } from 'components/Form'
import React from 'react'

export const normalizeCreditCard = (value, previousValue) => {
if (!value) return value

const onlyNums = value.replace(/[^\d]/g, '')

if (!previousValue || value.length > previousValue.length) {
// typing forward
if (onlyNums.length === 4) {
return onlyNums + ' '
}
if (onlyNums.length === 8) {
return onlyNums.slice(0, 4) + ' ' + onlyNums.slice(4) + ' '
}
if (onlyNums.length === 12) {
return (
onlyNums.slice(0, 4) +
' ' +
onlyNums.slice(4, 8) +
' ' +
onlyNums.slice(8, 12) +
' '
)
}
const { format, maxCardNumberLength } = getCardTypeByValue(value) || {
format: DEFAULT_CARD_FORMAT,
maxCardNumberLength: 16
}

if (onlyNums.length <= 4) {
return onlyNums
if (value.replace(/[^\d]/g, '').length > maxCardNumberLength) {
return previousValue
}
if (onlyNums.length <= 8) {
return onlyNums.slice(0, 4) + ' ' + onlyNums.slice(4)

if (format.global) {
const match = value.match(format)
return match ? match.join(' ') : ''
}

const execResult = format.exec(value.split(' ').join(''))
if (execResult) {
return execResult
.splice(1, 3)
.filter(x => x)
.join(' ')
}
if (onlyNums.length <= 12) {

return value
}

export const validateCreditCard = value => {
const cardType = getCardTypeByValue(value)

if (!cardType) {
return (
<FormattedMessage
id='formhelper.invalid_card_number'
defaultMessage='Invalid card number'
/>
)
}

if (!cardType.supported) {
return (
onlyNums.slice(0, 4) +
' ' +
onlyNums.slice(4, 8) +
' ' +
onlyNums.slice(8, 12)
<FormattedMessage
id='formhelper.card_type_unsupported'
defaultMessage='Card type not supported'
/>
)
}
return (
onlyNums.slice(0, 4) +
' ' +
onlyNums.slice(4, 8) +
' ' +
onlyNums.slice(8, 12) +
' ' +
onlyNums.slice(12, 16)
)
}

const CreditCardBox: React.FC<Props> = props => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// https://github.com/medipass/react-credit-card-input/blob/master/src/utils/formatter.js
export const DEFAULT_CVC_LENGTH = 3
export const DEFAULT_ZIP_LENGTH = 5
export const DEFAULT_CARD_MIN = 13
export const DEFAULT_CARD_FORMAT = /(\d{1,4})/g

export const CARD_TYPES = [
Expand All @@ -9,92 +10,118 @@ export const CARD_TYPES = [
format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
startPattern: /^3[47]/,
maxCardNumberLength: 15,
cvcLength: 4
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: 4,
supported: false
},
{
type: 'dankort',
format: DEFAULT_CARD_FORMAT,
startPattern: /^5019/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'hipercard',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(384100|384140|384160|606282|637095|637568|60(?!11))/,
maxCardNumberLength: 19,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'dinersclub',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(36|38|30[0-5])/,
maxCardNumberLength: 14,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'discover',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(6011|65|64[4-9]|622)/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'jcb',
format: DEFAULT_CARD_FORMAT,
startPattern: /^35/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'laser',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(6706|6771|6709)/,
maxCardNumberLength: 19,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'maestro',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(5018|5020|5038|6304|6703|6708|6759|676[1-3])/,
maxCardNumberLength: 19,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'mastercard',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(5[1-5]|677189)|^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'unionpay',
format: DEFAULT_CARD_FORMAT,
startPattern: /^62/,
maxCardNumberLength: 19,
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false,
luhn: false
},
{
type: 'visaelectron',
format: DEFAULT_CARD_FORMAT,
startPattern: /^4(026|17500|405|508|844|91[37])/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: true
},
{
type: 'elo',
format: DEFAULT_CARD_FORMAT,
startPattern: /^(4011(78|79)|43(1274|8935)|45(1416|7393|763(1|2))|50(4175|6699|67[0-7][0-9]|9000)|627780|63(6297|6368)|650(03([^4])|04([0-9])|05(0|1)|4(0[5-9]|3[0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8])|9([2-6][0-9]|7[0-8])|541|700|720|901)|651652|655000|655021)/,
maxCardNumberLength: 16,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: false
},
{
type: 'visa',
format: DEFAULT_CARD_FORMAT,
startPattern: /^4/,
maxCardNumberLength: 19,
cvcLength: DEFAULT_CVC_LENGTH
minCardNumberLength: DEFAULT_CARD_MIN,
cvcLength: DEFAULT_CVC_LENGTH,
supported: true
}
]

Expand All @@ -114,4 +141,4 @@ export type CardNameType =
| 'visa'

export const getCardTypeByValue = value =>
CARD_TYPES.filter(cardType => cardType.startPattern.test(value))[0]
CARD_TYPES.find(cardType => cardType.startPattern.test(value))
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CommonFieldProps, WrappedFieldMetaProps } from 'redux-form'
import { FormattedMessage } from 'react-intl'
import { TextBox } from 'components/Form'
import moment from 'moment'
import React from 'react'

export const normalizeCreditCardExpiry = (value, previousValue) => {
Expand All @@ -11,6 +13,37 @@ export const normalizeCreditCardExpiry = (value, previousValue) => {
return onlyNumsOrSlash
}

export const validateCreditCardExpiry = (value: string) => {
const regex = /\d{2}\/\d{2}/

if (!value.match(regex)) {
return (
<FormattedMessage
id='formhelper.invalid_expiry_date'
defaultMessage='Invalid Expiry Date'
/>
)
}

if (Number(value.split('/')[0]) > 12) {
return (
<FormattedMessage
id='formhelper.invalid_expiry_date'
defaultMessage='Invalid Expiry Date'
/>
)
}

if (moment(value, 'MM/YY') < moment()) {
return (
<FormattedMessage
id='formhelper.card_expired'
defaultMessage='Card Expired'
/>
)
}
}

const CreditCardExpiryBox: React.FC<Props> = props => {
return <TextBox {...props} />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,17 +377,18 @@ export default ({
)
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(
api.submitSBCardDetailsToEverypay,
{
ccNumber: formValues['card-number'],
ccNumber: formValues['card-number'].replace(/[^\d]/g, ''),
cvc: formValues['cvc'],
month: formValues['expiry-date'].split('/')[0],
year: formValues['expiry-date'].split('/')[1],
accessToken: providerDetails.everypay.mobileToken,
apiUserName: providerDetails.everypay.apiUsername,
nonce: '1234'
nonce: nonce
}
)
yield put(actions.form.stopSubmit('addCCForm'))
Expand Down

0 comments on commit 45dde34

Please sign in to comment.