Назад к блогу

Как работают платёжные сети: взгляд на Visa и Mastercard

Как работают платёжные сети: взгляд на Visa и Mastercard

Разбираем, как на самом деле устроен путь банковской карты от терминала до эмитента: почему ISO 8583 задаёт лишь общий каркас сообщений, а вся конкретика — поля, маршрутизация, клиринг и расчёты — остаётся на стороне платёжных сетей вроде Visa и Mastercard. Это понятное объяснение того, где заканчивается стандарт и начинается инженерия реальных платёжных систем.

Карточный платёж выглядит как мгновенное списание, но за ним стоит разделение труда между несколькими слоями: стандартом обмена сообщениями, телекоммуникационной сетью, клиринга и расчётом. Разберём, что именно фиксирует ISO 8583, что он сознательно оставляет за своими рамками и как это отражается в реализациях — от структур сообщений и битмапа до корреляции запросов и ответов.

Что задаёт стандарт

ISO 8583:2023 определяет общий интерфейс, посредством которого сообщения, инициированные финансовыми транзакционными картами, могут передаваться между эквайером и эмитентом. Он задаёт структуру и формат сообщений, включая нормализованные типы данных. А вот определения конкретных сообщений, полей, значений и вспомогательную информацию предоставляет не сам текст стандарта, а орган сопровождения ISO 8583 (MA). Именно поэтому конкретные определения нужно брать у MA, а не из документа ISO: стандарт задаёт только общую структуру и формат.

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

Из этого следует практическая особенность: большинство реализаций ISO 8583 используют конфиденциальные спецификации, различающиеся между платёжными системами, поэтому часто требуется создавать собственную спецификацию. Стандарт обычно не используется системами или сетями напрямую.

Спецификация и сообщение

В библиотеке moov-io/iso8583 разделение отражает эту двухуровневость. MessageSpec описывает каждое поле через максимальную длину, описание, кодировку значения, кодировку и тип префикса длины (фиксированный или переменный с числом цифр длины) и опциональное дополнение. Типы полей бывают строковыми, числовыми, двоичными и составными — последние для структур вроде TLV или полей с позиционными подполями.

Message хранит указатель на спецификацию и карту полей. Конструктор принимает спецификацию, проверяет её и сохраняет; при ошибке проверки спецификации происходит паника, поскольку спецификации в основном статичны.

Что остаётся за рамками стандарта

Два процесса, которые часто смешивают, устроены по-разному. клиринг наступает позже авторизации: мерчант подаёт финальную сумму — например, добавляя чаевые, написанные на чеке, или аннулируя транзакцию. расчёта (settlement) происходит уже после того, как транзакция финализирована. Клиринг инициирует перевод, расчёт его исполняет.

ISO 8583 — стандарт обмена сообщениями между эквайерами и эмитентами через карточные сети. Он определяет множество стандартных полей, которые остаются одинаковыми во всех системах или сетях, и оставляет несколько дополнительных полей для передачи специфики сети. Расчёты же сеть выполняет сама, имея финансовые отношения с каждой стороной. Для эффективности применяется нетто-расчёт: ежедневно дебеты и кредиты каждого участника суммируются, и в конце дня нетто-сумма переводится один раз.

Маршрутизация тоже вне стандарта: в сообщениях ISO 8583 нет адресной информации, поэтому иногда используется TPDU. Маршрутизацию выполняет платёжная сеть — её задача эксплуатировать телекоммуникационную сеть для маршрутизации сообщений о транзакциях, а на базовом техническом уровне ответственность сети — пересылка сообщений между участвующими эмитентами и эквайерами. Сообщения обычно идут по долгоживущим TCP-сокетам, и для разделения сообщений в потоке нужен отдельный слой обрамления — обычно 4-байтовый индикатор длины перед сообщением. Некоторые сети, например Visa, добавляют ещё один заголовок между обрамлением длины и самим сообщением; он содержит метаинформацию, включая отправителя, получателя и признак отклонения сетью с ошибками. Комиссии — merchant discount rate, interchange, network assessment fee — устанавливаются сетью и участниками, а не стандартом.

Транспорт поверх TCP

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

Поверх долгоживущего TCP-соединения каждое сообщение между клиентом и сервером сопровождается заголовком длины. Заголовок может быть 4 байта ASCII, 2 байта BCD или любым другим пользовательским форматом; для упрощения чтения и записи предоставляется интерфейс заголовка. Готовые реализации: длина в 2 байтах, длина в 4 байтах ASCII, длина в 2 байтах BCD и VMLH (2 байта длины плюс 2 зарезервированных байта).

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

Структура сообщения и битмапы

битмап — ключ к тому, как сообщение сообщает о своём составе. Поле считается присутствующим только тогда, когда соответствующий бит в битмапе установлен.

Сообщение содержит как минимум один битмап, называемый первичным, который указывает наличие элементов данных с 1 по 64. Наличие необязательного вторичного битмапа обозначается первым битом первичного; если он присутствует, вторичный указывает наличие элементов с 65 по 128. Аналогично может использоваться третичный битмап для полей с 129 по 192, хотя эти элементы данных используются редко.

В jPOS полный битмап хранится в поле -1 и может быть длиной до 192 бит. Если задано поле для третичного битмапа и соответствующий упаковщик является битмап-упаковщиком, то при наличии установленных битов выше 128 битмап разделяется: биты 128–192 выделяются в отдельный набор, в основном битмапе устанавливается бит поля третичного битмапа, а старшая часть очищается. Максимальный номер поля при упаковке вычисляется как минимум из максимального поля сообщения и 192 или 128 в зависимости от наличия третичного битмапа; при распаковке — как минимум из длины полей минус один и длины битмапа минус один. Если третичный битмап находится внутри элемента данных, максимальный номер поля обновляется как 128 плюс длина третичного битмапа минус один.

В Go-реализации при упаковке битмап сбрасывается, затем для каждого идентификатора поля, начиная с 2, устанавливается соответствующий бит, а биты присутствия битмапа пропускаются.

Вложенные сообщения

Вложенные сообщения сериализуются тремя способами: таблицами, вложенными битмап-сообщениями и TLV. В таблицах каждое поле обычно фиксированной длины и всегда включается — либо со значением, либо с плейсхолдером по умолчанию. Вложенное битмап-сообщение включает битмап перед элементами, сообщая получателю, какие поля присутствуют, а какие опущены; такие битмапы обычно короче и фиксированной длины, например 8 байт на 64 поля.

Некоторые таблицы целиком состоят из числовых полей, закодированных packed BCD. Это относится к полю Visa Additional Point of Sale Information, где каждое однозначное поле должно занимать только один ниббл, или половину байта.

Третичный битмап в jPOS

Сообщение содержит первичный битмап, если упаковщик поля 1 является битмап-упаковщиком. При упаковке полный битмап берётся из поля -1 компонента. Если задано поле третичного битмапа (по умолчанию -999) и в старшей части битмапа есть установленные биты, старшие биты вырезаются в отдельный набор, в основном битмапе ставится бит поля третичного битмапа, старшие биты очищаются, и в сообщение добавляется новый битмап с номером этого поля. Если битов выше 128 нет, остаточное поле удаляется из сообщения, бит сбрасывается, запись удаляется из клона карты полей.

При распаковке из байтового массива третичный битмап обрабатывается, когда счётчик доходит до поля третичного битмапа при условии, что полей больше 129, битмап занимает 16 байт и упаковщик этого поля является битмап-упаковщиком; максимальный номер поля увеличивается на длину третичного битмапа, а его биты переносятся в основной битмап со сдвигом 128. В потоковом варианте третичный битмап берётся из поля 65, если установлен бит 65 и упаковщик поля 65 является битмап-упаковщиком, после чего распаковываются поля со сдвигом на 128.

Типы сообщений и MTI

MTI (Message Type Indicator) расшифровывается по цифрам.

Первая цифра — версия ISO 8583: 0 — 1987, 1 — 1993, 2 — 2003, 8 — национальное использование, 9 — частное.

Вторая цифра — класс сообщения: 1 — авторизация, 2 — финансовое, 3 — действия с файлами, 4 — отмена или чарджбэк, 5 — сверка, 6 — административное, 7 — сбор комиссии, 8 — управление сетью.

Третья цифра — функция: 0 — запрос от эквайера к эмитенту, 1 — ответ на запрос, 2 — advice, 3 — ответ на advice, 4 — уведомление, 5 — подтверждение уведомления, 6 — инструкция, 7 — подтверждение инструкции. Запросы идут end-to-end и могут быть отклонены получателем-эмитентом, а advice и уведомления должны быть приняты получателем.

Четвёртая цифра — источник сообщения в платёжной цепочке: 0 — эквайер, 1 — повтор эквайера, 2 — эмиттер, 3 — повтор эмиттера, 4 — другое, 5 — повтор другого; повторы возникают после таймаутов.

Так, 1100 — это авторизационный запрос от эквайера по версии 1993 года, а 1102 был бы авторизационным запросом от эмиттера, что невозможно.

Авторизация и финансовые сообщения

Авторизационные сообщения (класс x1xx) служат для проверки доступности средств и получения одобрения, но не проводят операцию по счёту для последующей сверки; они относятся к двухсообщённой системе (Dual message system, DMS). Финансовые сообщения (класс x2xx) также проверяют доступность средств и получают одобрение, но сразу проводят операцию по счёту; они относятся к односообщённой системе (Single message system, SMS).

Класс сообщения определяется вторым символом MTI: для авторизации он равен '1', для финансовой операции — '2'. Третий символ задаёт функцию: запросы идут от эквайера к эмитенту и могут быть приняты или отклонены, а ответы на запрос являются ответом на запрос. Авторизация не постит средства на счёт, потому что лишь определяет доступность средств и получает одобрение, а проводка происходит позже через обмен файлами в DMS.

Правила преобразования MTI

В jPOS методы анализа MTI устроены так. Проверка на запрос смотрит на третий символ MTI: запросом считается сообщение с чётным значением этого символа. Ответом считается всё, что не является запросом. Авторизация и финансовая операция различаются вторым символом MTI: '1' для авторизации, '2' для финансовой операции. Отмена требует, чтобы второй символ был '4', а четвёртый — '0' или '1'. Повтором считается сообщение, у которого четвёртый символ равен '1'.

Установка ответного MTI возможна только для сообщения-запроса; для остальных это ошибка. Новый MTI формируется так: первые два символа сохраняются, третий увеличивается на 1, а четвёртый заменяется в зависимости от исходного — '0' или '1' дают '0', '2' или '3' дают '2', '4' или '5' дают '4'.

Частичный разбор через MessageScanner

MessageScanner потребляет байты последовательно, но возвращает только запрошенные поля, что делает его лёгким и подходящим для прокси и маршрутизаторов. Сканирование только в возрастающем порядке полей обязательно: сканирование поля на текущей позиции или раньше возвращает ошибку.

Это позволяет сначала запросить поле MTI, получить его значение и принять решение о маршрутизации до полной распаковки: например, если MTI равно "0800", сообщение управления сетью можно сразу передать в отдельный обработчик без дальнейшего разбора. Для остальных сообщений можно продолжить сканирование и получить STAN, чтобы маршрутизировать или логировать по MTI и STAN. Поля между текущей позицией и целевым полем потребляются из потока байтов, но отбрасываются, и только запрошенное поле выделяется и возвращается.

Поля данных и типы значений

Типы значений обозначаются буквенными сокращениями: 'n' — только цифры, 'an' — буквы и цифры, 'ans' — буквы, цифры и специальные символы, 'b' — двоичные данные, 'x+n' — сумма, где первый байт 'C' означает положительное (кредит) или 'D' — отрицательное (дебет) значение, за которым идут цифры.

Длины бывают фиксированные и переменные. 'LVAR' означает переменную длину, где каждая 'L' — цифра максимальной длины: LLVAR до 99 цифр, LLLVAR до 999 цифр. Фиксированное поле всегда занимает одинаковое число байт и дополняется padding; суммы часто 12 цифр, выровнены вправо и дополнены нулями:

000000002412

Переменные поля предшествует индикатор длины, поэтому padding не нужен, а получатель сначала декодирует длину, затем извлекает нужное число байт. Для числовых полей вроде PAN (n..19) длина указывается в цифрах, даже если данные упакованы в BCD, что важно для нечётной длины: например, '123' сериализуется в байты [1, 35], а при обратном чтении даёт '0123', и только индикатор длины позволяет отбросить ведущий ноль.

Составные поля

Составные поля делятся на четыре вида. Positional subfields — фиксированный формат, где несколько элементов просто склеены без тегов и разделителей; порядок задаётся функцией сортировки, а ключи в карте служат только идентификаторами для программного доступа. TLV-поля отличаются тем, что каждый элемент имеет явный тег, а длина тега и кодирование длины значения задаются в спецификации. BER-TLV отличается тем, что правила кодирования тега и длины стандартизованы и закодированы в самих данных, поэтому для тега не задают явную длину — кодирование BER-TLV определяет её динамически. Вложенные структуры с Data Set IDs строятся иерархически: внешнее поле использует Data Set ID как тег, каждый ID отображается на собственное составное поле, внутри которого может быть BER-TLV.

Для неизвестных тегов в примере с Data Set IDs заданы пропуск неизвестных тегов и префикс длины Data Set.

Обработка ошибок при разборе TLV

TLV требует особого подхода к неизвестным тегам. Поле missing_tags хранит хеш-карту тегов, которые ещё не реализованы, и это гарантирует, что сообщения по-прежнему корректно проходят round-trip без сбоев на новых тегах. Такой подход к защите от будущих изменений нужен в основном для TLV-сообщений, но аккуратная обработка локальных ошибок в под-сообщениях полезна и для остального парсера.

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

Практика: сборка, отправка и приём

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

Корреляция запроса и ответа в QMUX

QMUX строит ключ корреляции так: он начинается с имени выходной очереди, затем добавляется отображённый MTI, при включённом режиме использования заголовка как части ключа и наличии заголовка — hex-строка заголовка, затем значения полей из выбранного набора. Набор полей выбирается по ключу, построенному из префикса MTI и код обработки: код обработки берётся из поля 3 и нормализуется обрезкой пробелов, причём пустая строка превращается в отсутствие кода. Если набора по коду обработки нет, берётся набор по префиксу MTI или общий. Если ни одно поле ключа не найдено, построение ключа выбрасывает исключение.

При приёме вычисляется ключ, ищется ожидающий запрос по ключу с суффиксом ".req". Если найден асинхронный запрос, вызывается обработка ответа, иначе ответ кладётся в очередь по ключу; в обоих случаях инкрементируется счётчик совпадений. При таймауте асинхронный запрос уменьшает счётчик ожидающих, записывает метрику и вызывает обработчик истечения. При получении ответа отменяется future, увеличивается счётчик принятых, уменьшается счётчик ожидающих, обновляется время последней транзакции, записываются метрики и вызывается обработчик ответа.

Если ответ не совпал ни с одним ожидающим запросом, инкрементируется счётчик необработанных, сообщение по очереди предлагается каждому слушателю запросов, и если ни один не обработал, при заданной очереди необработанных сообщение кладётся в неё с временем жизни 120000, увеличивая счётчик необработанных.

Что из этого следует на практике

Стандарт задаёт каркас, а не готовый протокол. Определения сообщений и полей живут в реестре органа сопровождения, а не в тексте ISO, поэтому совместимость реализаций достигается не чтением стандарта, а согласованием конфиденциальных спецификаций между участниками конкретной платёжной системы.

Транспорт, маршрутизация, клиринг и расчёт — отдельные слои, каждый со своей ответственностью. Сообщение ISO 8583 не содержит адресной информации; доставку обеспечивает сеть, обрамление длины — отдельный механизм поверх долгоживущего TCP-соединения, а движение денег — нетто-расчёт, который сеть выполняет сама.

Битмапы и MTI — два независимых механизма навигации по сообщению. Битмап отвечает на вопрос «какие поля здесь есть», MTI — «что это за сообщение и куда оно идёт». Частичный разбор через forward-only сканирование позволяет принять решение по MTI, не распаковывая сообщение целиком, — это то, что делает возможными прокси и маршрутизаторы.

Обработка неизвестного — часть контракта. Сохранение нераспознанных тегов и замена неразобранных значений плейсхолдерами нужны для того, чтобы сообщение переживало round-trip, даже если спецификация неполна.

Где смотреть в коде

Источники

Похожее