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

Credit Card Validator control for ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.79/5 (115 votes)
30 Aug 2002CPOL13 min read 860.3K   18.3K   283  
Article outlining how to create a credit card validator control fully derived from BaseValidator.
using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Text.RegularExpressions;

namespace Etier
{
	[Flags, Serializable]
	public enum CardType
	{
		MasterCard	= 0x0001,
		VISA		= 0x0002,
		Amex		= 0x0004,
		DinersClub	= 0x0008,
		enRoute		= 0x0010,
		Discover	= 0x0020,
		JCB			= 0x0040,
		Unknown		= 0x0080,
		All			= CardType.Amex | CardType.DinersClub | CardType.Discover | CardType.Discover |
			CardType.enRoute | CardType.JCB | CardType.MasterCard | CardType.VISA
	}
	
	/// <summary>
	/// Credit Card Validation web control for ASP.NET.
	/// </summary>
	public class CreditCardValidator : System.Web.UI.WebControls.BaseValidator
	{
		private CardType _cardTypes;
		private System.Web.UI.WebControls.TextBox _creditCardTextBox;
		private bool _validateCardType;

		public CreditCardValidator()
		{
			_validateCardType = true;		
			_cardTypes = CardType.All | CardType.Unknown;	// Accept everything
		}

		protected override bool ControlPropertiesValid()
		{
			// Should have a text box control to check
			Control ctrl = FindControl(ControlToValidate);
			
			if ( null != ctrl )
			{
				if (ctrl is System.Web.UI.WebControls.TextBox)	// ensure its a text box
				{
					_creditCardTextBox = (System.Web.UI.WebControls.TextBox) ctrl;	// set the member variable
					return ( null != _creditCardTextBox );		// check that it's been set ok
				} else
					return false;
			}
			else
				return false;
		}

		protected override bool EvaluateIsValid()
		{
			if (_validateCardType)	// should the length be validated also?
			{
				// Check the length, if the length is fine then validate the card number
				if (IsValidCardType(_creditCardTextBox.Text))
					return ValidateCardNumber( _creditCardTextBox.Text );
				else
					return false;		// Invalid length
			}
			else
				// Check that the text box contains a valid number using the ValidateCardNumber method
				return ValidateCardNumber( _creditCardTextBox.Text );
		}

		/// <summary>
		/// Will ensure that the card is a valid length for the card type. If the
		/// card type isn't recognised it will return true by default.
		/// 
		/// The CreditCardValidator control also includes a CardTypes property that determines
		/// what card types should be accepted. If the card isn't recognised, and the Unknown card
		/// type is in the AcceptedCardTypes property then the DefaultLengthTest value will be
		/// returned.
		/// </summary>
		public bool IsValidCardType( string cardNumber )
		{	
			// AMEX -- 34 or 37 -- 15 length
			if ( (Regex.IsMatch(cardNumber,"^(34|37)")) && ((_cardTypes & CardType.Amex)!=0) )
				return (15==cardNumber.Length);
			
				// MasterCard -- 51 through 55 -- 16 length
			else if ( (Regex.IsMatch(cardNumber,"^(51|52|53|54|55)")) && ((_cardTypes & CardType.MasterCard)!=0) )
				return (16==cardNumber.Length);

				// VISA -- 4 -- 13 and 16 length
			else if ( (Regex.IsMatch(cardNumber,"^(4)")) && ((_cardTypes & CardType.VISA)!=0) )
				return (13==cardNumber.Length||16==cardNumber.Length);

				// Diners Club -- 300-305, 36 or 38 -- 14 length
			else if ( (Regex.IsMatch(cardNumber,"^(300|301|302|303|304|305|36|38)")) && ((_cardTypes & CardType.DinersClub)!=0) )
				return (14==cardNumber.Length);

				// enRoute -- 2014,2149 -- 15 length
			else if ( (Regex.IsMatch(cardNumber,"^(2014|2149)")) && ((_cardTypes & CardType.DinersClub)!=0) )
				return (15==cardNumber.Length);

				// Discover -- 6011 -- 16 length
			else if ( (Regex.IsMatch(cardNumber,"^(6011)")) && ((_cardTypes & CardType.Discover)!=0) )
				return (16==cardNumber.Length);

				// JCB -- 3 -- 16 length
			else if ( (Regex.IsMatch(cardNumber,"^(3)")) && ((_cardTypes & CardType.JCB)!=0) )
				return (16==cardNumber.Length);

				// JCB -- 2131, 1800 -- 15 length
			else if ( (Regex.IsMatch(cardNumber,"^(2131|1800)")) && ((_cardTypes & CardType.JCB)!=0) )
				return (15==cardNumber.Length);
			else
			{
				// Card type wasn't recognised, provided Unknown is in the CardTypes property, then
				// return true, otherwise return false.
				if ( (_cardTypes & CardType.Unknown)!=0 )
					return true;
				else
					return false;
			}
		}

		/// <summary>
		/// Performs a validation using Luhn's Formula.
		/// </summary>
		private static bool ValidateCardNumber( string cardNumber )
		{
			try 
			{
				// Array to contain individual numbers
				System.Collections.ArrayList CheckNumbers = new ArrayList();

				// So, get length of card
				int CardLength = cardNumber.Length;
	
				// Double the value of alternate digits, starting with the second digit
				// from the right, i.e. back to front.

				// Loop through starting at the end
				for (int i = CardLength-2; i >= 0; i = i - 2)
				{
					// Now read the contents at each index, this
					// can then be stored as an array of integers

					// Double the number returned
					CheckNumbers.Add( Int32.Parse(cardNumber[i].ToString())*2 );
				}

				int CheckSum = 0;	// Will hold the total sum of all checksum digits
				
				// Second stage, add separate digits of all products
				for (int iCount = 0; iCount <= CheckNumbers.Count-1; iCount++)
				{
					int _count = 0;	// will hold the sum of the digits

					// determine if current number has more than one digit
					if ((int)CheckNumbers[iCount] > 9)
					{
						int _numLength = ((int)CheckNumbers[iCount]).ToString().Length;
						// add count to each digit
						for (int x = 0; x < _numLength; x++)
						{
							_count = _count + Int32.Parse( ((int)CheckNumbers[iCount]).ToString()[x].ToString() );
						}
					}
					else
					{
						_count = (int)CheckNumbers[iCount];	// single digit, just add it by itself
					}

					CheckSum = CheckSum + _count;	// add sum to the total sum
				}

				// Stage 3, add the unaffected digits
				// Add all the digits that we didn't double still starting from the right
				// but this time we'll start from the rightmost number with alternating digits
				int OriginalSum = 0;
				for (int y = CardLength-1; y >= 0; y = y - 2)
				{
					OriginalSum = OriginalSum + Int32.Parse(cardNumber[y].ToString());
				}

				// Perform the final calculation, if the sum Mod 10 results in 0 then
				// it's valid, otherwise its false.
				return (((OriginalSum+CheckSum)%10)==0);
			}
			catch
			{
				return false;
			}
		}

		/// <summary>
		/// Should the length be validated also?
		/// </summary>
		public bool ValidateCardType
		{
			get
			{
				return _validateCardType;
			}
			set
			{
				_validateCardType = value;
			}
		}

		public string AcceptedCardTypes
		{
			get
			{
				return _cardTypes.ToString();
			}
			set
			{
				_cardTypes = (Etier.CardType) Enum.Parse(typeof(Etier.CardType), value, false );
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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 Kingdom United Kingdom
I graduated with a first class BSc honours degree in a Computer Science/Management Science hybrid Smile | :) from Loughborough University. I live in London and currently work as a .NET Developer in central London.

I'm also currently blogging my progress at further developing my articles into commercial components. Read my blog here.

I've also recently started dabbling at digital photography, and digital re-touching, and developing small simple multiplayer games in Flash.

Comments and Discussions