Алгоритм Луна: проверка номеров карт Uzcard, Humo и Visa в коде
Практика4 минРедакция Innovialabs
Коротко
Алгоритм Луна — простая контрольная сумма, которая ловит большинство опечаток в номере карты ещё до отправки формы; он не говорит, существует ли карта, — это проверяет только платёжный провайдер.
Что проверяет алгоритм Луна
Алгоритм Луна (Luhn, mod 10) — формула контрольной суммы. Последняя цифра номера карты подобрана так, чтобы сумма по особому правилу делилась на 10. Если пользователь ошибся в одной цифре или переставил две соседние, сумма почти всегда перестаёт сходиться.
Что Луна не делает:
- не проверяет, что карта существует и активна;
- не проверяет баланс, срок действия и владельца;
- не защищает от мошенничества.
Это проверка для удобства пользователя: подсветить опечатку сразу, а не после запроса к провайдеру.
Как считается сумма
- Идём по цифрам справа налево. Последняя цифра — контрольная, её не трогаем.
- Каждую вторую цифру (вторую справа, четвёртую справа и т. д.) умножаем на 2.
- Если после умножения получилось больше 9, вычитаем 9 (то же, что сложить цифры: 16 → 1 + 6 = 7).
- Складываем все цифры.
- Номер корректен, если сумма делится на 10 без остатка.
Пример на номере 79927398713: справа налево получаем 3, 2, 7, 7, 9, 6, 7, 4, 9, 9, 7. Сумма — 70, делится на 10, номер проходит проверку.
Известное ограничение: перестановку соседних 09 ↔ 90 алгоритм не замечает.
Реализация на JavaScript
function luhnValid(input) {
const digits = String(input).replace(/[\s-]/g, "");
if (!/^[0-9]{12,19}$/.test(digits)) return false;
let sum = 0;
let double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits.charCodeAt(i) - 48;
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return sum % 10 === 0;
}
luhnValid("4111 1111 1111 1111"); // true
luhnValid("4111 1111 1111 1112"); // false
Реализация на Python
import re
def luhn_valid(number: str) -> bool:
digits = re.sub(r"[\s-]", "", number)
if not re.fullmatch(r"[0-9]{12,19}", digits):
return False
total = 0
for i, ch in enumerate(reversed(digits)):
d = int(ch)
if i % 2 == 1:
d *= 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
assert luhn_valid("4111 1111 1111 1111")
assert not luhn_valid("4111-1111-1111-1112")
Обратите внимание: цифры проверяются как [0-9], а не \d. В Python \d по умолчанию совпадает и с цифрами других алфавитов.
Тип карты по BIN
Первые цифры номера — BIN (IIN) — указывают на платёжную систему и банк. Для подсказки в интерфейсе (логотип, длина маски) обычно достаточно префикса:
| Система | Типичный префикс |
|---|---|
| Humo | 9860 |
| Uzcard | 8600 |
| Visa | 4 |
| Mastercard | 51–55, 2221–2720 |
function detectBrand(digits) {
if (digits.startsWith("9860")) return "humo";
if (digits.startsWith("8600")) return "uzcard";
if (digits.startsWith("4")) return "visa";
const p2 = Number(digits.slice(0, 2));
const p4 = Number(digits.slice(0, 4));
if ((p2 >= 51 && p2 <= 55) || (p4 >= 2221 && p4 <= 2720)) return "mastercard";
return "unknown";
}
Важно:
- Диапазоны BIN меняются, появляются новые и кобейдж-карты. Держите таблицу префиксов в конфиге, а не размазанной по коду.
- Проверяйте более длинные префиксы раньше коротких.
- Для локальных систем уточните в документации провайдера, применяется ли к их номерам проверка Луна. Если нет уверенности, показывайте предупреждение, а не блокируйте отправку.
unknown— не ошибка. Не мешайте пользователю оплатить картой, которую ваш код не узнал.
Маска ввода
Разбивка по 4 цифры заметно упрощает проверку номера глазами:
const format = (v) =>
v.replace(/[^0-9]/g, "").slice(0, 19).replace(/(\d{4})(?=\d)/g, "$1 ");
Практические советы:
- Используйте
<input inputmode="numeric" autocomplete="cc-number">, а неtype="number": последний ломает ведущие нули и показывает стрелки. - Принимайте вставку с пробелами и дефисами — очищайте её, а не отклоняйте.
- При форматировании следите за позицией курсора, иначе он прыгает в конец при правке в середине.
- Проверку Луна запускайте, когда введена полная длина, а не на каждом символе.
Настоящая проверка — у провайдера
Клиентская проверка — только фильтр опечаток. Существование карты, баланс, 3-D Secure или SMS-подтверждение и лимиты проверяет платёжный провайдер.
- По возможности используйте платёжную форму или поля провайдера и токенизацию: тогда номер карты не проходит через ваш сервер.
- Не храните полный номер карты в своей базе и логах. Работа с данными карт регулируется стандартом PCI DSS.
- Повторяйте проверку на сервере, если номер всё же к вам приходит: клиентский код легко обойти.