Все статьи
Криптография и безопасность

Валидация кредитных карт: алгоритм Луна

Алгоритм Луна, проверка номеров карт, определение типа карты (Visa, MC, Amex), безопасность.

27 марта 2025
7 мин чтения
ConvertHub
#кредитные карты#luhn#валидация

Введение

При вводе номера карты в интернет-магазине вы наверняка замечали: опечатка в одной цифре почти сразу подсвечивается красным. Как сайт понимает, что номер неверен, ещё до обращения в банк? Ответ — алгоритм Луна. Это простая математическая проверка, которая отсеивает большинство случайных ошибок ввода. В статье разберём, как устроен алгоритм, как по номеру определить тип карты и какие ограничения важно учитывать. Проверить карту можно в нашемвалидаторе кредитных карт.

Что такое алгоритм Луна

Алгоритм Луна разработан инженером IBM Хансом Питером Луном в 1954 году. Это контрольная сумма, которая обнаруживает любую одиночную ошибку в цифре номера, а также большинство перестановок соседних цифр (например, когда пользователь случайно набрал «32» вместо «23»).

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

Как работает алгоритм

Шаги вычисления контрольной суммы по Луну:

  1. Отбросить пробелы и нецифровые символы.
  2. Начать с последней цифры и двигаться влево.
  3. Каждую вторую цифру (счёт с конца, начиная с предпоследней) умножить на 2.
  4. Если результат умножения больше 9, вычесть из него 9 (или сложить цифры результата).
  5. Сложить все полученные цифры.
  6. Если сумма делится на 10 без остатка — номер валиден.

Пример для номера 4539 1488 0343 6467

Номер:        4  5  3  9  1  4  8  8  0  3  4  3  6  4  6  7
Удвоить (x2): 8     6     2     8     0     8     8     8
                       ↓1+8=9
Цифры:        8  5  6  9  2  4  7  8  0  3  8  3  3  4  3  7

Сумма = 8+5+6+9+2+4+7+8+0+3+8+3+3+4+3+7 = 80
80 mod 10 = 0 → номер валиден

Реализация алгоритма

JavaScript

function luhnCheck(cardNumber) {
  // Убираем всё, кроме цифр
  const digits = String(cardNumber).replace(/\D/g, '');

  if (digits.length < 13 || digits.length > 19) {
    return false;
  }

  let sum = 0;
  let shouldDouble = false;

  // Идём справа налево
  for (let i = digits.length - 1; i >= 0; i--) {
    let digit = parseInt(digits[i], 10);

    if (shouldDouble) {
      digit *= 2;
      if (digit > 9) digit -= 9;
    }

    sum += digit;
    shouldDouble = !shouldDouble;
  }

  return sum % 10 === 0;
}

luhnCheck('4539148803436467'); // true
luhnCheck('4539148803436460'); // false

Python

def luhn_check(card_number: str) -> bool:
    digits = [int(c) for c in str(card_number) if c.isdigit()]
    if not 13 <= len(digits) <= 19:
        return False

    total = 0
    should_double = False

    for digit in reversed(digits):
        if should_double:
            digit *= 2
            if digit > 9:
                digit -= 9
        total += digit
        should_double = not should_double

    return total % 10 == 0

print(luhn_check('4539148803436467'))  # True

Go

package main

import (
  "fmt"
  "unicode"
)

func luhnCheck(cardNumber string) bool {
  var digits []int
  for _, r := range cardNumber {
    if unicode.IsDigit(r) {
      digits = append(digits, int(r-'0'))
    }
  }

  if len(digits) < 13 || len(digits) > 19 {
    return false
  }

  sum := 0
  shouldDouble := false
  for i := len(digits) - 1; i >= 0; i-- {
    d := digits[i]
    if shouldDouble {
      d *= 2
      if d > 9 { d -= 9 }
    }
    sum += d
    shouldDouble = !shouldDouble
  }
  return sum%10 == 0
}

func main() {
  fmt.Println(luhnCheck("4539148803436467")) // true
}

Определение типа карты по BIN

Первые 6–8 цифр номера карты — это BIN (Bank Identification Number), или IIN. По ним можно определить платёжную систему, банк-эмитент, тип карты (дебетовая или кредитная) и страну. Платёжная система определяется по первым цифрам:

Платёжная системаНачальные цифрыДлина
Visa413, 16, 19
Mastercard51–55, 2221–272016
American Express34, 3715
Discover6011, 622126–622925, 644–649, 6516, 19
МИР2200–220416–19
JCB3528–358916–19
Diners Club300–305, 3095, 36, 38, 3914–19

Пример определения платёжной системы

function detectCardType(number) {
  const n = number.replace(/\D/g, '');

  if (/^4/.test(n)) return 'visa';
  if (/^(5[1-5]|2(2[2-9]|[3-6]\d|7[01]|720))/.test(n)) return 'mastercard';
  if (/^3[47]/.test(n)) return 'amex';
  if (/^(6011|65|64[4-9])/.test(n)) return 'discover';
  if (/^220[0-4]/.test(n)) return 'mir';
  if (/^3(?:0[0-5]|095)/.test(n)) return 'diners';
  if (/^(?:352[89]|35[3-8]\d)/.test(n)) return 'jcb';

  return 'unknown';
}

Что алгоритм НЕ проверяет

Лун-проверка ловит случайные опечатки, но не гарантирует, что карта:

  • Реально существует. Можно придумать число, удовлетворяющее формуле Луна, но не привязанное ни к какому счёту.
  • Активна. Срок действия и статус карты можно проверить только через платёжный шлюз.
  • Имеет средства. Баланс и лимиты — также за пределами алгоритма.
  • Не украдена. Проверка по базам украденных карт — отдельная процедура.

Поэтому валидация по Луну — это лишь первая линия обороны. После неё следует проверка срока действия, CVV и, при необходимости, 3-D Secure.

Практическое применение

В веб-формах

Валидация по Луну подключается на этапе ввода. Когда пользователь набирает номер, клиентский JavaScript проверяет сумму и подсвечивает поле, если она не сходится. Это снижает количество ошибочных отправок формы.

В платёжных шлюзах

Серверная проверка также обязательна. Даже если клиентский скрипт пропустил ошибку, шлюз не отправит запрос в банк с невалидным номером — это экономит комиссию за попытку.

В системах учёта

Алгоритм Луна применяют не только для карт. Им нумеруют:

  • IMEI телефонов.
  • Национальные идентификаторы поставщиков (GS1).
  • Номера социального страхования (в некоторых странах).
  • Штрих-коды SimCards.

Ограничения

У алгоритма есть несколько ограничений:

  • Он не ловит перестановку вида «09 ↔ 90» — обе эти пары дают одинаковый вклад в сумму.
  • Он не защищает от подделки — формула публичная.
  • Он не проверяет срок действия, имя владельца и CVV.
  • Некоторые карты могут иметь нестандартную длину, что усложняет проверки.

Безопасность при работе с номерами карт

  1. Не храните PAN. Если бизнес-требования не требуют хранения полного номера — не храните его.
  2. Используйте токенизацию. Многие шлюзы возвращают токен вместо PAN — работайте с ним.
  3. Маскируйте номер. В логах и UI показывайте только последние 4 цифры: **** **** **** 6467.
  4. Шифруйте при передаче. Только HTTPS, никакой передачи PAN в URL.
  5. Соответствуйте PCI DSS. Если вы обрабатываете карты — сертификация обязательна.
  6. Не логируйте CVV. Это запрещено стандартом PCI DSS.

Регулярные выражения для валидации

Часто валидация номера карты объединяет проверку длины и платёжной системы в одно регулярное выражение:

const CARD_PATTERNS = {
  visa:       /^4[0-9]{12}(?:[0-9]{3})?(?:[0-9]{3})?$/,
  mastercard: /^(5[1-5][0-9]{14}|2(2[2-9][0-9]{12}|[3-6][0-9]{13}|7[01][0-9]{12}|720[0-9]{12}))$/,
  amex:       /^3[47][0-9]{13}$/,
  discover:   /^6(?:011|5[0-9]{2})[0-9]{12}$/,
  mir:        /^220[0-4][0-9]{12,15}$/,
};

function validateCard(number, type) {
  const clean = number.replace(/\D/g, '');
  const pattern = CARD_PATTERNS[type];
  return pattern ? pattern.test(clean) && luhnCheck(clean) : luhnCheck(clean);
}

Заключение

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

Попробуйте эти инструменты

Похожие статьи