I'm using Yup to validate a signup form but I'm struggling to validate the credit card fields due to needing methods from both Yup's string
and number
schema.
Card number for example should not exceed 16 characters. This is easily achievable using the max
method from the string
schema. However, if the user typed in 16 letters rather than numbers this would currently pass validation. If I change the schema from string
to number
then max
doesn't behave in the same way and instead adds up all the numbers in the field and checks to see if it equates to less than the max number.
An example of a field where I would need to use the min
and max
methods of the number
schema is the expiry month where the min would be 1 and the max would be 12. However, I still need to check to make sure that the number of characters is in this field is 2 as a leading 0 should be used for the months January to September.
const validationSchema = {
cardNumber: Yup.string()
.label('Card number')
.max(16)
.required(),
cvc: Yup.string()
.label('CVC')
.min(3)
.max(4)
.required(),
nameOnCard: Yup.string()
.label('Name on card')
.required(),
expiryMonth: Yup.string()
.label('Expiry month')
.min(2)
.max(2)
.required(),
expiryYear: Yup.string()
.label('Expiry year')
.min(4)
.max(4)
.required(),
};
Instead of doing validation like this, you can use Yup's test method and return true
or false
given by third-party validators. For example: braintree/card-validator
.
So, it would look something like this:
import valid from 'card-validator'; //import statement
CreditCardNumber:
Yup
.string()
.test('test-number', // this is used internally by yup
'Credit Card number is invalid', //validation message
value => valid.number(value).isValid) // return true false based on validation
.required()
There are also valid.expirationDate(value).isValid
, valid.cvv(value).isValid
and valid.postalCode(value).isValid
Hope it helps!
Here's how I used Yup's .test()
method for validating a credit card's expiration in the past.
1) I had to use a mask, and by using a mask the DOM element I had to use was a single input field. The mask looks like this __/__
(two underscores separated by a forward slash)
At first, the validation started off as:
export const expirationDate = Yup.string()
.typeError('Not a valid expiration date. Example: MM/YY')
.max(5, 'Not a valid expiration date. Example: MM/YY')
.matches(
/([0-9]{2})\/([0-9]{2})/,
'Not a valid expiration date. Example: MM/YY'
)
.required('Expiration date is required')
Even though there was server-side validation, I should still do the work to prevent bad inputs such as: 13/19
or 99/99
. Why waste a roundtrip network call. So, in between the .matches()
and .required()
method calls I added:
.test(
'test-credit-card-expiration-date',
'Invalid Expiration Date has past',
expirationDate => {
if (!expirationDate) {
return false
}
const today = new Date()
const monthToday = today.getMonth() + 1
const yearToday = today
.getFullYear()
.toString()
.substr(-2)
const [expMonth, expYear] = expirationDate.split('/')
if (Number(expYear) < Number(yearToday)) {
return false
} else if (
Number(expMonth) < monthToday &&
Number(expYear) <= Number(yearToday)
) {
return false
}
return true
}
)
.test(
'test-credit-card-expiration-date',
'Invalid Expiration Month',
expirationDate => {
if (!expirationDate) {
return false
}
const today = new Date()
.getFullYear()
.toString()
.substr(-2)
const [expMonth] = expirationDate.split('/')
if (Number(expMonth) > 12) {
return false
}
return true
}
)
references:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With