Click here to Skip to main content
15,861,168 members
Articles / Web Development / ASP.NET

Ultimate .NET Credit Card Utility Class

Rate me:
Please Sign up or sign in to vote.
4.84/5 (41 votes)
29 Aug 2007CPOL6 min read 195.6K   6.4K   161   35
A powerfully simple .NET utility class for validating and testing credit card numbers in C# and VB.

Screenshot - UltimateCreditCardUtility.jpg

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#
C#
public static bool PassesLuhnTest(string cardNumber)
{
    //Clean the card number- remove dashes and spaces
    cardNumber = cardNumber.Replace("-", "").Replace(" ", "");
    
    //Convert card number into digits array
    int[] digits = new int[cardNumber.Length];
    for (int len = 0; len < cardNumber.Length; len++)
    {
        digits[len] = Int32.Parse(cardNumber.Substring(len, 1));
    }

    //Luhn Algorithm
    //Adapted from code availabe on Wikipedia at
    //http://en.wikipedia.org/wiki/Luhn_algorithm
    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;
    }

    //If Mod 10 equals 0, the number is good and this will return true
    return sum % 10 == 0;
}
VB
VB
Public Shared Function PassesLuhnTest(ByVal cardNumber As String) As Boolean
    'Clean the card number- remove dashes and spaces
    cardNumber = cardNumber.Replace("-", "").Replace(" ", "")

    'Convert card number into digits array
    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

    'Luhn Algorithm
    'Adapted from code availabe on Wikipedia at
    'http://en.wikipedia.org/wiki/Luhn_algorithm
    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

    'If Mod 10 equals 0, the number is good and this will return true
    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#
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)
{
    //Create new instance of Regex comparer with our
    //credit card regex patter
    Regex cardTest = new Regex(cardRegex);

    //Compare the supplied card number with the regex
    //pattern and get reference regex named groups
    GroupCollection gc = cardTest.Match(cardNum).Groups;
   
    //Compare each card type to the named groups to 
    //determine which card type the number matches
    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
    {
        //Card type is not supported by our system, return null
        //(You can modify this code to support more (or less)
        // card types as it pertains to your application)
        return null;
    }
}
VB
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
    'Create new instance of Regex comparer with our
    'credit card regex patter
    Dim cardTest As New Regex(cardRegex)

    'Compare the supplied card number with the regex
    'pattern and get reference regex named groups
    Dim gc As GroupCollection = cardTest.Match(cardNum).Groups

    'Compare each card type to the named groups to 
    'determine which card type the number matches
    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
        'Card type is not supported by our system, return null
        '(You can modify this code to support more (or less)
        ' card types as it pertains to your application)
        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#
C#
public enum CreditCardTypeType
{
    Visa,
    MasterCard,
    Discover,
    Amex,
    Switch,
    Solo
}
VB
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#
C#
public static bool IsValidNumber(string cardNum, CreditCardTypeType? cardType)
{
    //Create new instance of Regex comparer with our 
    //credit card regex pattern
    Regex cardTest = new Regex(cardRegex);

    //Make sure the supplied number matches the supplied
    //card type
    if (cardTest.Match(cardNum).Groups[cardType.ToString()].Success)
    {
        //If the card type matches the number, then run it
        //through Luhn's test to make sure the number appears correct
        if (PassesLuhnTest(cardNum))
            return true;
        else
            //The card fails Luhn's test
            return false;
    }
    else
        //The card number does not match the card type
        return false;
}
VB
VB
Public Shared Function IsValidNumber(ByVal cardNum As String, _
    ByVal cardType As CreditCardTypeType) As Boolean
    'Create new instance of Regex comparer with our 
    'credit card regex pattern
    Dim cardTest As New Regex(cardRegex)

    'Make sure the supplied number matches the supplied
    'card type
    If cardTest.Match(cardNum).Groups(cardType.ToString()).Success Then
        'If the card type matches the number, then run it
        'through Luhn's test to make sure the number appears correct
        If PassesLuhnTest(cardNum) Then
            Return True
        Else
            Return False
            'The card fails Luhn's test
        End If
    Else
        Return False
        'The card number does not match the card type
    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#
C#
public static bool IsValidNumber(string cardNum)
{
    Regex cardTest = new Regex(cardRegex);

    //Determine the card type based on the number
    CreditCardTypeType? cardType = GetCardTypeFromNumber(cardNum);

    //Call the base version of IsValidNumber and pass the 
    //number and card type
    if (IsValidNumber(cardNum, cardType))
        return true;
    else
        return false;
}
VB
VB
Public Shared Function IsValidNumber(ByVal cardNum As String) As Boolean
    Dim cardTest As New Regex(cardRegex)

    'Determine the card type based on the number
    Dim cardType As CreditCardTypeType = GetCardTypeFromNumber(cardNum)

    'Call the base version of IsValidNumber and pass the 
    'number and card type
    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#
C#
public static string GetCardTestNumber(CreditCardTypeType cardType)
{
    //Return bogus CC number that passes Luhn and format tests
    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
VB
Public Shared Function GetCardTestNumber(ByVal cardType As CreditCardTypeType)
    As String
    'Return bogus CC number that passes Luhn and format tests
    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#
C#
[Test()]
public void IsValidNumberTypeTest()
{
    //Bogus Visa numbers in various formats
    //The IsValidNumber function should accept dashes,
    //spaces, and no separators. It should fail on period 
    //separators and bad numbers.
    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

VersionDate Comments
1.1
30 August 2007 Updated test credit numbers with PayPal's recommended test numbers (thanks Scott).
1.0 29 August 2007Initial version posted.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Todd Anglin is an Experts Exchange ASP.NET Master and a Telerik Technical Evangelist responsible for building a strong Telerik community across the globe. Before joining Telerik, Todd worked as a developer in a Fortune 200 financial services company in San Antonio as a Systems Analyst supporting applications on a wide range of platforms and technologies, including Unix, Windows Server, Informix, Oracle, and SQL Server. Todd graduated Magna Cum Laude with Business Honors from Mays Business School at Texas A&M University with a BBA in MIS and now resides with his wife in The Woodlands, Texas.

Comments and Discussions

 
GeneralGreat Utility Pin
merlin98130-Aug-07 3:58
professionalmerlin98130-Aug-07 3:58 
GeneralRe: Great Utility Pin
BlueLaser0530-Aug-07 7:27
BlueLaser0530-Aug-07 7:27 
QuestionIs there a way to determine a Visa Debit card? Pin
totig29-Aug-07 23:05
totig29-Aug-07 23:05 
AnswerRe: Is there a way to determine a Visa Debit card? Pin
BlueLaser0530-Aug-07 7:37
BlueLaser0530-Aug-07 7:37 
GeneralRe: Is there a way to determine a Visa Debit card? Pin
kcs3-Sep-07 15:34
professionalkcs3-Sep-07 15:34 
GeneralRe: Is there a way to determine a Visa Debit card? Pin
BlueLaser054-Sep-07 3:15
BlueLaser054-Sep-07 3:15 
GeneralRe: Is there a way to determine a Visa Debit card? Pin
kcs4-Sep-07 3:36
professionalkcs4-Sep-07 3:36 
GeneralRe: Is there a way to determine a Visa Debit card? Pin
Alex Michel27-Nov-07 5:55
Alex Michel27-Nov-07 5:55 
http://en.wikipedia.org/wiki/Credit_card_number[^]
GeneralCredit Card Test Numbers Pin
Scott Dorman29-Aug-07 16:02
professionalScott Dorman29-Aug-07 16:02 
GeneralRe: Credit Card Test Numbers Pin
NormDroid30-Aug-07 0:50
professionalNormDroid30-Aug-07 0:50 
GeneralRe: Credit Card Test Numbers Pin
Scott Dorman30-Aug-07 10:49
professionalScott Dorman30-Aug-07 10:49 
GeneralRe: Credit Card Test Numbers Pin
Garth J Lancaster30-Aug-07 12:01
professionalGarth J Lancaster30-Aug-07 12:01 
GeneralRe: Credit Card Test Numbers Pin
BlueLaser0530-Aug-07 12:05
BlueLaser0530-Aug-07 12:05 
GeneralRe: Credit Card Test Numbers Pin
Garth J Lancaster30-Aug-07 12:12
professionalGarth J Lancaster30-Aug-07 12:12 
GeneralRe: Credit Card Test Numbers Pin
BlueLaser0530-Aug-07 7:26
BlueLaser0530-Aug-07 7:26 
GeneralRe: Credit Card Test Numbers Pin
Scott Dorman30-Aug-07 10:48
professionalScott Dorman30-Aug-07 10:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.