Validate Credit Cards with JavaScript

By  on  

We all know that JavaScript shouldn't be a web form's only method of validation but client side validation does prevent unnecessary server side processing when user input is obviously invalid.  I'd also consider client side form validation a usability booster (...or nightmare when done poorly.)  I often hear developers complain about how difficult validating credit cards can be, especially when only supporting specific credit cards.  Luckily the Dojo Toolkit includes dojox.validate.creditCard, a resource capable of efficiently validating a credit card.  Better yet, you can easily take its logic and create you own validator.

The Dojo JavaScript

The validation code is quite compact but well-commented;  read those comments as you look at the code:

dojo.provide("dojox.validate.creditCard");
/*=====

	dojox.validate.creditCard = {
		// summary:
		//		Module provides validation functions for Credit Cards, using account number
		//		rules in conjunction with the Luhn algorigthm, with a plugable card info database.
	};
	
=====*/
dojo.require("dojox.validate._base");

dojox.validate._cardInfo = {
	// summary: A dictionary list of credit card abbreviations
	//
	// description:
	//
	//		A hash of valid CC abbreviations and regular expressions
	//
	//		mc: Mastercard
	//		ec: Eurocard
	//		vi: Visa
	//		ax: American Express
	//		dc: Diners Club
	//		bl: Carte Blanch
	//		di: Discover
	//		jcb: JCB
	//		er: Enroute
	//
	//	example:
	//		Define your own card, gift-card, whatever. Starts with 7,
	//		is 15 total length.
	//	| dojo.mixin(dojox.validate._cardInfo, {
	//	| 	"my":"7[0-9]{14}"
	//	| });
	
	'mc':'5[1-5][0-9]{14}',
	'ec':'5[1-5][0-9]{14}',
	'vi':'4(?:[0-9]{12}|[0-9]{15})',
	'ax':'3[47][0-9]{13}',
	'dc':'3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
	'bl':'3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
	'di':'6011[0-9]{12}',
	'jcb':'(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
	'er':'2(?:014|149)[0-9]{11}'
}

dojox.validate.isValidCreditCard = function(value, ccType){
	// summary: Validate a credit card number by type with Luhn checking.
	//
	// description:
	//		Checks if a credit card type matches the # scheme in a passed value, and if
	//		the Luhn checksum is accurate (unless its an Enroute card, in which case
	//		the checkSum is skipped), returning a Boolean to check against.
	//
	// value: String|Int
	//		A Value (credit card number) to validate
	//
	// ccType: String
	//		A credit-card abbreviation.
	//
	// example:
	// |	if(dojox.validate.isValidCreditCard("12345", "mc")){
	// |		console.log('inconceivable');
	// |	}
	
	return ((ccType.toLowerCase() == 'er' || dojox.validate.isValidLuhn(value)) &&
			dojox.validate.isValidCreditCardNumber(value, ccType.toLowerCase())); // Boolean
}

dojox.validate.isValidCreditCardNumber = function(value, ccType){
	// summary:
	//		Checks if value matches the pattern for that card or any card types if none is specified
	//
	// value: String|Int
	//		CC #, white spaces and dashes are ignored
	//
	// ccType: String?
	//		One of the abbreviation values in `dojox.validate._cardInfo` --
	//		if Omitted, function returns a `|` delimited string of matching card types,
	//		or false if no matches found.

	value = String(value).replace(/[- ]/g,''); //ignore dashes and whitespaces

	var cardinfo = dojox.validate._cardInfo, results = [];
	if(ccType){
		var expr = '^' + cardinfo[ccType.toLowerCase()] + '$';
		return expr ? !!value.match(expr) : false; // boolean
	}

	for(var p in cardinfo){
		if(value.match('^' + cardinfo[p] + '$')){
			results.push(p);
		}
	}
	return results.length ? results.join('|') : false; // String | boolean
}

dojox.validate.isValidCvv = function(/* String|Int */value, /* String */ccType) {
	// summary:
	//  	Validate the security code (CCV) for a passed credit-card type.
	//
	// description:
	//
	// value:
	
	if(!dojo.isString(value)){
		value = String(value);
	}
	var format;
	switch (ccType.toLowerCase()){
		case 'mc':
		case 'ec':
		case 'vi':
		case 'di':
			format = '###';
			break;
		case 'ax':
			format = '####';
			break;
	}
	
	return !!format && value.length && dojox.validate.isNumberFormat(value, { format: format }); // Boolean
}

You would use the code above by requiring the resource and running the isValidCreditCard method, passing the value and card type.  But what if you don't use the Dojo Toolkit?  You can pull the code out of Dojo and into your own application:

// Create an object
var creditCardValidator = {};
// Pin the cards to them
creditCardValidator.cards = {
	'mc':'5[1-5][0-9]{14}',
	'ec':'5[1-5][0-9]{14}',
	'vi':'4(?:[0-9]{12}|[0-9]{15})',
	'ax':'3[47][0-9]{13}',
	'dc':'3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
	'bl':'3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
	'di':'6011[0-9]{12}',
	'jcb':'(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
	'er':'2(?:014|149)[0-9]{11}'
};
// Add the card validator to them
creditCardValidator.validate = function(value,ccType) {
	value = String(value).replace(/[- ]/g,''); //ignore dashes and whitespaces

	var cardinfo = creditCardValidator.cards, results = [];
	if(ccType){
		var expr = '^' + cardinfo[ccType.toLowerCase()] + '$';
		return expr ? !!value.match(expr) : false; // boolean
	}

	for(var p in cardinfo){
		if(value.match('^' + cardinfo[p] + '$')){
			results.push(p);
		}
	}
	return results.length ? results.join('|') : false; // String | boolean
};

With the creditCardValidator object complete, it's time to use the resource:

if(!creditCardValidator.validate(document.id("creditCardField"))) {
	alert("Invalid credit card!");
}

There you have it:  simple credit card validation with code taken from the Dojo Toolkit.  Problem solved!

Recent Features

  • By
    5 Awesome New Mozilla Technologies You’ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

  • By
    5 HTML5 APIs You Didn’t Know Existed

    When you say or read "HTML5", you half expect exotic dancers and unicorns to walk into the room to the tune of "I'm Sexy and I Know It."  Can you blame us though?  We watched the fundamental APIs stagnate for so long that a basic feature...

Incredible Demos

  • By
    Create a Dojo Lightbox with dojox.image.Lightbox

    One of the reasons I love the Dojo Toolkit is that it seems to have everything.  No scouring for a plugin from this site and then another plugin from that site to build my application.  Buried within the expansive dojox namespace of Dojo is

  • By
    Introducing MooTools Dotter

    It's best practice to provide an indicator of some sort when performing an AJAX request or processing that takes place in the background. Since the dawn of AJAX, we've been using colorful spinners and imagery as indicators. While I enjoy those images, I am...

Discussion

  1. very nice job, excellent…

  2. Leandro Poblet

    Very nice job.

    This could also be done with HTML5, right?

    I love your blog, keep it this way!

  3. Nice, but you could go even further by verifying the checksum of the number using Luhn algorithm. ;)

    http://en.wikipedia.org/wiki/Luhn_algorithm

    • Great Post, David! :)
      Gonna use that in my new projects, thanks.

      As Adrien mentioned, you could include a Luhn algorithm check. You can read more about that here: http://www.ee.unb.ca/cgi-bin/tervo/luhn.pl and ISO-7812-1.

      I’ve implemented both checks for you. You can test the code on JSFiddle: http://jsfiddle.net/silvinci/QsHA8/12/

      The given example credit card validates.

      // Create an object
      var creditCardValidator = {
          // Pin the cards to them
          'cards': {
              'mc':    '5[1-5][0-9]{14}',
              'ec':    '5[1-5][0-9]{14}',
              'vi':    '4(?:[0-9]{12}|[0-9]{15})',
              'ax':    '3[47][0-9]{13}',
              'dc':    '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
              'bl':    '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
              'di':    '6011[0-9]{12}',
              'jcb':    '(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
              'er':    '2(?:014|149)[0-9]{11}'
          },
          // Add the structure validator to them
          'validateStructure': function(value, ccType) {
              value = String(value).replace(/[^0-9]/g, ''); // ignore dashes and whitespaces - We could even ignore all non-numeric chars (/[^0-9]/g)
      
              var cardinfo = creditCardValidator.cards,
                  results  = [];
              if(ccType){
                  var expr = '^' + cardinfo[ccType.toLowerCase()] + '$';
                  return expr ? !!value.match(expr) : false; // boolean
              }
      
              for(var i in cardinfo){
                  if(value.match('^' + cardinfo[i] + '$')){
                      results.push(i);
                  }
              }
              return results.length ? results.join('|') : false; // String | boolean
          },
          // Add the Luhn validator to them
          'validateChecksum': function(value) {
              value = String(value).replace(/[^0-9]/g, ''); // ignore dashes and whitespaces - We could even ignore all non-numeric chars (/[^0-9]/g)
              
              var sum        = 0,
                  parity    = value.length % 2;
              
              for(var i = 0; i  9) {
                      digit = digit - 9; // get the cossfoot - Exp: 10 - 9 = 1 + 0 | 12 - 9 = 1 + 2 | ... | 18 - 9 = 1 + 8
                  }
                  
                  sum += digit;
              }
              
              return ((sum % 10) == 0); // divide by 10 and check if it ends in 0 - return true | false
          },
          // Apply both validations
          'validate': function(value, ccType) {
              if(this.validateChecksum(value)) {
                  return this.validateStructure(value, ccType);
              }
              return false;
          }
      };
      
      // Example
      var card   = prompt("Enter the credit card type (mc, ec, vi, ax, dc, bl, di, jcb, er)", "mc"),
          number = prompt("Enter the credit card number (default matches mc)", "5100000000000040");
      alert(creditCardValidator.validate(number, card));
      
  4. Looks pretty awesome, I might need this in the future. ;)

    I’ve decided I’m going to learn MooTools, it’s definitely more to my liking than any other framework. This blog is going to become a gold mine for me. xD

    Keep up the great writing, we all dig it.

  5. Nice work!

    Another really great credit card processor is:
    https://github.com/madrobby/creditcard_js

  6. Carlos

    Or no need for libraries (originally from http://http://developer.netscape.com/library/examples/ But URL no longer seems to work):

    function isCreditCard(st) {
    	if( st.length > 19 || 13 > st.length ) { return false; }
    	var sum = 0;
    	var mul = 1;
    	l = st.length;
    	for( var i = 0; i = 10 ) {
    			sum += (tproduct % 10) + 1;
    		} else {
    			sum += tproduct;
    		}
    		if( mul == 1 ) {
    			mul++;
    		} else {
    			mul--;
    		}
    	}
    	return ( (sum % 10) == 0 );
    }
    
    • Carlos

      Ah sorry, didn’t pay enough attention, you did provide alternative without needing Dojo.

  7. Brian

    Isn’t there an issue of trust with client side anything?

  8. Great Job, David! Really nice read.

    As Adrien mentioned, you could even include the Luhn algorithm to improve the validation.

    I’ve done this for you: http://jsfiddle.net/silvinci/84bru/

    // Create an object
    var creditCardValidator = {
        // Pin the cards to them
        'cards': {
            'mc':    '5[1-5][0-9]{14}',
            'ec':    '5[1-5][0-9]{14}',
            'vi':    '4(?:[0-9]{12}|[0-9]{15})',
            'ax':    '3[47][0-9]{13}',
            'dc':    '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
            'bl':    '3(?:0[0-5][0-9]{11}|[68][0-9]{12})',
            'di':    '6011[0-9]{12}',
            'jcb':    '(?:3[0-9]{15}|(2131|1800)[0-9]{11})',
            'er':    '2(?:014|149)[0-9]{11}'
        },
        // Add the structure validator to them
        'validateStructure': function(value, ccType) {
            value = String(value).replace(/[^0-9]/g, ''); // ignore dashes and whitespaces - We could even ignore all non-numeric chars (/[^0-9]/g)
    
            var cardinfo = creditCardValidator.cards,
                results  = [];
            if(ccType){
                var expr = '^' + cardinfo[ccType.toLowerCase()] + '$';
                return expr ? !!value.match(expr) : false; // boolean
            }
    
            for(var i in cardinfo){
                if(value.match('^' + cardinfo[i] + '$')){
                    results.push(i);
                }
            }
            return results.length ? results.join('|') : false; // String | boolean
        },
        // Add the Luhn validator to them
        'validateChecksum': function(value) {
            value = String(value).replace(/[^0-9]/g, ''); // ignore dashes and whitespaces - We could even ignore all non-numeric chars (/[^0-9]/g)
            
            var sum        = 0,
                parity    = value.length % 2;
            
            for(var i = 0; i  9) {
                    digit = digit - 9; // get the cossfoot - Exp: 10 - 9 = 1 + 0 | 12 - 9 = 1 + 2 | ... | 18 - 9 = 1 + 8
                }
                
                sum += digit;
            }
            
            return ((sum % 10) == 0); // divide by 10 and check if it ends in 0 - return true | false
        },
        // Apply both validations
        'validate': function(value, ccType) {
            if(this.validateChecksum(value)) {
                return this.validateStructure(value, ccType);
            }
            return false;
        }
    };
    
    // Example
    var valid = creditCardValidator.validate(number, card);
    
  9. Shawn

    All these are cool. To bad they ALL miss validating the CCV to the number That’s important, ya know?

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!