Алгоритм Луна (Luhn algorithm) - это процесс вычисления контрольной цифры для числа в соответствии со стандартом ISO/IEC 7812. Сам процесс не является криптографическим средством и никак не защищает находящиеся в этом числе данные. Он предназначен, в первую очередь, для выявления ошибок, вызванных с непреднамеренным искажением данных. Например, при ручном вводе номера карты или любого другого числа. Данный алгоритм позволяет с некоторой степенью достоверности судить об отсутствии ошибок в блоке цифр, но никак не может исправить их.
Алгоритм разработан сотрудником IBM Хансом Питером Луном в 1954-м году и запатентован в 1960-м году. Наиболее часто данный алгоритм применяется при формировании номеров всех банковских карт, некоторых номеров дисконтных карт, кодов социального страхования, IMEI-кодов, номеров железнодорожных вагонов РЖД, уникальных серийных номеров SIM-карт (ICCID) и в других случаях.
Проверка числа
Формула очень проста: для того чтобы проверить число в соответствии с алгоритмом Луна, необходимо просуммировать все цифры на нечётных позициях справа налево, далее прибавить к полученному значению сумму всех чётных цифр, умноженных на 2, при этом, если произведение таких чисел больше 9, то из него вычитается 9. Если полученная сумма делится на 10 без остатка, значит начальное число введено верно.
Рассмотрим корректное число "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
В конечном итоге получаем сумму равную 60. Это число делится на 10 без остатка, значит, номер введён правильно.
Теперь проверим некорректное число "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
Таким образом мы получили информацию о том, что начальное число неверно написано, а значит необходимо проверить порядок цифр и исправить ошибку.
Пакетное решение на PHP
Пакетных решений по проверки числа по алгоритму Луна много, но редко кто предлагает генерацию таких чисел, и так на свет появился проект The Dragon Code: Card Number.
Данный проект позволяет не только проверять любые числа по алгоритму Луна, но и генерировать их, например, для применения в программе лояльности клиентов.
Установка
Проще всего установить данный продукт можно при помощи Composer:
composer require dragon-code/card-number
И сразу после этого можно приступать к работе.
Валидация
Проверять можно абсолютно любые числа и в любом формате. Например:
use DragonCodeCardNumberCardNumber;
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
Кроме того, помимо валидности самого числа, можно также проверять принадлежность номера какому-либо типу банковских карт. Например:
use DragonCodeCardNumberCardNumber;
use DragonCodeCardNumberEnumsCardType;
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
В настоящий момент проект содержит валидаторы для номеров следующих типов карт: AmericanExpress, Dankort, DinersClub, Discovery, Forbrugsforeningen, HiperCard, JCB, Maestro, MasterCard, МИР, Troy, UnionPay, VISA и VISA Electron.
Генерация номеров
Для генерации номера, например, по идентификатору клиента, можно вызвать следующий код:
use DragonCodeCardNumberCardNumber;
CardNumber::generate(1); // 18
CardNumber::generate(2); // 26
CardNumber::generate(10); // 109
CardNumber::generate(90); // 901
CardNumber::generate(908); // 9084
Также возможно применение специального форматирующего класса. Например, для карт лояльности:
use DragonCodeCardNumberCardNumber;
use DragonCodeCardNumberFormattersLoyaltyFormatter;
$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
Или банковских карт:
use DragonCodeCardNumberCardNumber;
use DragonCodeCardNumberFormattersBankFormatter;
$bank = BankFormatter::create();
CardNumber::generate(558047337202473, $bank); // 5580 4733 7202 4733
CardNumber::generate(529391143678555, $bank); // 5293 9114 3678 5557
По-умолчанию доступно три форматтера:
-
DragonCodeCardNumberFormattersDefaultFormatter
-
DragonCodeCardNumberFormattersBankFormatter
-
DragonCodeCardNumberFormattersLoyaltyFormatter
Но Вы с лёгкостью можете создать свой и использовать его. Просто отнаследуйте созданный класс от абстрактного класса "DragonCodeCardNumberFormattersFormatter
".
Фабрики
При генерации также есть возможность использования фабрик. Это позволит легко и без дополнительного кода сформировать число, для которого необходимо получить контрольное значение по алгоритму Луна.
Например, мы хотим сформировать номер карты лояльности для клиента на основании следующих данных. Допустим, полученное число должно быть длиной ровно 11 символов и общий вид должен иметь формат "xxx-xxxx-xxxx", где:
-
Первые две цифры - год выдачи карты;
-
Третья и четвёртая - уровень или тип лояльности клиента;
-
С пятой по десятую- оставляем под идентификатор пользователя;
-
Одиннадцатая - контрольная цифра.
Будем использовать следующие данные:
-
Год выдачи - 2023
-
Уровень лояльности - 4
-
Идентификатор пользователя - 1234
Для получения числа нам нужно создать фабрику и передать её в параметр идентификатора генератора:
use DragonCodeCardNumberCardNumber;
use DragonCodeCardNumberFactoriesCustomerFactory;
use DragonCodeCardNumberFormattersLoyaltyFormatter;
$formatter = LoyaltyFormatter::create();
$loyaltyLevel = 4;
$userId = 1234;
$customer = CustomerFactory::create()
->level($loyaltyLevel)
->customer($userId);
return CardNumber::generate($customer, $formatter);
// 230-4001-2348
Таким образом, на выходе будет получено число "230-4001-2348", которое будет корректным с точки зрения алгоритма Луна.
Изначально проект Card Number содержит фабрики для уровня лояльности клиентов и формирования номеров банковских карт, но Вы можете легко создать свой форматтер по своим условиям. Для этого нужно отнаследовать созданный класс от абстрактного класса "DragonCodeCardNumberFactoriesFactory
".
Laravel Framework
Проект Card Number также содержит правило валидации для фреймворка Laravel 10 и выше.
use DragonCodeCardNumberLaravelValidationRulesCardNumberRule;
use IlluminateFoundationHttpFormRequest;
class SomeRequest extends FormRequest
{
public function rules(): array
{
return [
'number' => ['required', new CardNumberRule()]
];
}
}
Кроме этого можно валидировать конкретный тип или типы карт, передав в параметр конструктора соответствующие данные. Например:
use DragonCodeCardNumberEnumsCardType;
use DragonCodeCardNumberLaravelValidationRulesCardNumberRule;
use IlluminateFoundationHttpFormRequest;
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'])],
];
}
}
По-умолчанию, в случае передачи некорректного типа карты для проверки, правило вернёт сообщение об ошибке с текстом: "The :attribute field must contain a card number of one of the following types:" и перечислит в конце все доступные типы карт для проверки.
В случае если некорректным окажется номер карты, тогда будет возвращено сообщение "The :attribute field must be a valid card number.".
Для локализации этих сообщений Вы можете установить пакет dragon-code/translation-set, который содержит переводы этих сообщений на 78 языков. Данный пакет полностью совместим с проектом Laravel Lang.
Автор: Andrey Helldar