Коды банковских карт

Номер банковской карты (PAN — Primary Account Number) представляет собой последовательность цифр, которая кодирует тип платёжной системы, эмитента и уникальный идентификатор счёта. На практике длина PAN варьируется от 13 до 19 символов, однако наиболее распространённый формат — 16 цифр.

В интерфейсах ввода такие данные требуют строгого контроля формата: разделения на группы, ограничения допустимых символов, проверки префиксов и длины. Для этих задач используется библиотека Inputmask, позволяющая описывать правила ввода через маски и динамическую валидацию.

Общая модель маски банковской карты

Типичная визуальная структура номера карты:

  • 4 группы по 4 цифры: #### #### #### ####
  • или 4-6-5 (для American Express): #### ###### #####

Маска выполняет несколько функций:

  • ограничивает ввод только цифрами;
  • автоматически вставляет разделители;
  • контролирует длину;
  • может адаптироваться под тип карты;
  • обеспечивает единообразный формат отображения.

Базовая маска для банковской карты в Inputmask

Простейшая реализация:

import Inputmask from "inputmask";

Inputmask("9999 9999 9999 9999").mask("#card");

Здесь:

  • 9 — обязательная цифра;
  • пробелы — статические разделители;
  • #card — целевой input-элемент.

Такая маска подходит для фиксированных 16-значных номеров, но не учитывает различия между платёжными системами.

Использование универсальной цифровой маски

Для повышения гибкости применяется символ 9 или \d-аналог через определения:

Inputmask({
  mask: "9999 9999 9999 9999 999",
  placeholder: "",
  showMaskOnHover: false,
  showMaskOnFocus: true
}).mask("#card");

Добавление 19 символов позволяет учитывать редкие форматы карт (например, некоторых локальных банковских систем).

Динамические маски для разных платёжных систем

Карточные номера не унифицированы. Например:

  • Visa — 16 цифр
  • Mastercard — 16 цифр
  • American Express — 15 цифр (4-6-5)

Для поддержки всех форматов применяется динамическая маска:

Inputmask({
  mask: [
    "9999 9999 9999 9999",
    "9999 999999 99999"
  ],
  keepStatic: true
}).mask("#card");

Параметр keepStatic: true позволяет библиотеке выбирать подходящий шаблон в процессе ввода.

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

Первые 6–8 цифр PAN формируют BIN (Bank Identification Number), который определяет платёжную систему.

Пример логики определения:

function detectCardType(value) {
  const cleaned = value.replace(/\s/g, "");

  if (/^4/.test(cleaned)) return "visa";
  if (/^5[1-5]/.test(cleaned)) return "mastercard";
  if (/^3[47]/.test(cleaned)) return "amex";

  return "unknown";
}

Эта логика используется совместно с Inputmask для переключения маски в реальном времени.

Динамическое переключение масок

const amexMask = "9999 999999 99999";
const defaultMask = "9999 9999 9999 9999";

const mask = Inputmask({
  mask: defaultMask,
  onBeforeMask: function (value) {
    return value.replace(/\D/g, "");
  }
});

mask.mask("#card");

document.querySelector("#card").addEventListener("input", (e) => {
  const value = e.target.value.replace(/\s/g, "");

  if (/^3[47]/.test(value)) {
    mask.option({ mask: amexMask });
  } else {
    mask.option({ mask: defaultMask });
  }
});

Такой подход обеспечивает адаптивный ввод без перезагрузки компонента.

Форматирование групп цифр

Одной из ключевых задач является визуальное разделение PAN на читаемые блоки.

Inputmask поддерживает статические и динамические разделители:

Inputmask({
  mask: "9999 9999 9999 9999",
  groupSeparator: " ",
  autoGroup: true
}).mask("#card");

Параметр autoGroup позволяет автоматически вставлять разделители по шаблону.

Контроль допустимых символов

Банковские номера строго цифровые, поэтому необходимо исключить:

  • буквы;
  • спецсимволы;
  • пробелы (кроме форматирования).

Реализация через definitions:

Inputmask({
  mask: "9999 9999 9999 9999",
  definitions: {
    "9": {
      validator: "[0-9]",
      cardinality: 1
    }
  }
}).mask("#card");

Такой подход усиливает контроль над вводом и исключает некорректные символы на уровне библиотеки.

Обработка вставки (paste)

При вставке длинных строк часто поступают данные без пробелов:

Inputmask({
  mask: "9999 9999 9999 9999",
  onBeforePaste: function (pastedValue) {
    return pastedValue.replace(/\D/g, "");
  }
}).mask("#card");

Это обеспечивает нормализацию входных данных до применения маски.

Проверка завершённости ввода

Для банковских форм важно определять момент полного заполнения:

Inputmask({
  mask: "9999 9999 9999 9999",
  oncomplete: function () {
    console.log("Номер карты введён полностью");
  },
  onincomplete: function () {
    console.log("Ввод не завершён");
  }
}).mask("#card");

Такие события используются для активации кнопки отправки формы.

Интеграция с Luhn-алгоритмом

Inputmask не выполняет финансовую валидацию, но может работать совместно с алгоритмом Луна:

function luhnCheck(value) {
  const digits = value.replace(/\D/g, "").split("").reverse();

  let sum = 0;

  for (let i = 0; i < digits.length; i++) {
    let n = parseInt(digits[i], 10);

    if (i % 2 === 1) {
      n *= 2;
      if (n > 9) n -= 9;
    }

    sum += n;
  }

  return sum % 10 === 0;
}

Использование вместе с Inputmask:

document.querySelector("#card").addEventListener("blur", (e) => {
  const valid = luhnCheck(e.target.value);

  if (!valid) {
    e.target.classList.add("error");
  }
});

Маскирование в реактивных интерфейсах

В современных SPA Inputmask часто применяется внутри компонентов:

import Inputmask from "inputmask";

function attachCardMask(el) {
  const im = new Inputmask({
    mask: "9999 9999 9999 9999",
    placeholder: " "
  });

  im.mask(el);

  return im;
}

При смене состояния компонента маска может переинициализироваться без потери данных.

Оптимизация поведения ввода

При работе с банковскими данными критичны следующие параметры:

  • greedy: false — предотвращает избыточное заполнение;
  • clearIncomplete: true — очищает неполные значения;
  • autoUnmask: true — возвращает «чистое» значение без форматирования.

Пример:

Inputmask({
  mask: "9999 9999 9999 9999",
  greedy: false,
  clearIncomplete: true,
  autoUnmask: true
}).mask("#card");

Защита от некорректных данных на уровне UX

Маска не является средством безопасности, но снижает вероятность ошибок:

  • предотвращает ввод букв;
  • исключает неправильную длину;
  • визуально структурирует номер;
  • снижает количество серверных ошибок.

Комбинация маски и серверной проверки остаётся стандартной практикой при работе с PAN.