Validating UK Credit & Debit Cards With LUHN to Type
March 9, 2010Credit 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:
* http://www.codeproject.com/KB/aspnet/UltimateCreditCardUtility.aspx
* http://orb-of-knowledge.blogspot.com/2009/08/extremely-fast-luhn-function-for-c.html
* Extra Code monkey @ the start: Joe Bragg
Data Sources
* The Identifying BIN Numbers Change every so often so heres the only real decent UK resource for this…
* http://www.barclaycard.co.uk/business/existing-customers/ (select Latest BIN range for point of sale equipment) then the pdf
* Download Example MVC ProjectMostly 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
Code Updated 09-03-2010 (BIN from Feb 2010)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>4\\d{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}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})|(?:\\d{4}\\1\\d{4}\\1\\d{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>4\\d{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}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})|(?:(?:\\d{8,15})|(?:\\d{4}\\1\\d{4}\\1\\d{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; } } }
Enjoy
Download Example MVC Project
You’ll need to add 456735 after visadebit in the download, Barclaycard seem to be omitting it from their BIN file but it exists
Handy Tools/Links
- Google Credit Cards: http://code.google.com/apis/checkout/developer/index.html#credit_card_authorization
- Paypal Test Cards: https://www.paypal.com/en_GB/vhelp/paypalmanager_help/paypalmanager.htm#credit_card_numbers.htm
- Credit Card REGEX Explained in plain English http://converter.telerik.com/downloads/CreditCardRegEx.html
- Wikipedia Credit Card Numbers http://en.wikipedia.org/wiki/Credit_card_number

4 Responses to “ Validating UK Credit & Debit Cards With LUHN to Type ”
hey Chris,
Just thought I'd say firstly, very nicely done - this will indeed save headaches.
I'm sitting here with a visa debit card in front of me with the prefix 4539 so it may be worth adding that (I shall do so to the code that I've downloaded).
It's incredible that there are so few decent sources of regexes for this sort of thing - one would think it should be a relatively straight forward process!
I also found: http://en.wikipedia.org/wiki/Credit_card_numbers though didn't find that helped much at all (it claims anything that isn't visa electron is visa credit, which isn't true, though their regex's/prefixes again don't cover my local scenario (4539 prefix + debit card).
Cheers,
Terry
@Terry: I'll be updating the regex soon enough as I have a full UK BIN table (including the card you've got).
It appears that you've put a good amount of effort into your article and I want a lot more of these on the web these days. I truly got a kick out of your post. I do not have a bunch to to say in response, I only wanted to register to say marvellous work.
Killer blog! Nice one, I'll be back for more.
Leave a Reply