Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

Main Page Content

Validating a Credit Card Number with JavaScript

Rated 4.32 (Ratings: 9) (Add your rating)

Log in to add a comment
(7 comments so far)

Want more?

 
Picture of activeneter

Paul Wilton

Member info | Full bio

User since: April 15, 2002

Last login: April 15, 2002

Articles written: 1

Please note, this article is an excerpt from the book Practical JavaScript for the Usable Web (ISBN: 1904151051) by Paul Wilton, Stephen Williams, and Sing Li.

JavaScript is a core skill for web professionals, and as every web professional knows, client-side JavaScript can produce all sorts of glitches and bugs. Practical JavaScript for the Usable Web takes a two pronged approach to learning the JavaScript that you need to get your work done: teaching the core client-side JavaScript that you need to incorporate usable interactivity into your web applications, including many short functional scripts, and building up a complete application with shopping cart functionality.

The JavaScript function excerpted below doesn't require you to own the book in order to get it to work. We, of course, hope it will give you a taste of what the book has to offer, but if not, enjoy it anyway.

Practical JavaScript for the Usable Web

Our final validation method checks whether a credit card number could be a valid card number. Note that I say "could be" rather than "is" — just because the number is valid, doesn't mean that the card has been allocated or that it has not been canceled, if it was allocated. Only server-side processing can possibly validate a card number. However, what we can do here is check that the user hasn't made an accidental mistake so that we can get them to rectify any mistakes before we attempt server-side checks.

As we'll see shortly, validating a credit card is much more complex than any of the validation methods we have created so far. There are three checks we can perform client-side:

  • Check that only numbers or spaces are given in the credit card number — not letters or other characters.
  • Check that, for the given card type, the number of digits given is valid and the prefix to the number is valid.
  • Use the Luhn formula to check the validity of the entered credit card number. This is a special algorithm that can be applied to most credit card numbers to check that the number would be valid.

We'll be using all three of these checks in our method.

Validate.prototype.isValidCreditCardNumber = function(cardNumber, cardType)
{
  var isValid = false;
  var ccCheckRegExp = /[^\d ]/;
  isValid = !ccCheckRegExp.test(cardNumber);

  if (isValid)
  {
    var cardNumbersOnly = cardNumber.replace(/ /g,"");
    var cardNumberLength = cardNumbersOnly.length;
    var lengthIsValid = false;
    var prefixIsValid = false;
    var prefixRegExp;

    switch(cardType)
    {
      case "mastercard":
        lengthIsValid = (cardNumberLength == 16);
        prefixRegExp = /^5[1-5]/;
        break;

      case "visa":
        lengthIsValid = (cardNumberLength == 16 || cardNumberLength == 13);
        prefixRegExp = /^4/;
        break;

      case "amex":
        lengthIsValid = (cardNumberLength == 15);
        prefixRegExp = /^3(4|7)/;
        break;

      default:
        prefixRegExp = /^$/;
        alert("Card type not found");
    }

    prefixIsValid = prefixRegExp.test(cardNumbersOnly);
    isValid = prefixIsValid && lengthIsValid;
  }

  if (isValid)
  {
    var numberProduct;
    var numberProductDigitIndex;
    var checkSumTotal = 0;

    for (digitCounter = cardNumberLength - 1; 
      digitCounter >= 0; 
      digitCounter--)
    {
      checkSumTotal += parseInt (cardNumbersOnly.charAt(digitCounter));
      digitCounter--;
      numberProduct = String((cardNumbersOnly.charAt(digitCounter) * 2));
      for (var productDigitCounter = 0;
        productDigitCounter < numberProduct.length; 
        productDigitCounter++)
      {
        checkSumTotal += 
          parseInt(numberProduct.charAt(productDigitCounter));
      }
    }

    isValid = (checkSumTotal % 10 == 0);
  }

  return isValid;
}

We'll take this method step by step. Note first of all, that the method takes two parameters — the card number and the card type (mastercard, amex, and visa are valid card types that we will cater for here, though the method could be extended for other card types).

The first part of the method checks that only numbers or spaces have been entered:

  var isValid = false;
  var ccCheckRegExp = /[^\d ]/;
  isValid = !ccCheckRegExp.test(cardNumber);

The regular expression /[^\d ]/ will match invalid characters (any character that is not a digit or a space). When we test the card number against the regular expression on the third line, as in previous methods, we use the ! character to reverse the logic, so that isValid is set to false if invalid characters are found in the card number.

The next part of the method checks that the card has a valid prefix, that is it starts with the correct numbers for that card type, and contains the correct number of digits, again specific to a card type. The prefixes and lengths for some commonly available cards are shown below:

Card Type Prefix Number of Digits
Visa 4 13,16
Mastercard 51-55 16
American Express 34,37 15

You can find more information on card details at http://www.beachnet.com/~hstiles/cardtype.html.

First we strip out any spaces the user may have put in their credit card number when they entered it:

  if (isValid)
  {
    var cardNumbersOnly = cardNumber.replace(/ /g,"");
    var cardNumberLength = cardNumbersOnly.length;
    var lengthIsValid = false;
    var prefixIsValid = false;
    var prefixRegExp;

The if statement checks whether the previous validation (that the card number only contained numbers or spaces) found the number to be valid, and only proceeds with the next check if it did. Then, using a regular expression (a space between the / regular expression delimiters, together with the global flag g) and the String object's replace() method, we strip out the spaces.

Variable cardNumberLength is set to the length of the string (the number of digits in the string). The variables lengthIsValid and prefixIsValid will store Boolean values indicating the validity of the length and prefix checks that we do next.

We now need to check the cardType parameter of the method and, from that, decide what the prefix to the card number should be and how long the number should be.

The switch statement checks the card type to see which, if any, of the known card types is found:

    switch(cardType)
    {
      case "mastercard":
        lengthIsValid = (cardNumberLength == 16);
        prefixRegExp = /^5[1-5]/;
        break;

      case "visa":
        lengthIsValid = (cardNumberLength == 16 || cardNumberLength == 13);
        prefixRegExp = /^4/;
        break;

      case "amex":
        lengthIsValid = (cardNumberLength == 15);
        prefixRegExp = /^3(4|7)/;
        break;

      default:
        prefixRegExp = /^$/;
        alert("Card type not found");
    }

In each case statement, we set lengthIsValid to the Boolean returned by the logical expression that checks for the correct card length. Then we create a regular expression that will check for the correct prefix for that card. In the default case, we create a regular expression that matches nothing, and warn the customer that their card type hasn't been found. If we want to allow different cards, for example diners club, then our switch statement simply needs an extra case adding to match the new type of card's parameters, that is size and start digits.

We next check the prefix using the regular expression set in the relevant case statement, and then set isValid to the results of the logical addition prefixIsValid and lengthIsValid, which will be true only if both these values are true:

    prefixIsValid = prefixRegExp.test(cardNumbersOnly);
    isValid = prefixIsValid && lengthIsValid;

OK, that's the two easy checks done. Now we have the part of the method that checks the card number using the Luhn formula, which works with almost all card types. This special formula, also known as Modula 10 or Mod 10, tells us whether the number it is applied to could be a valid number. Obviously, it doesn't guarantee that the number is actually in use, only that it could be used.

We'll walk through the basic formula, using the credit card number 4221 3456 1243 1237 as an example:

  1. Start with the second digit from last in the card number. Moving backwards towards the first digit in the number, double each alternate digit.
    In our example, we would double the bold numbers in 4221 3456 1243 1237 to give us:
    (4x2), (2x2), (3x2), (5x2), (1x2), (4x2), (1x2), (3x2)
    which is:
    8, 4, 6, 10, 2, 8, 2, 6

  2. Take the results of the doubling, add each of the individual digits in each doubled number together, and then add to the running total.
    In our example we have:
    8 + 4 + 6 + (1 + 0) + 2 + 8 + 2 + 6 = 37

  3. Add all the non-doubled digits from the credit card number together.
    In our example we have:
    2 + 1 + 4 + 6 + 2 + 3 + 2 + 7 = 27

  4. Add the values calculated in step 2 and step 3 together.
    In our example:
    37 + 27 = 64

  5. Take the value calculated in step 4 and calculate the remainder when it is divided by 10. If the remainder is zero, then it's a valid number, otherwise it's invalid.
    In our example:
    64 / 10 = 6 with remainder 4.
    So, our example number is not a valid card number, as the remainder is not 0.

Now we understand how it works, let's look at the code in our method that uses it.

First we check if isValid is true (that is, if all other checks so far proved satisfactory). Then, after our variable declarations, we have the main for loop that will go through the credit card number a digit at a time starting with the last digit and moving to the first.

  if (isValid)
  {
    var numberProduct;
    var numberProductDigitIndex;
    var checkSumTotal = 0;

    for (digitCounter = cardNumberLength - 1; 
      digitCounter > 0; 
      digitCounter--)
    {

In this for loop we do a number of things. Firstly we add a digit's value to the running total, checkSumTotal.

      checkSumTotal += parseInt (cardNumbersOnly.charAt(digitCounter));

Then we move to the digit that is one nearer the start of the card number string. We multiply this next digit by 2.

      digitCounter--;
      numberProduct = String((cardNumbersOnly.charAt(digitCounter) * 2));

We take the digits forming the results of the product calculation and in the inner for loop we add these digits to the running total.

      for (var productDigitCounter = 0;
        productDigitCounter < numberProduct.length; 
        productDigitCounter++)
      {
        checkSumTotal += 
          parseInt(numberProduct.charAt(productDigitCounter));
      }

Our outer for loop continues by moving to the next digit to the left, and iterates until we reach the first digit in the credit card number string.

If we think back to the explanation of the Luhn formula, our approach is out of step in that we are not doing step 1, then step 2, and so on, but instead are merging steps 1-4 and processing the number on a digit by digit basis, keeping a running total. It amounts to exactly the same thing, but reduces the number of loops required.

Let's summarize the steps in our outer and inner for loops:

  • Extract the character whose position is specified by digitCounter, from the string in cardNumbersOnly.
  • Add this number to our running total, which is kept in variable checkSumTotal.
  • Decrement the character counter, digitCounter. This now refers to the next digit to the left in our string cardNumbersOnly.
  • Extract the next digit, convert it to a number, and then double it. This is all done on one line and this product is stored in variable numberProduct.
  • Convert the product calculated in step 4 to a string.
  • Loop through each digit in the product string, convert it to an integer, and add to the running total. This is our inner for loop. For example, if the character extracted in step 4 was 8, its product would be 16, and so we'd add 1 and then 6 to our running total.
  • Decrement the digitCounter and move to step 1.

In the table below, we show what digits are extracted and what values added to the running total for the example card number 4221 3456 1243 1287.

Character Extracted What happens to it What happens to running total Running total Value
7 Added to running total Running total (0) + character extracted (7) 7
8 Doubled (16), then each digit in the product added to running total Running total (7) + first digit in product (1) + second digit in product (6) 14
2 Added to running total Running total (14) + character extracted (2) 16
1 Doubled (2), then each digit in the product added to running total Running total (16) + first digit in product (2) 18
3 Added to running total Running total (18) + character extracted (3) 21
4 Doubled (8), then each digit in the product added to running total Running total (21) + first digit in product (8) 29
2 Added to running total Running total (29) + character extracted (2) 31
1 Doubled (2), then each digit in the product added to running total Running total (31) + first digit in product (2) 33
6 Added to running total Running total (33) + character extracted (6) 39
5 Doubled (10), then each digit in the product added to running total Running total (39) + first digit in product (1) + second digit in product(0) 40
4 Added to running total Running total (40) + character extracted (4) 44
3 Doubled (6), then each digit in the product added to running total Running total (44) + first digit in product (6) 50
1 Added to running total Running total (50) + character extracted (1) 51
2 Doubled (4), then each digit in the product added to running total Running total (51) + first digit in product (4) 55
2 Added to running total Running total (55) + character extracted (2) 57
4 Doubled (8), then each digit in the product added to running total Running total (57) + first digit in product (8) 65

Once we have our result, in the table above that's 65, we then find its modulus 10 value, that is the remainder left over when the running total is divided by ten. If it's zero, we have ourselves a valid credit card number, otherwise it's a fake. We set isValid to the result of the Boolean expression comparing the remainder to zero — if true it's valid, if false it's invalid. In our example, we have 65, the modulus 10 value of which is 5, so the example number is invalid — you didn't really think I'd give my credit card number out now did you?

After an initial stint as a Visual Basic applications programmer at the Ministry of Defence in the UK, Paul Wilton found himself pulled into the Net. He is currently working freelance and is busy trying to piece together the Microsoft .Net jigsaw. Paul's main skills are in developing web front ends using DHTML, JavaScript, VBScript, and Visual Basic, and back-end solutions with ASP, Visual Basic, and SQL Server.

Better than Cold Fusion

Submitted by Per on April 30, 2002 - 15:53.

Would it be better to use this one than the built-in functions of Cold Fusion or do they perform the same checks, does anyone know? / Per

login or register to post comments

client-side vs. server-side

Submitted by aardvark on April 30, 2002 - 16:25.

Per, while I'm not a CF monkey, or a remarkable JS monkey, I can tell you that you should consider using this to supplement what you do in CF. This code sample is only client-side, you'll still need to validate on the server side as you always have. The author touches on this in the first paragraph.

After all, if I disable JS, then does that mean I can insert letters into the CF application as my credit card number? The JS is here to minimize the server hassle and provide more immediate feedback for users, instead of the click-and-wait game.

login or register to post comments

Please add support for dashes

Submitted by mcombs on May 1, 2002 - 09:42.

Just a pet peeve of mine, but I prefer sites that also allow (but not require) dashes when I enter my credit card number. That'd be a simple change to make to this otherwise excellent script.

login or register to post comments

regexp in js = good

Submitted by teradome on May 1, 2002 - 16:08.

that should be as simple as changing two regular expression matches in the provided code:

var ccCheckRegExp = /[^\d -]/;
and
var cardNumbersOnly = cardNumber.replace(/ -/g,"");

login or register to post comments

JS compatibility

Submitted by bobince on May 4, 2002 - 04:59.

It's also worth avoiding more advanced JS such as the Function constructor and regexp literals if you want to be compatible across most browsers. In particular, even the newest version of Opera will generate a nasty JS error when confronted with /.../.

login or register to post comments

Opaque implementation

Submitted by JohnLloydJones on May 5, 2002 - 09:55.

The JS implemetation of Luhn's algorithm suffers from poor readability and coding style. Here's a alternative implementation (written a couple of years back) that is easier to follow. A single for loop does it all. No need to convert the number * 2 back to a string; subtracting nine is the same as summing the digits. Separators are also handled in the same loop. Card type and length are checked in a separate function.

<pre>
function isValidCardNumber (strNum)
{
   var nCheck = 0;
   var nDigit = 0;
   var bEven = false;
   
   for (n = strNum.length - 1; n >= 0; n--)
   {
      var cDigit = strNum.charAt (n);
      if (isDigit (cDigit))
      {
         var nDigit = parseInt(cDigit, 10);
         if (bEven)
         {
            if ((nDigit *= 2) > 9)
               nDigit -= 9;
         }
         nCheck += nDigit;
         bEven = ! bEven;
      }
      else if (cDigit != ' ' && cDigit != '.' && cDigit != '-')
      {
         return false;
      }
   }
   return (nCheck % 10) == 0;
}
function isDigit (c)
{
   var strAllowed = "1234567890";
   return (strAllowed.indexOf (c) != -1);
}
function isCardTypeCorrect (strNum, type)
{
   var nLen = 0;
   for (n = 0; n < strNum.length; n++)
   {
      if (isDigit (strNum.substring (n,n+1)))
         ++nLen;
   }
   
   if (type == 'Visa')
      return ((strNum.substring(0,1) == '4') && (nLen == 13 || nLen == 16));
   else if (type == 'Amex')
      return ((strNum.substring(0,2) == '34' || strNum.substring(0,2) == '37') && (nLen == 15));
   else if (type == 'Master Card')
      return ((strNum.substring(0,2) == '51' || strNum.substring(0,2) == '52'
              || strNum.substring(0,2) == '53' || strNum.substring(0,2) == '54'
              || strNum.substring(0,2) == '55') && (nLen == 16));
   else
      return false;
   
}
</pre>

login or register to post comments

Late Nitpicker

Submitted by rikki_tikki on June 13, 2003 - 12:37.

In the step-by-step explanation, the FOR loop condition is incorrectly shown as:
"digitCounter > 0".

It should be the correct way it's shown up at the top in the complete listing:
"digitCounter >= 0"

login or register to post comments

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.