Validating UK Credit & Debit Cards With LUHN to Type

Validating UK Credit & Debit Cards With LUHN to Type

Note: The regex here is pretty worthless; bin's are listed as ranges and should be handled as such, at the time of writing the ranges were relatively limited and this LUCKILY didn't cause any issues

Credit Card validation is a Pain in the Arse *Updated April 2010
But its not going anywhere fast. So I’ve combined two of the scripts I’ve found to meet my needs, added UK credit card data (Maestro/VisaDebit etal) and a bit of optimization and here it is.

Algorithm Sources:

Mostly data entry but it should save someones fingers and headache.

Change the Namespace name to fit your project; the original was written for ASP.net 2; this was used as a helper in C# ASP.net 3.5 MVC

using System;
using System.Text.RegularExpressions;

namespace Helpers
{
    /// <summary>
    /// Credit Card Validation Utility (CreditCardUtility)
    /// </summary>
    public static class CreditCardUtility
    {
        // MAESTRO/SOLO: (?<Maestro>(?:5018|5020|5038|6304|6759|6761|6763|6334|6767|4903|4905|4911|4936|564182|633110|6333|6759)d{8,15})
        // MAESTRO: (?<Maestro>5018)|(?<Maestro>5020)|(?<Maestro>5038)|(?<Maestro>6304)|(?<Maestro>6759)|(?<Maestro>6767)
        // MAESTRO INTERNATIONAL:
        // SOLO: (?<Maestro>6334)|(?<Maestro>6767)|(?<Maestro>4903)|(?<Maestro>4905)|(?<Maestro>4911)|(?<Maestro>4936)|(?<Maestro>564182)|(?<Maestro>633110)|(?<Maestro>6333)|(?<Maestro>6759)
        // VISA DEBIT: (?<VisaDebit>(?:400626|40854749|40940002|41228586|41373337|41378788|418760|41917679|419772|420672|42159294|422793|423769|431072|444001|44400508|44620011|44621354|44625772|44627483|446286|446294|450875|45397|454313|45443235|454742|45672545|46583079|46590150|47511059|47571059|47622069|47634089|48440910|484427|49096079|49218182))
        // VISA CREDIT: (?<Visa>4d{3})
        // MASTERCARD CREDIT: (?<MasterCard>5[1-5]d{2})
        // MASTERCARD DEBIT: "(?(?<MasterCardDebit>(?:516730|516979|517000|517049|535110|535309|535420|535819|537210|537609|557347|557496|557498|557547))|" +
        // AMEX: (?<Amex>3[47]d{2}))([ -]?)(?(DinersClub)(?:d{6}1d{4})|(?(Amex)(?:d{6}1d{5})|(?:d{4}1d{4}1d{4}))

        /// <summary>
        /// Credit Card Start Code REGEX Check
        /// </summary>
        private const string CardRegex = "^(?:(?<Maestro>(?:5018|5020|5038|6304|6759|6761|6763|6334|6767|4903|4905|4911|4936|564182|633110|6333|6759))|" +
            "(?<VisaDebit>(?:456735|400626|40854749|40940002|41228586|41373337|41378788|418760|41917679|419772|420672|42159294|422793|423769|431072|444001|44400508|44620011|44621354|44625772|44627483|446286|446294|450875|45397879|454313|45443235|454742|45672545|46583079|46590150|47511059|47571059|47622069|47634089|48440910|484427|49096079|49218182|400115|40083739|41292123|417935|419740|419741|41977376|424519|4249623|444000|48440608|48441126|48442855|49173059|49173000|491880))|" +
            "(?<Visa>4d{3})|" +
            "(?<MasterCardDebit>(?:516730|516979|517000|517049|535110|535309|535420|535819|537210|537609|557347|557496|557498|557547))|" +
            "(?<MasterCard>5[1-5]d{2})|" +
            "(?<Amex>3[47]d{2}))([ -]?)(?(DinersClub)(?:d{6}1d{4})|(?(Amex)(?:d{6}1d{5})|(?:(?:d{8,15})|(?:d{4}1d{4}1d{4}))))$";

        /// <summary>
        /// IsValidNumber handles the raw card code without a type
        /// </summary>
        /// <param name="cardNum"></param>
        /// <returns></returns>
        public static bool IsValidNumber(string cardNum)
        {
            //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
            return IsValidNumber(cardNum, cardType) ? true : false;
        }

        /// <summary>
        /// IsValidNumber handles the raw card code with type
        /// </summary>
        /// <param name="cardNum"></param>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static bool IsValidNumber(string cardNum, CreditCardTypeType? cardType)
        {
            //Create new instance of Regex comparer with our 
            //credit card regex pattern
            var 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
                return PassesLuhnTest(cardNum) ? true : false;
            }
            else
                //The card number does not match the card type
                return false;
        }

        /// <summary>
        /// Test CVV Code 
        /// </summary>
        /// <param name="cvvCode"></param>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static bool IsValidCvvCode(string cvvCode, CreditCardTypeType? cardType)
        {
            int digits = 0;
            switch (cardType)
            {
                case CreditCardTypeType.MasterCard:
                    digits = 3;
                    break;
                case CreditCardTypeType.MasterCardDebit:
                    digits = 3;
                    break;
                case CreditCardTypeType.VisaDebit:
                    digits = 3;
                    break;
                case CreditCardTypeType.Visa:
                    digits = 3;
                    break;
                //case "DISCOVER":
                //case "EUROCARD":
                case CreditCardTypeType.Maestro:
                case CreditCardTypeType.Amex:
                    digits = 4;
                    break;
                default:
                    return false;
            }
            var regEx = new Regex("[0-9]{" + digits + "}");
            return (cvvCode.Length == digits && regEx.Match(cvvCode).Success);
        }

        /// <summary>
        /// SplitNameOnCard
        /// </summary>
        /// <param name="nameOnCard"></param>
        /// <returns></returns>
        public static string[] SplitNameOnCard(string nameOnCard)
        {
            //Nasty
            var returnedSplitNameOnCard = new string[3];
            string[] splitNameOnCard = nameOnCard.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            switch (splitNameOnCard.Length)
            {
                case 1:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[0];
                    returnedSplitNameOnCard[2] = splitNameOnCard[0];
                    break;
                case 2:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[1];
                    returnedSplitNameOnCard[2] = splitNameOnCard[0];
                    break;
                default:
                    returnedSplitNameOnCard[0] = splitNameOnCard[0];
                    returnedSplitNameOnCard[1] = splitNameOnCard[1];
                    returnedSplitNameOnCard[2] = splitNameOnCard[2];
                    break;
            }
            return returnedSplitNameOnCard;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mmyyString"></param>
        /// <returns></returns>
        public static string PadMMYYString(string mmyyString)
        {
            string[] mmyySplit = mmyyString.Split(new[] { '/' });
            for (int i = 0; i < mmyySplit.Length; i++)
            {
                mmyySplit[i] = string.Format("{0,2}", mmyySplit[i]).Replace(" ", "0");
            }
            return String.Join(null, mmyySplit);
        }

        public static CreditCardTypeType? GetCardTypeFromNumber(string cardNum)
        {
            //Create new instance of Regex comparer with our
            //credit card regex pattern
            var 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.MasterCardDebit.ToString()].Success)
            {
                return CreditCardTypeType.MasterCardDebit;
            }
            else if (gc[CreditCardTypeType.MasterCard.ToString()].Success)
            {
                return CreditCardTypeType.MasterCard;
            }
            else if (gc[CreditCardTypeType.VisaDebit.ToString()].Success)
            {
                return CreditCardTypeType.VisaDebit;
            }
            else if (gc[CreditCardTypeType.Visa.ToString()].Success)
            {
                return CreditCardTypeType.Visa;
            }
            else if (gc[CreditCardTypeType.Maestro.ToString()].Success)
            {
                return CreditCardTypeType.Maestro;
            }
            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;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cardTypeCode"></param>
        /// <returns></returns>
        public static CreditCardTypeType? GetCardTypeFromCardTypeCode(string cardTypeCode)
        {
            switch (cardTypeCode)
            {
                case "SW":
                case "SD":
                    return CreditCardTypeType.Maestro;
                case "MasterCard":
                case "MC":
                    return CreditCardTypeType.MasterCard;
                case "MD":
                    return CreditCardTypeType.MasterCardDebit;
                case "VISA":
                case "VI":
                    return CreditCardTypeType.Visa;
                case "VD":
                    return CreditCardTypeType.VisaDebit;
                case "AM":
                    return CreditCardTypeType.Amex;
                //case "BC":
                //    return CreditCardTypeType.BarclaysConnect;
                default:
                    //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;
            }
        }

        /// <summary>
        /// Debugging Tool
        /// </summary>
        /// <param name="cardType"></param>
        /// <returns></returns>
        public static string GetCardTestNumber(CreditCardTypeType cardType)
        {
            //Test Numbers from PayPal
            //for testing card transactions are:
            //Credit Card Type              Credit Card Number
            //*American Express             378282246310005
            //*American Express             371449635398431
            //American Express Corporate    378734493671000
            //Diners Club                   30569309025904
            //Diners Club                   38520000023237
            //Discover                      6011111111111117
            //Discover                      6011000990139424
            //*MasterCard                   5555555555554444
            //*MasterCard                   5105105105105100
            //*Visa Credit                  4111111111111111
            //*Visa Debit                   4012888888881881
            //*Maestro (switch/solo)        6331101999990016
            //Src: https://www.paypal.com/en_GB/vhelp/paypalmanager_help/credit_card_numbers.htm
            //Return bogus CC number that passes Luhn and format tests
            switch (cardType)
            {
                case CreditCardTypeType.Amex:
                    return "3782 822463 10005";
                case CreditCardTypeType.MasterCard:
                    return "5105 1051 0510 5100";
                case CreditCardTypeType.MasterCardDebit:
                    return "5105 1051 0510 5100";
                case CreditCardTypeType.Visa:
                    return "4111 1111 1111 1111";
                case CreditCardTypeType.VisaDebit:
                    return "4917 4800 0000 0008";
                case CreditCardTypeType.Maestro:
                    return "6331 1019 9999 0016";
                default:
                    return null;
            }
        }

        /// <summary>
        /// LUHN Algorithm Card Number Test
        /// If Mod 10 equals 0, the number is good and this will return true
        /// </summary>
        /// <param name="cardNumber"></param>
        /// <returns></returns>
        private static bool PassesLuhnTest(string cardNumber)
        {
            var deltas = new[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 0 };
            int checksum = 0;
            char[] chars = new Regex(@"[^d]").Replace(cardNumber, "").ToCharArray(); //Extract digits
            for (int i = chars.Length - 1; i > -1; i--)
            {
                int j = (chars[i]) - 48;
                checksum += j;
                if (((i - chars.Length) % 2) == 0)
                    checksum += deltas[j];
            }
            return ((checksum % 10) == 0);
        }
    }

    /// <summary>
    /// CreditCardTypeType copied for PayPal WebPayment Pro API
    /// (If you use the PayPal API, you do not need this definition)
    /// </summary>
    public enum CreditCardTypeType
    {
        Visa,
        VisaDebit,
        MasterCard,
        MasterCardDebit,
        Amex,
        Maestro
    }
}

A test controller (CreditCardTestController.cs)…

using System;
using System.Text;
using System.Web.Mvc;
using Helpers;

namespace Controllers
{
    public class CreditCardTestController : Controller
    {
        // GET: /CreditCardTest/?card=number

        public ActionResult Index(string card)
        {
            string cardNum = card;
            var sb = new StringBuilder("TEST Credit Card<br/>");

            if (CreditCardUtility.IsValidNumber(cardNum))
            {
                CreditCardTypeType? cardType = CreditCardUtility.GetCardTypeFromNumber(cardNum);
                string strCardType = (cardType == null) ? "Unknown" : cardType.ToString();

                sb.Append(String.Format("You have entered a valid card number. The card type is {0}.", strCardType));
            }
            else
            {
                CreditCardTypeType? cardType = CreditCardUtility.GetCardTypeFromNumber(cardNum);
                string strCardType = (cardType == null) ? "Unknown" : cardType.ToString();

                sb.Append(String.Format("You have entered an invalid card number. <br />The card type is {0}.",
                                        strCardType));
            }


            Response.Write(sb.ToString());
            return null;
        }
    }
}
Chris McKee

Chris McKee

https://chrismckee.co.uk

Software Engineer, Web Front/Backend/Architecture; all-round tech obsessed geek. I hate unnecessary optimism