Introduction
The ultimate credit card utility provides all of the functions you need to validate and test credit cards in your .NET application, including:
IsValidNumber
GetCardTypeFromNumber
GetCardTestNumber
PassesLuhnTest
All code is provided in both C# and VB. In addition to the utility class, this project includes complete NUnit tests and a sample website for testing the code.
Background
If you have ever tried to process credit cards in your .NET applications, you know that it can be a challenge. Even if you have a good payment processor that provides a .NET SDK, there are a number of functions you're left to handle on your own, like credit card number validation and card type detection. Stop wasting time trying to piece together a utility class from all of the code snippets scattered across the Internet. I have assembled the "ultimate" credit card utility class that provides robust credit number testing, easy credit card type detection, and functions for testing credit card interfaces in your application. To top it off, the ultimate credit card utility class also comes with NUnit tests to help guarantee the code always works as expected in your important credit card applications.
In this article, we'll look at each of the functions included in the ultimate credit card utility class and explain how simple .NET concepts are used to provide powerful functionality.
Using the Code
The ultimate credit card utility class can be easily integrated into any .NET project by directly adding the .cs/.vb file or by compiling the credit card class into a separate assembly that is referenced by the application. If you have multiple applications that deal with credit cards, the best approach is to build the credit card utility class into a separate DLLl so that any updates you make in the future can easily be distributed to all of your .NET applications.
There are five basic functions in this utility class, and we'll look at each in reverse order, building up from the more complex base functions to the simple validation functions.
PassesLuhnTest
At the core of any credit card utility class is Luhn's test. This test, also known as "mod 10", was created by IBM engineer Hans Luhn in the mid-1950's, and stands as one of the easiest ways to validate that a series of numbers is valid and is not a random collection of integers. All credit card numbers should pass Luhn's test; if they don't, it's an easy way to determine that an incorrect number has been entered.
In the ultimate credit card utility, we implement a simple function that accepts a credit card number as a string and returns true if the number passes Luhn's test.
C#
public static bool PassesLuhnTest(string cardNumber)
{
cardNumber = cardNumber.Replace("-", "").Replace(" ", "");
int[] digits = new int[cardNumber.Length];
for (int len = 0; len < cardNumber.Length; len++)
{
digits[len] = Int32.Parse(cardNumber.Substring(len, 1));
}
int sum = 0;
bool alt = false;
for (int i = digits.Length - 1; i >= 0; i--)
{
int curDigit = digits[i];
if (alt)
{
curDigit *= 2;
if (curDigit > 9)
{
curDigit -= 9;
}
}
sum += curDigit;
alt = !alt;
}
return sum % 10 == 0;
}
VB
Public Shared Function PassesLuhnTest(ByVal cardNumber As String) As Boolean
cardNumber = cardNumber.Replace("-", "").Replace(" ", "")
Dim digits As Integer() = New Integer(cardNumber.Length - 1) {}
Dim len As Integer = 0
While len < cardNumber.Length
digits(len) = Int32.Parse(cardNumber.Substring(len, 1))
len += 1
End While
Dim sum As Integer = 0
Dim alt As Boolean = False
Dim i As Integer = digits.Length - 1
While i >= 0
Dim curDigit As Integer = digits(i)
If alt Then
curDigit *= 2
If curDigit > 9 Then
curDigit -= 9
End If
End If
sum += curDigit
alt = Not alt
i -= 1
End While
Return sum Mod 10 = 0
End Function
GetCardTypeFromNumber
When you shop online, you may have noticed that some sites require you to select your card type and some don't. Whether you ask for the card type explicitly or not, you can easily determine a card's type simply by analyzing the number. To do that in our utility class, we are going to leverage the power of Regular Expressions and their ability to store matches in named groups. Learning Regular Expressions can be challenging, but great resources exist that help you generate Regular Expression patterns for every situation, such as regexlib.com and Regex Buddy (software). For our utility, we will use a Regular Expression from RegExLib that validates all major credit cards.
^(?:(?<Visa>4\d{3})|(?<MasterCard>5[1-5]\d{2})|(?<Discover>6011)|
(?<DinersClub>(?:3[68]\d{2})|(?:30[0-5]\d))|(?<Amex>3[47]\d{2}))([ -]?)
(?(DinersClub)(?:\d{6}\1\d{4})|(?(Amex)(?:\d{6}\1\d{5})|
(?:\d{4}\1\d{4}\1\d{4})))$
For a complete analysis of this Regular Expression, visit this external resource. Generated by Regex Buddy, this analysis describes in English how each rule in the regex pattern works, and links to online help explaining each rule.
With our regex pattern in hand, we can now take any credit card number and determine its type.
C#
private const string cardRegex = "^(?:(?<Visa>4\\d{3})|
(?<MasterCard>5[1-5]\\d{2})|(?<Discover>6011)|(?<DinersClub>
(?:3[68]\\d{2})|(?:30[0-5]\\d))|(?<Amex>3[47]\\d{2}))([ -]?)
(?(DinersClub)(?:\\d{6}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})
|(?:\\d{4}\\1\\d{4}\\1\\d{4})))$";
public static CreditCardTypeType? GetCardTypeFromNumber(string cardNum)
{
Regex cardTest = new Regex(cardRegex);
GroupCollection gc = cardTest.Match(cardNum).Groups;
if (gc[CreditCardTypeType.Amex.ToString()].Success)
{
return CreditCardTypeType.Amex;
}
else if (gc[CreditCardTypeType.MasterCard.ToString()].Success)
{
return CreditCardTypeType.MasterCard;
}
else if (gc[CreditCardTypeType.Visa.ToString()].Success)
{
return CreditCardTypeType.Visa;
}
else if (gc[CreditCardTypeType.Discover.ToString()].Success)
{
return CreditCardTypeType.Discover;
}
else
{
return null;
}
}
VB
Private Const cardRegex As String = "^(?:(?<Visa>4\d{3})|
(?<MasterCard>5[1-5]\d{2})|(?<Discover>6011)|(?<DinersClub>
(?:3[68]\d{2})|(?:30[0-5]\d))|(?<Amex>3[47]\d{2}))([ -]?)
(?(DinersClub)(?:\d{6}\1\d{4})|(?(Amex)(?:\d{6}\1\d{5})|
(?:\d{4}\1\d{4}\1\d{4})))$"
Public Shared Function GetCardTypeFromNumber(ByVal cardNum As String)
As CreditCardTypeType
Dim cardTest As New Regex(cardRegex)
Dim gc As GroupCollection = cardTest.Match(cardNum).Groups
If gc(CreditCardTypeType.Amex.ToString()).Success Then
Return CreditCardTypeType.Amex
ElseIf gc(CreditCardTypeType.MasterCard.ToString()).Success Then
Return CreditCardTypeType.MasterCard
ElseIf gc(CreditCardTypeType.Visa.ToString()).Success Then
Return CreditCardTypeType.Visa
ElseIf gc(CreditCardTypeType.Discover.ToString()).Success Then
Return CreditCardTypeType.Discover
Else
Return Nothing
End If
End Function
You may have noticed that we are referring to a CreditCardTypeType
in the above code. The original version of this utility was designed to work with PayPal's Web Payments Pro and that type was provided by the PayPal API. Without the PayPal API available, we can easily recreate this type for use in our utility.
C#
public enum CreditCardTypeType
{
Visa,
MasterCard,
Discover,
Amex,
Switch,
Solo
}
VB
Public Enum CreditCardTypeType
Visa
MasterCard
Discover
Amex
Switch
Solo
End Enum
IsNumberValid
Now that we've built-up some basic items needed to validate credit cards, we can create a simple function to validate card numbers. Why not access the Luhn test directly to validate credit cards? While the Luhn formula will validate that numbers appear in the correct order, it will not validate that a card is of the correct type. To make sure our utility class validates that numbers are correct and of a specific card type, we'll use the regex test and our PassesLuhnTest
to ensure the supplied credit card number appears valid.
C#
public static bool IsValidNumber(string cardNum, CreditCardTypeType? cardType)
{
Regex cardTest = new Regex(cardRegex);
if (cardTest.Match(cardNum).Groups[cardType.ToString()].Success)
{
if (PassesLuhnTest(cardNum))
return true;
else
return false;
}
else
return false;
}
VB
Public Shared Function IsValidNumber(ByVal cardNum As String, _
ByVal cardType As CreditCardTypeType) As Boolean
Dim cardTest As New Regex(cardRegex)
If cardTest.Match(cardNum).Groups(cardType.ToString()).Success Then
If PassesLuhnTest(cardNum) Then
Return True
Else
Return False
End If
Else
Return False
End If
End Function
Sometimes you may just want to validate a number, though, without supplying the card type. Well, we already have a function that can determine card type by card number, so let's simply create an overloaded version of IsValidNumber
that accepts a single argument (the card number).
C#
public static bool IsValidNumber(string cardNum)
{
Regex cardTest = new Regex(cardRegex);
CreditCardTypeType? cardType = GetCardTypeFromNumber(cardNum);
if (IsValidNumber(cardNum, cardType))
return true;
else
return false;
}
VB
Public Shared Function IsValidNumber(ByVal cardNum As String) As Boolean
Dim cardTest As New Regex(cardRegex)
Dim cardType As CreditCardTypeType = GetCardTypeFromNumber(cardNum)
If IsValidNumber(cardNum, cardType) Then
Return True
Else
Return False
End If
End Function
GetCardTestNumber
We have built a comprehensive test that will check card numbers for both type and accuracy, so now we need an easy way to generate "bogus" test card numbers for use during development. If you add code to your application to detect when it's in "dev" mode (such as adding a key to the configuration files or reading the active URL), you can code your interfaces to automatically fill-in a bogus credit card number when you run your tests. That will save you from the repetitive task of manually entering valid test data when developing your application. When your application runs in production, this test should be turned-off and only customer credit card numbers should be accepted.
C#
public static string GetCardTestNumber(CreditCardTypeType cardType)
{
switch (cardType)
{
case CreditCardTypeType.Amex:
return "3782 822463 10005";
case CreditCardTypeType.Discover:
return "6011 1111 1111 1117";
case CreditCardTypeType.MasterCard:
return "5105 1051 0510 5100";
case CreditCardTypeType.Visa:
return "4111 1111 1111 1111";
default:
return null;
}
}
VB
Public Shared Function GetCardTestNumber(ByVal cardType As CreditCardTypeType)
As String
Select Case cardType
Case CreditCardTypeType.Amex
Return "3782 822463 10005"
Case CreditCardTypeType.Discover
Return "6011 1111 1111 1117"
Case CreditCardTypeType.MasterCard
Return "5105 1051 0510 5100"
Case CreditCardTypeType.Visa
Return "4111 1111 1111 1111"
Case Else
Return Nothing
End Select
End Function
Testing the Utility
Any code that is going to be supporting transactions as important as processing credit cards should have a complete set of tests to ensure the code is working as expected. As such, the ultimate credit utility comes with a comprehensive set of NUnit tests that can be added to your regular testing routines.
Covering the ins and outs of creating NUnit tests is not in the scope of this article, but here is a sample NUnit test for this utility class that tests the IsValidNumber
function.
C#
[Test()]
public void IsValidNumberTypeTest()
{
string numDash = "4111-1111-1111-1111";
string numSpace = "4111 1111 1111 1111";
string numNoSpace = "4111111111111111";
string numBadSep = "4111.1111.1111.1111";
string numBadLen = "4111-1111-1111-111";
Assert.IsTrue(CreditCardUtility.IsValidNumber(numDash),
"IsValidNumber should allow numbers with dashes");
Assert.IsTrue(CreditCardUtility.IsValidNumber(numSpace),
"IsValidNumber should allow numbers with spaces");
Assert.IsTrue(CreditCardUtility.IsValidNumber(numNoSpace),
"IsValidNumber should allow numbers with no spaces");
Assert.IsFalse(CreditCardUtility.IsValidNumber(numBadLen),
"IsValidNumber should not allow numbers with too few numbers");
Assert.IsFalse(CreditCardUtility.IsValidNumber(numBadSep),
"IsValidNumber should not allow numbers with dot separators");
}
Demo Website
Included in the ultimate credit card utility download is a demo website that allows you to quickly test any credit card number in the browser. The site CSS has been optimized for Firefox, but it is usable in any modern browser. To add an extra level of interactivity to the demo application, trial versions of the Telerik RadControls have been used for masked input and AJAX. The trial version of the controls can be freely used in demo applications, but a periodic trail message will appear on a random number of page loads. For more information on the commercial version of RadControls, visit http://www.telerik.com.
Conclusions
And that's it! The ultimate credit card utility class provides all of the basic functions you need to validate credit card numbers before you pass them along to your processor. The utility cannot absolutely guarantee that a number is a real credit card number before it is processed, but it can eliminate most card input errors and enable you to reduce trips to your payment processor. Hopefully, you will find this utility helpful and it will save you time on your next credit card application.
History
Version |
Date |
Comments |
1.1
|
30 August 2007 |
Updated test credit numbers with PayPal's recommended test numbers (thanks Scott). |
1.0 |
29 August 2007 |
Initial version posted. |