Generation and validation of numbers using the Luhn algorithm

Luna algorithm (Luhn algorithm) is the process of calculating a check digit for a number in accordance with ISO/IEC 7812. The process itself is not a cryptographic tool and does not protect the data contained in this number in any way. It is intended primarily to detect errors caused by unintentional data corruption. For example, when manually entering the card number or any other number. This algorithm makes it possible to judge with some degree of certainty that there are no errors in the block of digits, but cannot correct them in any way.

The algorithm was developed by IBM employee Hans Peter Lun in 1954 and patented in 1960. Most often, this algorithm is used when generating the numbers of all bank cards, some discount card numbers, social security codes, IMEI codes, numbers of Russian Railways railway cars, unique serial numbers of SIM cards (ICCID) and in other cases.

Number check

To check, you need to sort through the numbers from right to left and multiply every second by 2. If the resulting product is greater than or equal to 10, subtract 9 from the result obtained. Then add the obtained values ​​with all odd numbers and, if the sum is divisible by 10 without a remainder, then the number entered correctly.

Consider the correct number “5062 8212 3456 7892”:

5 0 6 2 8 2 1 2 3 4 5 6 7 8 9 2
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   // умножаем каждое второе число на 2
10  12  16  2   6   10  14  18
↓   ↓   ↓           ↓   ↓   ↓
1   3   7           1   5   9   // от чисел свыше 9 отнимаем 9
1 0 3 2 7 2 2 2 6 4 1 6 5 8 9 2 // берём все нечётные цифры
                                // и полученный результат чётных
1+0+3+2+7+2+2+2+6+4+1+6+5+8+9+2 = 60

In the end, we get the sum equal to 60. This number is divisible by 10 without a remainder, which means that the number is entered correctly.

Now let’s check the incorrect number “5062 8217 3456 7892”:

5 0 6 2 8 2 1 7 3 4 5 6 7 8 9 2
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   // умножаем каждое второе число на 2
10  12  16  2   6   10  14  18
↓   ↓   ↓           ↓   ↓   ↓
1   3   7           1   5   9   // от чисел свыше 9 отнимаем 9
1 0 3 2 7 2 2 7 6 4 1 6 5 8 9 2 // берём все нечётные цифры
                                // и полученный результат чётных
1+0+3+2+7+2+2+7+6+4+1+6+5+8+9+2 = 65

Thus, we received information that the initial number was written incorrectly, which means that it is necessary to check the order of the digits and correct the error.

Batch solution in PHP

There are many batch solutions for checking a number using the Luhn algorithm, but rarely does anyone offer the generation of such numbers, and so the project was born The Dragon Code: Card Number.

This project allows not only to check any numbers using the Luhn algorithm, but also to generate them, for example, for use in a customer loyalty program.

Installation

The easiest way to install this product is to use Composer:

composer require dragon-code/card-number

And right after that, you can start working.

Validation

You can check absolutely any number and in any format. For example:

use DragonCode\CardNumber\CardNumber;

CardNumber::isValid(18); // true
CardNumber::isValid(12); // false

CardNumber::isValid('0018'); // true
CardNumber::isValid('0019'); // false

CardNumber::isValid('123-455'); // true
CardNumber::isValid('123-454'); // false

CardNumber::isValid('12-3456-1239'); // true
CardNumber::isValid('12-3456-1230'); // false

CardNumber::isValid('5580 4733 7202 4733'); // true
CardNumber::isValid('5580 4733 7202 4732'); // false

CardNumber::isValid('5580-4733x7202_47 33'); // true
CardNumber::isValid('5580-4733x7202_47 32'); // false

In addition, in addition to the validity of the number itself, you can also check whether the number belongs to any type of bank cards. For example:

use DragonCode\CardNumber\CardNumber;
use DragonCode\CardNumber\Enums\CardType;

CardNumber::isValid('4026 8434 8316 8683', CardType::visa); // true
CardNumber::isValid('2730 1684 6416 1841', CardType::visa); // false

CardNumber::isValid('4026 8434 8316 8683', 'visa'); // true
CardNumber::isValid('2730 1684 6416 1841', 'visa'); // false

Currently, the project contains validators for the numbers of the following types of cards: AmericanExpress, Dankort, DinersClub, Discovery, Forbrugsforeningen, HiperCard, JCB, Maestro, MasterCard, MIR, Troy, UnionPay, VISA and VISA Electron.

Number generation

To generate a number, for example, by client ID, you can call the following code:

use DragonCode\CardNumber\CardNumber;

CardNumber::generate(1);   // 18
CardNumber::generate(2);   // 26
CardNumber::generate(10);  // 109
CardNumber::generate(90);  // 901
CardNumber::generate(908); // 9084

It is also possible to use a special formatting class. For example, for loyalty cards:

use DragonCode\CardNumber\CardNumber;
use DragonCode\CardNumber\Formatters\LoyaltyFormatter;

$loyalty = LoyaltyFormatter::create();
 
CardNumber::generate(1, $loyalty); // 0018
CardNumber::generate(2, $loyalty); // 0026

CardNumber::generate(12345, $loyalty); // 123-455
CardNumber::generate(23456, $loyalty); // 234-567

CardNumber::generate(123456, $loyalty); // 123-4566
CardNumber::generate(234567, $loyalty); // 234-5676

CardNumber::generate(123456123, $loyalty); // 12-3456-1239
CardNumber::generate(234567123, $loyalty); // 23-4567-1230

Or bank cards:

use DragonCode\CardNumber\CardNumber;
use DragonCode\CardNumber\Formatters\BankFormatter;

$bank = BankFormatter::create();

CardNumber::generate(558047337202473, $bank); // 5580 4733 7202 4733
CardNumber::generate(529391143678555, $bank); // 5293 9114 3678 5557

Three formatters are available by default:

  • DragonCode\CardNumber\Formatters\DefaultFormatter

  • DragonCode\CardNumber\Formatters\BankFormatter

  • DragonCode\CardNumber\Formatters\LoyaltyFormatter

But you can easily create your own and use it. Just inherit the created class from the abstract class “DragonCode\CardNumber\Formatters\Formatter“.

Factories

When generating, it is also possible to use factories. This will allow you to easily and without additional code generate a number for which you need to get a control value using the Luhn algorithm.

For example, we want to generate a loyalty card number for a customer based on the following data. Let’s say the resulting number should be exactly 11 characters long and the general form should be in the format “xxx-xxxx-xxxx”, where:

  • The first two digits are the year the card was issued;

  • The third and fourth – the level or type of customer loyalty;

  • From the fifth to the tenth – leave under the user ID;

  • The eleventh is a check digit.

We will use the following data:

  • Year of issue – 2023

  • Loyalty level – 4

  • User ID – 1234

To get the number, we need to create a factory and pass it to the generator id parameter:

use DragonCode\CardNumber\CardNumber;
use DragonCode\CardNumber\Factories\CustomerFactory;
use DragonCode\CardNumber\Formatters\LoyaltyFormatter;

$formatter = LoyaltyFormatter::create();

$loyaltyLevel = 4;
$userId       = 1234;

$customer = CustomerFactory::create()
    ->level($loyaltyLevel)
    ->customer($userId);

return CardNumber::generate($customer, $formatter);
// 230-4001-2348

Thus, the output will be the number “230-4001-2348”, which will be correct from the point of view of the Luhn algorithm.

Initial project card number contains factories for the level of customer loyalty and generation of bank card numbers, but you can easily create your own formatter according to your conditions. To do this, you need to inherit the created class from the abstract class “DragonCode\CardNumber\Factories\Factory“.

Laravel Framework

Project card number also contains a validation rule for the Laravel 10 framework and above.

use DragonCode\CardNumber\Laravel\Validation\Rules\CardNumberRule;
use Illuminate\Foundation\Http\FormRequest;

class SomeRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'number' => ['required', new CardNumberRule()]
        ];
    }
}

In addition, you can validate a specific type or types of maps by passing the appropriate data to the constructor parameter. For example:

use DragonCode\CardNumber\Enums\CardType;
use DragonCode\CardNumber\Laravel\Validation\Rules\CardNumberRule;
use Illuminate\Foundation\Http\FormRequest;

class SomeRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'visa_card_1' => ['required', new CardNumberRule(CardType::visa)],
            'visa_card_2' => ['required', new CardNumberRule('visa')],
            
            'few_cards' => ['required', new CardNumberRule([CardType::visa, CardType::masterCard])],
            'few_cards' => ['required', new CardNumberRule(['visa', 'mastercard'])],
        ];
    }
}

By default, if an invalid card type is passed for validation, the rule will return an error message with the text: “The :attribute field must contain a card number of one of the following types:” and list all available card types for validation at the end.

If the card number is incorrect, then the message “The :attribute field must be a valid card number.” will be returned.

To localize these messages, you can install the package dragon-code/translation-set, which contains translations of these messages into 78 languages. This package is fully compatible with the project Laravel Language.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *