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:

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:

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?