LUHN Validation for ASP.NET (Web Forms and MVC)

In this post, I thought I'd look at a complete server-side and client-side solution for validating credit card numbers using the LUHN algorithm for both ASP.NET Web Forms and MVC. The core code uses some nice LINQ that I have previously blogged about, but thought it was worth providing a complete example to demonstrate how to leverage the validation infrastructure in the various ASP.NET technologies.

LUHN Algorithm

The LUHN algorithm is a popular way to validate credit card numbers. I’ve used it many times while developing e-commerce applications to check that a user has entered their credit card number correctly. By using the LUHN algorithm to verify a card number, you can let a customer know their card number is invalid before taking payment through a gateway. After all, it’s a better user experience if they don’t have to wait for the server to try and authorize their card through a payment gateway with incorrect details that could have been detected using a simple LUHN check!

Yes that’s right, the LUHN algorithm is very simple, despite a lot of example code making it look hard to implement!

The LUHN formula produces a checksum that can be used to determine if a credit card number is valid. It’s not bullet proof, but it is good at catching genuine user input errors.

The LUHN checksum is calculated by first reversing the credit card number. Secondly, in the reversed number every other digit is doubled. This means the first digit remains the same, the second digit is doubled, the third digit stays the same, the fourth is doubled, etc. This leaves you with a set of numbers some of which will be two digit numbers, some single digit numbers. Thirdly, take all the digits in the set of numbers from the second step and add them up (you will treat each digit in the two digit numbers as two single digits when adding everything up). Finally, take the sum module 10 and this gives the LUHN checksum.

Take 12345678 as an example. First: we reverse it to give 87654321. Second: every other digit starting with the second digit is doubled giving: (8), (14), (6), (10), (4), (6), (2) and (2). Third: we add each digit together: 8 + 1 + 4 + 6 + 1 + 0 + 4 + 6 + 2 + 2 to give 34. Last: we calculate 34 % 10, which means the checksum is equal to 4.

The final thing to note with the LUHN algorithm is that valid credit card numbers have a checksum of zero.

LUHN algorithm in C# and LINQ

This LUHN utility class provides a robust and complete means of checking the validity of a credit card number using LINQ. It uses several maths tricks and properties of the LUHN formula to reduce the LINQ statement down to two steps.

public static class LuhnUtility
{
   public static bool IsCardNumberValid(string cardNumber, bool allowSpaces = false)
   {
      if (allowSpaces)
      {
         cardNumber = cardNumber.Replace(" ", "");
      }

      if (cardNumber.Any(c => !Char.IsDigit(c)))
      {
         return false;
      }

      int checksum = cardNumber
         .Select((c, i) => (c - '0') << ((cardNumber.Length - i - 1) & 1))
         .Sum(n => n > 9 ? n - 9 : n);

      return (checksum % 10) == 0 && checksum > 0;
   }
}

The utility also optionally allows spaces as some payment gateways are tolerant of spaces and some users like to put spaces when entering their card details.

The method correctly rejects non-numeric input and rogue characters. The two guards in the prevent non-numeric digits from accidentally validating and this also means that I can make the LINQ statement quite optimised because by the time the LINQ statement executes, the string is guaranteed to consist only of digits.

LUHN algorithm in JavaScript

If we want to roll the LINQ and C# implementation into some validation logic for ASP.NET Web Forms or MVC that supports client-side validation we also need a JavaScript version of the LUHN formula:

function isCardNumberValid(cardNumber, allowSpaces) {
   if (allowSpaces) {
      cardNumber = cardNumber.replace(/ /g, '');
   }

   if (!cardNumber.match(/^\d+$/)) {
      return false;
   }

   var checksum = 0;

   for (var i = 0; i < cardNumber.length; i++) {
      var n = (cardNumber.charAt(cardNumber.length - i - 1) - '0') << (i & 1);

      checksum += n > 9 ? n - 9 : n;
   }

   return (checksum % 10) == 0 && checksum > 0;
}

This JavaScript code is referred to as LuhnUtility.js in the example code below.

LUHN Validator for ASP.NET Web Forms

Once we have the basic algorithms coded in C# and JavaScript it's pretty easy to create a validator control for ASP.NET Web Forms:

public sealed class LuhnValidator : BaseValidator
{
   public bool AllowSpaces
   {
      get
      {
         return (bool)(ViewState["AllowSpaces"] ?? false);
      }
      set
      {
         ViewState["AllowSpaces"] = value;
      }
   }

   public bool AllowEmpty
   {
      get
      {
         return (bool)(ViewState["AllowEmpty"] ?? false);
      }
      set
      {
         ViewState["AllowEmpty"] = value;
      }
   }

   protected override bool EvaluateIsValid()
   {
      string value = GetControlValidationValue(ControlToValidate);

      if (AllowEmpty && String.IsNullOrEmpty(value))
      {
         return true;
      }

      return LuhnUtility.IsCardNumberValid(value, AllowSpaces);
   }

   protected override void OnPreRender(EventArgs e)
   {
      base.OnPreRender(e);

      if (RenderUplevel)
      {
         RegisterProperty("evaluationfunction", "luhnValidatorEvaluateIsValid");

         if (AllowSpaces)
         {
            RegisterProperty("evaluationspaces", "true");
         }

         if (AllowEmpty)
         {
            RegisterProperty("evaluationempty", "true");
         }

         RegisterScript("~/LuhnUtility.js");
         RegisterScript("~/LuhnValidator.js");
      }
   }

   private void RegisterProperty(string name, string value)
   {
      ScriptManager.RegisterExpandoAttribute(this, ClientID, name, value, true);
   }

   private void RegisterScript(string url)
   {
      ScriptManager.RegisterClientScriptInclude(this, GetType(), url, ResolveUrl(url));
   }
}

The JavaScript code for LuhnValidator.js is:

function luhnValidatorEvaluateIsValid(validator) {
   var value = ValidatorGetValue(validator.controltovalidate);

   if (value.length < 1 && validator.evaluationempty == "true") {
      return true;
   }

   return isCardNumberValid(value, validator.evaluationspaces == "true");
}

To use this in an ASP.NET page you would wire up the validation to a text box like so:

<asp:TextBox ID="CardNumberTextBox" runat="server" />

<ssc:LuhnValidator runat="server" ControlToValidate="CardNumberTextBox" ErrorMessage="*"
   Text="Please enter a valid credit card number" />

LUHN Validation in ASP.NET MVC 3

To perform model validation in MVC requires a custom validation attribute:

public class LuhnAttribute : ValidationAttribute, IClientValidatable
{
   public bool AllowSpaces { get; set; }
   public bool AllowEmpty { get; set; }

   public override bool IsValid(object value)
   {
      string cardNumber = (string)value;

      if (String.IsNullOrEmpty(cardNumber))
      {
         return AllowEmpty;
      }

      return LuhnUtility.IsCardNumberValid(cardNumber, AllowSpaces);
   }

   public IEnumerable<ModelClientValidationRule>
      GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
   {
      yield return new LuhnRule(ErrorMessage, AllowSpaces, AllowEmpty);
   }

   class LuhnRule : ModelClientValidationRule
   {
      public LuhnRule(string errorMessage, bool allowSpaces, bool allowEmpty)
      {
         ErrorMessage = errorMessage;
         ValidationType = "luhn";

         ValidationParameters["allowspaces"] = allowSpaces;
         ValidationParameters["allowempty"] = allowEmpty;
      }
   }
}

This attribute can then be placed on a model like this for instant validation:

public class PaymentModel
{
   [Luhn(ErrorMessage="Please enter a valid card number", AllowSpaces=false)]
   public string CardNumber { get; set; }
}

An example of the accompanying Razor view would be:

@using (Html.BeginForm())
{
   <div>
      @Html.TextBoxFor(m => m.CardNumber)
      @Html.ValidationMessageFor(m => m.CardNumber)

      <input type="submit" />
   </div>
}

Unobtrusive JavaScript for MVC

Here's the unobtrusive jQuery JavaScript adapater:

(function ($) {
   $.validator.addMethod("luhn", function (value, element, params) {
      if (value.length < 1 && params.allowEmpty) {
         return true;
      }

      return isCardNumberValid(value, params.allowSpaces);
   });

   $.validator.unobtrusive.adapters.add("luhn", ["allowempty", "allowspaces"], function (options) {
       options.rules['luhn'] = {
          allowEmpty: options.params.allowempty == "True",
          allowSpaces: options.params.allowspaces == "True"
       };

       options.messages['luhn'] = options.message;
    });
} (jQuery));

Note you will need to include the following client-side libraries for this to work:

<script src="@Url.Content("~/scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript">
</script>

To enable unobtrusive client-side validation in MVC 3 you need to ensure the following configuration settings are enabled:

<appSettings>
   <add key="ClientValidationEnabled" value="true"/>
   <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>

Standard JavaScript for MVC

Here's the normal MVC validation JavaScript - in case you want to turn off unobtrusive validation:

Sys.Mvc.ValidatorRegistry.validators["luhn"] = function (rule) {
   var allowEmpty = rule.ValidationParameters.allowempty;
   var allowSpaces = rule.ValidationParameters.allowspaces;

   return function (value, context) {
      if (value.length < 1 && allowEmpty) {
         return true;
      }

      return isCardNumberValid(value, allowSpaces);
   };
};

Note you will need to include the following client-side libraries for this to work:

<script src="@Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>

And finally to turn on normal client-side validation in MVC:

<appSettings>
   <add key="ClientValidationEnabled" value="true"/>
   <add key="UnobtrusiveJavaScriptEnabled" value="false"/>
</appSettings>