Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Yup to validate credit card details

Tags:

javascript

yup

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(),
};
like image 980
GuerillaRadio Avatar asked Jun 08 '18 10:06

GuerillaRadio


2 Answers

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

https://github.com/braintree/card-validator

https://github.com/jquense/yup#mixedtestname-string-message-string--function-test-function-schema

Hope it helps!

like image 95
Riadhoq Avatar answered Sep 21 '22 10:09

Riadhoq


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:

  • getMonth()
like image 33
Sgnl Avatar answered Sep 17 '22 10:09

Sgnl