The Luhn's algorithm in practice

Serhii Korol - Sep 11 - - Dev Community

Hi folks, today, I would like to discuss what you probably know. You all used payment systems or paid at Internet shops. On the purchase page, you used to enter your card details. There exists card validation. If you enter a non-valid card number, you get an error. The card numbers are using Luhn's algorithm for validation of invalid cards. This algorithm is the topic of my article. Later, I'll show you how it works in practice.

History.

Luhn's algorithm is aka "mod 10" due to his appearance as Hans Peter Luhn. It is a simple check-digit formula used to validate various identification numbers. It was invented in 1954 and designed to protect against accidental errors, not malicious attacks. Most credit cards and many government identification numbers use the algorithm to distinguish valid numbers from mistyped or otherwise incorrect numbers.

Hans Peter Luhn

Princip of work

Let's consider the example of a credit card.

luhn

We have the credit card number with 16 digits. The last digit is the control number. You must conditionally split the card number into pairs to check this number. Next, you should each first digit multiply on two. In the following step, for all numbers that are more than 9, you should summarize digits that would get one digit. You also should sum the digits with the rest. In our case, the sum is 80. It's a valid number. You can ask why I highlighted the control number. It checks that the rest of the sum digits are valid. The sum without a control number is 76. We can calculate the control number by this formula:
(10 - (x mod 10)), where x - calculated sum of 15 digits.
If you perform it, you get (10 - (76 mod 10)) = 4
As you can see, the card number is valid. If you change the last digit from 4 to 3, the other digits should have different values.

Practice

Let's create a simple MAUI project. In the MainPage.xaml file, paste this code:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CardValidator.MainPage">

    <ContentPage.Content>
        <VerticalStackLayout Padding="20" Spacing="20" BackgroundColor="White" VerticalOptions="Start" HorizontalOptions="Center">

            <!-- Credit Card Frame -->
            <Border StrokeShape="RoundRectangle 20" BackgroundColor="#4B0082" HeightRequest="200" WidthRequest="350" HorizontalOptions="Center">
                <Grid Padding="10">

                    <!-- Card Logo -->
                    <Image Source="visa.jpg" Aspect="AspectFit" HeightRequest="30" WidthRequest="60" HorizontalOptions="End" VerticalOptions="Start"/>

                    <!-- Card Number Entries -->
                    <Grid ColumnSpacing="10" HorizontalOptions="Center" VerticalOptions="Center">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>

                        <Entry Grid.Column="0" x:Name="CardNumberEntry1"
                               Placeholder="1234"
                               PlaceholderColor="#6D6D6B"
                               Keyboard="Numeric"
                               MaxLength="4"
                               FontSize="20"
                               TextColor="White"
                               BackgroundColor="#6A0D91"
                               WidthRequest="70"
                               Margin="0, 0, 5, 0"
                               HorizontalTextAlignment="Center"
                               VerticalTextAlignment="Center"
                               TextChanged="OnCardNumberTextChanged"
                               Focused="OnEntryFocused"/>

                        <Entry Grid.Column="1" x:Name="CardNumberEntry2"
                               Placeholder="5678"
                               PlaceholderColor="#6D6D6B"
                               Keyboard="Numeric"
                               MaxLength="4"
                               FontSize="20"
                               TextColor="White"
                               BackgroundColor="#6A0D91"
                               WidthRequest="70"
                               Margin="0, 0, 5, 0"
                               HorizontalTextAlignment="Center"
                               VerticalTextAlignment="Center"
                               TextChanged="OnCardNumberTextChanged"
                               Focused="OnEntryFocused"/>

                        <Entry Grid.Column="2" x:Name="CardNumberEntry3"
                               Placeholder="9876"
                               PlaceholderColor="#6D6D6B"
                               Keyboard="Numeric"
                               MaxLength="4"
                               FontSize="20"
                               TextColor="White"
                               BackgroundColor="#6A0D91"
                               WidthRequest="70"
                               Margin="0, 0, 5, 0"
                               HorizontalTextAlignment="Center"
                               VerticalTextAlignment="Center"
                               TextChanged="OnCardNumberTextChanged"
                               Focused="OnEntryFocused"/>

                        <Entry Grid.Column="3" x:Name="CardNumberEntry4"
                               Placeholder="5432"
                               PlaceholderColor="#6D6D6B"
                               Keyboard="Numeric"
                               MaxLength="4"
                               FontSize="20"
                               TextColor="White"
                               BackgroundColor="#6A0D91"
                               WidthRequest="70"
                               HorizontalTextAlignment="Center"
                               VerticalTextAlignment="Center"
                               TextChanged="OnCardNumberTextChanged"
                               Focused="OnEntryFocused"/>
                    </Grid>

                    <!-- Cardholder Name -->
                    <Label Text="JOHN DOE" 
                           FontSize="16" 
                           TextColor="White" 
                           FontAttributes="Bold"
                           HorizontalOptions="Start" 
                           VerticalOptions="End"
                           Margin="10, 0, 0, 15"/>

                    <!-- Expiry Date -->
                    <Label Text="12/24" 
                           FontSize="16" 
                           TextColor="White"
                           HorizontalOptions="End" 
                           VerticalOptions="End"
                           Margin="0, 0, 10, 15"/>
                </Grid>
            </Border>

            <!-- Submit Button -->
            <Button Text="Submit"
                    BackgroundColor="#6A0D91"
                    TextColor="White"
                    FontSize="18"
                    CornerRadius="10"
                    HeightRequest="35"
                    Padding="10,0"
                    HorizontalOptions="Center"
                    Clicked="OnSubmitClicked" />
        </VerticalStackLayout>
    </ContentPage.Content>
</ContentPage>
Enter fullscreen mode Exit fullscreen mode

It's a simple layout. Don't forget to add any logo to the Resources/Images folder.

Next, add into MainPage.xaml.cs this code:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void OnSubmitClicked(object sender, EventArgs e)
    {
        string cardNumber1 = CardNumberEntry1.Text;
        string cardNumber2 = CardNumberEntry2.Text;
        string cardNumber3 = CardNumberEntry3.Text;
        string cardNumber4 = CardNumberEntry4.Text;

        string fullCardNumber = $"{cardNumber1}{cardNumber2}{cardNumber3}{cardNumber4}";

        if (fullCardNumber.Length == 16)
        {
            if (IsValidCardNumber(fullCardNumber))
            {
                DisplayAlert("Success", $"Card Number: {fullCardNumber} submitted.", "OK");
            }
            else
            {
                DisplayAlert("Error", "The card number is invalid.", "OK");
            }
        }
        else
        {
            DisplayAlert("Error", "Please enter all 16 digits of the card number.", "OK");
        }
    }

    private bool IsValidCardNumber(string cardNumber)
    {
        int sum = 0;
        bool alternate = false;

        for (int i = cardNumber.Length - 1; i >= 0; i--)
        {
            int n = int.Parse(cardNumber[i].ToString());

            if (alternate)
            {
                n *= 2;
                if (n > 9)
                {
                    n -= 9;
                }
            }

            sum += n;
            alternate = !alternate;
        }

        return (sum % 10 == 0);
    }

    private void OnEntryFocused(object sender, FocusEventArgs e)
    {
        if (sender is Entry entry)
        {
            entry.Text = string.Empty;
        }
    }

    private void OnCardNumberTextChanged(object sender, TextChangedEventArgs e)
    {
        if (sender is not Entry entry) return;

        if (entry.Text.Length != entry.MaxLength) return;

        if (entry == CardNumberEntry1)
        {
            CardNumberEntry2.Focus();
        }
        else if (entry == CardNumberEntry2)
        {
            CardNumberEntry3.Focus();
        }
        else if (entry == CardNumberEntry3)
        {
            CardNumberEntry4.Focus();
        }
        else if (entry == CardNumberEntry4)
        {
            CardNumberEntry4.Unfocus();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Let's go to this method:

private bool IsValidCardNumber(string cardNumber)
    {
        int sum = 0;
        bool alternate = false;

        for (int i = cardNumber.Length - 1; i >= 0; i--)
        {
            int n = int.Parse(cardNumber[i].ToString());

            if (alternate)
            {
                n *= 2;
                if (n > 9)
                {
                    n -= 9;
                }
            }

            sum += n;
            alternate = !alternate;
        }

        return (sum % 10 == 0);
    }
Enter fullscreen mode Exit fullscreen mode

We parse the card number length and iterate all digits from right to left. In other words, we multiply every second digit, and if it is more than 9, we subtract 9.
Let's check this out.

valid

You get an error if you change the control number from 4 to 3.

ivalid

Now, you also know how credit card validation works under the hood. I hope this article is helpful for you. See you next week, and happy coding.

The source code.

Buy Me A Beer

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .