Перейти к содержанию
Innovialabs

Алгоритм Луна: проверка номеров карт Uzcard, Humo и Visa в коде

Практика4 мин

Коротко

Алгоритм Луна — простая контрольная сумма, которая ловит большинство опечаток в номере карты ещё до отправки формы; он не говорит, существует ли карта, — это проверяет только платёжный провайдер.

Что проверяет алгоритм Луна

Алгоритм Луна (Luhn, mod 10) — формула контрольной суммы. Последняя цифра номера карты подобрана так, чтобы сумма по особому правилу делилась на 10. Если пользователь ошибся в одной цифре или переставил две соседние, сумма почти всегда перестаёт сходиться.

Что Луна не делает:

  • не проверяет, что карта существует и активна;
  • не проверяет баланс, срок действия и владельца;
  • не защищает от мошенничества.

Это проверка для удобства пользователя: подсветить опечатку сразу, а не после запроса к провайдеру.

Как считается сумма

  1. Идём по цифрам справа налево. Последняя цифра — контрольная, её не трогаем.
  2. Каждую вторую цифру (вторую справа, четвёртую справа и т. д.) умножаем на 2.
  3. Если после умножения получилось больше 9, вычитаем 9 (то же, что сложить цифры: 16 → 1 + 6 = 7).
  4. Складываем все цифры.
  5. Номер корректен, если сумма делится на 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) — указывают на платёжную систему и банк. Для подсказки в интерфейсе (логотип, длина маски) обычно достаточно префикса:

СистемаТипичный префикс
Humo9860
Uzcard8600
Visa4
Mastercard51–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.
  • Повторяйте проверку на сервере, если номер всё же к вам приходит: клиентский код легко обойти.

Частые вопросы

Нет. Луна подтверждает только, что цифры согласованы между собой. Подобрать номер с правильной суммой просто. Существование карты проверяет только провайдер при авторизации платежа.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.