← назад к разделу

Как только в системе появляются банковские карты, появляется и аббревиатура PCI DSS — и обычно в тревожном контексте: «мы попадаем под PCI?», «аудит через квартал». Разберём стандарт глазами разработчика: когда он вас касается, что реально требует и почему главное архитектурное решение — сделать так, чтобы касался как можно меньше.

Насколько глубоко стандарт вас касается, решает одно: проходит ли номер карты через ваши системы.

один и тот же платёж, три способа приёма редирект / iframe своя форма, транзит своя форма + хранение ваши системы — через какие идёт PAN страница с формой карты скрипты вашей страницы сервис платежей БД платежей логи, трейсы, дампы ✓✓ ✓✓✓✓✓ в CDE: 0 ваших систем в CDE: 2 ваши системы в CDE: 5 ваших систем редирект или iframe форму рисует провайдер PAN видит только он вам возвращается tok_1a2b3c область аудита: минимум своя форма, PAN транзитом страницу отдаёте вы PAN не храните, но касаетесь логи и БД — вне пути карты область аудита: средняя своя форма + хранение PAN шифруется, на показ 6+4 CVV не хранится никогда номер может утечь в логи область аудита: максимум SAQ A · анкета SAQ A-EP · шире SAQ D + аудитор QSA каждая ваша система в CDE — плюс к площади аудита

Редирект или iframe провайдера: номер не заходит в ваши системы — 0 из 5, опросник SAQ A. Своя форма: страница и её скрипты уже в CDE — 2 из 5, SAQ A-EP. Своя форма со своим хранением: в CDE и сервис, и БД, и логи — 5 из 5, SAQ D и аудитор на площадке. Способ приёма выбирают до реализации: он и определяет объём проверки.

Обязательно

Что это и когда касается вас

PCI DSS (Payment Card Industry Data Security Standard) — стандарт безопасности, созданный платёжными системами (Visa, Mastercard и другими). Он обязателен для всех, кто хранит, обрабатывает или передаёт данные платёжных карт. Это не закон государства, а договорное требование: нарушили — штрафы от банка-эквайера и вплоть до запрета принимать карты.

Версию стоит называть вслух: действующая редакция — PCI DSS 4.0.1, она вышла в июне 2024 года и заменила 4.0. Предыдущее поколение стандарта (3.2.1) больше не действует. Разница не косметическая: часть требований 4.x касается именно разработчика, и о них — ниже.

Ключевые термины:

  • PAN (Primary Account Number) — номер карты; главный защищаемый объект;
  • SAD (Sensitive Authentication Data) — CVV, содержимое магнитной полосы, PIN: после авторизации хранить нельзя вообще, даже зашифрованно. До авторизации эти данные можно держать в памяти — иначе платёж просто не провести, — но дальше памяти они уходить не должны. На языке кода это значит: ни в базу, ни в кэш, ни в очередь, ни в трассировку и, самое частое нарушение, ни в лог. Одна строка «залогируем тело запроса, чтобы разобраться» — и правило нарушено;
  • CDE (Cardholder Data Environment) — все системы, которые хранят, обрабатывают или передают карточные данные, плюс всё, что с ними напрямую связано сетью.

Главная стратегия: сузить CDE

Требования PCI DSS распространяются на весь CDE — и на людей, и на процессы вокруг него. Поэтому центральный архитектурный ход — чтобы карточные данные вообще не попадали в ваши системы:

  • Платёжный провайдер + токенизация. Форма оплаты рисуется провайдером (redirect или его iframe на вашей странице); PAN уходит напрямую провайдеру, вам возвращается токен — суррогат без ценности для вора. Ваши списания идут по токену. Большинство сервисов электронной коммерции живут именно так — их область аудита минимальна (самоопросник SAQ A вместо полного аудита).
  • Сегментация. Если карты всё же обрабатываются у вас (платёжный шлюз, процессинг), CDE выделяется в изолированный сетевой сегмент: отдельные сервисы, свой контур доступа, минимум связей наружу. Каждый сервис, добавленный в CDE, — плюс к площади аудита; проектируйте так, чтобы «обычные» сервисы (каталог, заказы, профили) в CDE не входили.

Правило на языке кода: сервис заказов должен знать «оплачено токеном tok_abc», и никогда — номер карты.

Способы приёма и их цена

Как именно карта попадает к провайдеру — главное решение: оно определяет, видит ли ваш код номер вообще, и, как следствие, глубину проверки (какой опросник SAQ вы заполняете).

Способ приёмаКто касается номера картыСкоуп PCIОпросникУсилия
Редирект на страницу провайдератолько провайдерминимальныйSAQ Aнизкие
iframe / hosted fields провайдератолько провайдер (поля — на его домене)минимальныйSAQ Aнизкие
Своя форма, PAN уходит из браузера прямо провайдеру (его скрипт или SDK)провайдер, но страницу и скрипты на ней отдаёте высреднийSAQ A-EPсредние
Своя форма → ваш бэкенд → API провайдераваш сервер принимает PAN, даже если не хранитмаксимальныйSAQ Dвысокие
Своя форма + собственное хранение PANвы храните и обрабатываете PANмаксимальныйSAQ D + аудит QSAочень высокие

Третья и четвёртая строки выглядят похоже, а разница между ними — самая дорогая во всей таблице. В третьей номер карты из браузера уходит сразу провайдеру: ваш сервер его не видит вообще, вы отвечаете только за саму страницу и за скрипты на ней. В четвёртой форма отправляет номер на ваш бэкенд, и уже он зовёт API провайдера. Слово «не храним» тут ничего не меняет: PAN прошёл через вашу память, ваши логи, ваш трейсинг — сервер попал в CDE, и вместо анкеты на пару страниц вы заполняете SAQ D. Если в обсуждении звучит «своя форма, но мы же ничего не храним» — первым делом уточните, куда именно уходит номер.

Для третьего варианта (SAQ A-EP) есть два требования, которые касаются разработчика страницы напрямую и до которых обычно не доходят руки: вести учёт всех скриптов платёжной страницы с обоснованием, зачем каждый нужен, и иметь механизм, который заметит их подмену. В PCI DSS 4.x это пункты 6.4.3 и 11.6.1; переходный период кончился, они обязательны с 31 марта 2025 года. Смысл прямой: если на вашу страницу оплаты подсадят лишний скрипт, он прочитает поля формы до того, как номер уйдёт провайдеру.

Разница между первыми и последними строками — это разница между заполнить анкету на пару страниц и приглашать аудитора. Поэтому по умолчанию берут редирект или hosted fields; своя форма и тем более своё хранение — только когда без этого действительно нельзя.

3-D Secure: кто отвечает за спорную операцию

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

3-D Secure — шаг подтверждения на стороне банка, выпустившего карту: покупателя отправляют на страницу его банка, где он подтверждает платёж. Современная версия (EMV 3DS, её же называют 3DS2) в большинстве случаев проходит незаметно: провайдер передаёт банку данные об устройстве и поведении, банк оценивает риск и пропускает платёж без вопросов, а подтверждение просит только когда риск высокий.

Главное следствие деловое: при успешном подтверждении ответственность за мошенническую операцию переходит к банку-эмитенту, без подтверждения она остаётся на магазине. Именно поэтому 3DS включают не «для безопасности вообще», а чтобы не платить за чужие кражи; эквайер обычно этого и требует.

Для кода это означает, что платёж перестаёт быть одним вызовом. Провайдер в ответ может сказать не «оплачено» и не «отказ», а «нужно подтверждение» и дать адрес, куда отправить покупателя. Отсюда три требования к вашей модели:

  • Статус «ждёт подтверждения». Заказ в этом состоянии не оплачен и не отклонён, товар по нему не отгружают и место на складе держат с таймаутом. Считать «платёж создан» за «деньги получены» — самая частая ошибка в этом месте.
  • Результат приходит уведомлением, а не в ответе. Покупатель вернётся не всегда: закроет вкладку, потеряет сеть. Итог платежа приносит уведомление от провайдера (webhook), и обработчик этого уведомления обязан быть идемпотентным: одно и то же уведомление приходит повторно, это нормальная работа провайдера.
  • Таймаут и сверка. Платёж, который висит в «ждёт подтверждения» дольше нескольких минут, дожимают запросом статуса у провайдера, а не оставляют висеть. Раз в сутки — сверка своих платежей с выпиской провайдера: расхождение ловят так, а не по жалобам.

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

Требования: что они значат для кода

Формально стандарт — 12 требований; для разработчика они сводятся к нескольким привычкам:

  • Не хранить лишнего. PAN — только при доказанной необходимости, зашифрованный при хранении; SAD — никогда. При показе номер маскируют, и «первые 6 и последние 4» — это максимум, который разрешено показать, а не рекомендуемая маска: если в интерфейсе хватает четырёх последних цифр, показывайте только их. Первая часть номера, кстати, не всегда шестизначная — банковский префикс бывает и восьмизначным, так что правило формулируют как «не больше, чем префикс и последние четыре». Проверьте самое коварное место — логи: PAN в логе запроса или в стектрейсе — готовое нарушение. Это тот же принцип, что и с PII в логах.
  • Шифрование при передаче: TLS на всех путях карточных данных, включая внутренние.
  • Минимальный доступ: роли по принципу «нужно для работы», уникальные учётки (никаких общих админских), MFA для доступа в CDE.
  • Журналирование: кто, когда и к каким карточным данным обращался — аудит, защищённый от подмены.
  • Безопасная разработка: ревью кода, управление уязвимостями и зависимостями, тестовые среды без прод-данных карт.

В коде это выражается в модели данных: сущность платежа хранит токен, а не номер — тогда PAN негде случайно засветить (в логах, трейсах, дампах БД):

// В сервисе заказов нет ни поля pan, ни cvv — только суррогаты от провайдера.
record Payment(String token, String brand, String last4, Instant paidAt) {}

// Оплата: номер ушёл провайдеру, к нам вернулся токен.
paymentRepo.save(new Payment("tok_1a2b3c", "VISA", "1111", Instant.now()));

// Повторное списание (подписка, доплата) — по токену, не по номеру:
gateway.charge("tok_1a2b3c", amount);
// В сервисе заказов нет ни поля pan, ни cvv — только суррогаты от провайдера.
type Payment struct {
	Token  string
	Brand  string
	Last4  string
	PaidAt time.Time
}

// Оплата: номер ушёл провайдеру, к нам вернулся токен.
payments.Save(ctx, Payment{Token: "tok_1a2b3c", Brand: "VISA", Last4: "1111", PaidAt: time.Now()})

// Повторное списание (подписка, доплата) — по токену, не по номеру:
gateway.Charge(ctx, "tok_1a2b3c", amount)
// В сервисе заказов нет ни поля pan, ни cvv — только суррогаты от провайдера.
type Payment = { token: string; brand: string; last4: string; paidAt: Date };

// Оплата: номер ушёл провайдеру, к нам вернулся токен.
await paymentRepo.save({ token: 'tok_1a2b3c', brand: 'VISA', last4: '1111', paidAt: new Date() });

// Повторное списание (подписка, доплата) — по токену, не по номеру:
await gateway.charge('tok_1a2b3c', amount);
# В сервисе заказов нет ни поля pan, ни cvv — только суррогаты от провайдера.
@dataclass(frozen=True)
class Payment:
    token: str
    brand: str
    last4: str
    paid_at: datetime


# Оплата: номер ушёл провайдеру, к нам вернулся токен.
payment_repo.save(Payment("tok_1a2b3c", "VISA", "1111", datetime.now(timezone.utc)))

# Повторное списание (подписка, доплата) — по токену, не по номеру:
gateway.charge("tok_1a2b3c", amount)

Если PAN всё же проходит транзитом (собственная форма), для показа его маскируют. Ниже — предельный допустимый вариант: видны префикс и последние четыре, остальное скрыто.

static String maskPan(String pan) {
    return pan.replaceAll("(?<=\\d{6})\\d(?=\\d{4})", "*");
}
// 4111111111111111  ->  411111******1111

В регулярных выражениях Go нет просмотра назад, поэтому маску собирают срезами строки:

func maskPan(pan string) string {
	return pan[:6] + strings.Repeat("*", len(pan)-10) + pan[len(pan)-4:]
}
// 4111111111111111  ->  411111******1111
const maskPan = (pan) => pan.replace(/(?<=\d{6})\d(?=\d{4})/g, '*');
// 4111111111111111  ->  411111******1111
def mask_pan(pan: str) -> str:
    return re.sub(r"(?<=\d{6})\d(?=\d{4})", "*", pan)
# 4111111111111111  ->  411111******1111

А главное — PAN и CVV не должны попадать в объекты, которые уходят в логи и в тексты ошибок: их там ищут в первую очередь.

браузер покупателя форму рисует провайдер PAN мимо ваших систем провайдер хранит PAN, выдаёт токен в ответ tok_1a2b3c ваш сервис token, brand, last4 номера карты у вас нет повторное списание по токену, не по номеру

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

Токен провайдера — тоже ключ к деньгам

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

Отсюда правила, которые в статьях про токенизацию обычно опускают:

  • Токен не попадает в логи, адреса и события. Он живёт в платёжном сервисе, в одной таблице, и наружу выходит только как последние четыре цифры и тип карты. GET /payments?token=tok_… — та же ошибка, что номер карты в строке запроса.
  • Списание — внутренняя операция. Ручка «списать по токену» не должна быть доступна клиенту ни под какой ролью: иначе клиент, подобрав чужой токен, платит с чужой карты. Списание инициирует ваш сервис, а клиент видит только заказ.
  • Область действия токена суживают у провайдера. Большинство провайдеров умеют выдавать токен, действующий однократно, или ограниченный только повторными списаниями по подписке. Для разового платежа берут однократный, для подписки — привязанный к этому назначению, а не универсальный.
  • Удаление клиента отзывает токен. Удалить строку в своей базе недостаточно: пока токен действует у провайдера, по нему можно списать. Отзыв делается вызовом к провайдеру и входит в ту же процедуру удаления данных, что и остальные шаги.
  • Ручка списания под лимитом. Скорость вызовов ограничивают, а всплеск отказов от провайдера считают сигналом: перебор токенов выглядит именно так.

Тестовые среды и тестовые карты

«Тестовые среды без прод-данных карт» — строка из списка требований, за которой скрывается самая дорогая ошибка эксплуатации: боевой дамп, восстановленный в тестовый контур. В момент восстановления тестовый контур становится частью CDE со всеми требованиями, только без их выполнения — там нет ни сегментации, ни ограниченного доступа, а пароли обычно общие.

Что делают вместо этого.

Тестовые карты провайдера. У любого провайдера есть набор номеров для проверок: карта, которая всегда оплачивается, карта, которая всегда отклоняется, карта, которая требует подтверждения 3DS, карта с недостатком средств. Они не секретны, их можно и нужно держать в репозитории рядом с тестами — это единственные номера карт, которым место в коде.

Отдельные ключи и явный признак среды. Ключи тестового и боевого режима у провайдера разные, и их легко перепутать; тестовый ключ в проде выглядит особенно неприятно — платежи «проходят», а денег нет. Поэтому среда видна в самом ключе и проверяется при старте: сервис с тестовым ключом и признаком production не поднимается.

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

Проверка, что дамп не приехал. Полезен простой ночной контроль в тестовой базе: поиск строк, похожих на номер карты (тринадцать-девятнадцать цифр, проходящих проверку по алгоритму Луна), и сравнение числа клиентов с ожидаемым для тестовых данных. Он ловит и восстановленный дамп, и разработчика, который «залил немного реальных данных, чтобы проверить».

Если номер карты всё же попал в лог

Это происходит, и почти всегда одинаково: кто-то залогировал тело запроса, чтобы разобраться с отказом провайдера. Дальше важен порядок: сначала перекрыть, потом чистить, потом сообщать.

Перекрыть. Убрать запись и выкатить, а не «поправить в следующем релизе». Пока строка пишется, любая чистка бессмысленна.

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

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

Закрыть на будущее. Одного «больше так не делать» мало, потому что делать будет другой человек. Фильтр маскирования в самом журнальном приложении (шаблон на последовательности цифр с проверкой по алгоритму Луна), запрет логировать тело на платёжных путях, тест на маскирующую функцию и периодический поиск номеров в логах как контрольная мера — вот что действительно закрывает.

Дополнительно: при первом чтении можно пропустить

Глубже: уровни и проверкирасширенное

Глубина проверки зависит от объёма операций: от ежегодного самоопросника (SAQ) у малых до аудита на площадке сертифицированным аудитором (QSA) у крупных. Пороги задаёт каждая платёжная система отдельно и подтверждает банк-эквайер, поэтому точную цифру всегда уточняют у него. Порядок величин такой: у Visa и Mastercard верхний уровень начинается примерно с 6 млн операций по картам этой системы в год, следующий — с миллиона, а подавляющее большинство интернет-магазинов попадает в нижний уровень, где хватает самоопросника.

Практическое следствие для команды важнее самих порогов: какой именно SAQ вы заполняете, определяется не оборотом, а архитектурой приёма платежей — редирект на провайдера, его iframe или собственная форма дают разные списки требований. Решение «как принимаем карты» стоит принимать вместе с тем, кто отвечает за соответствие, — до реализации, а не после.

Глубже: российский контекст: НСПК, МИР и 152-ФЗрасширенное

Сам стандарт создан международными платёжными системами, а операции по картам внутри России обрабатываются через Национальную систему платёжных карт: и карты международных систем, выпущенные в России, и карты «МИР», для которых НСПК ещё и платёжная система. Практически это значит, что требования к вам приходят не от Visa напрямую, а от банка-эквайера и от правил НСПК, и формулировки могут отличаться от того, что написано в англоязычной версии стандарта.

Отсюда два практических следствия. Первое: перед выбором способа приёма платежей требования уточняют у эквайера — какой опросник он ждёт, какие документы просит, требует ли 3DS обязательно. Второе: у карт «МИР» свои тонкости в потоке подтверждения и в наборе тестовых карт, поэтому провайдера выбирают из тех, кто работает и с ними, а тестовые сценарии прогоняют на карте каждой системы отдельно.

И пересечение с законом. Номер карты сам по себе — не персональные данные в смысле 152-ФЗ, но он никогда не лежит в одиночестве: рядом фамилия держателя, телефон, адрес доставки, и всё вместе подпадает под закон целиком. Значит, к платёжным данным применяются оба списка требований сразу: правила стандарта — к номеру и подтверждающим данным, закон — к персональным данным вокруг платежа, включая требование первичного хранения в России. Выбор платёжного провайдера поэтому не только про удобство API: он определяет, где окажутся данные ваших покупателей.

Коротко

  • PCI DSS обязателен для всех, кто хранит, обрабатывает или передаёт карточные данные; это договорное требование платёжных систем со штрафами через банк.
  • CVV и прочие SAD после авторизации не хранятся нигде — ни в базе, ни в кэше, ни в логах; PAN — только при необходимости, зашифрованный, а при показе не больше префикса и последних четырёх. Самое частое нарушение — номер карты в логах.
  • Главный ход — сузить CDE: токенизация через платёжного провайдера выводит ваши сервисы из области аудита; при собственной обработке — жёсткая сегментация.
  • Для кода это: минимум данных, TLS везде, наименьшие привилегии, аудит доступа, чистые логи и тестовые среды без прод-карт.
  • Способ приёма платежей (redirect / iframe / своя форма) определяет глубину аудита — выбирайте его осознанно и заранее.
  • 3-D Secure переносит ответственность за мошенническую операцию на банк-эмитента, а в коде делает платёж двухшаговым: статус «ждёт подтверждения», идемпотентный обработчик уведомления, таймаут и ночная сверка с выпиской.
  • Токен провайдера — ключ к списанию, а не безобидный суррогат: не в логи и не в адреса, ручка списания только внутренняя, при удалении клиента токен отзывают у провайдера.
  • В тестовой среде только тестовые карты и тестовые ключи; боевой дамп не восстанавливают, а если берут срез — обезличивают и вычищают токены.
  • Номер в логе: сначала убрать запись и выкатить, потом вычистить индексы, архивы и копии, потом сообщить эквайеру; закрывает это маскирующий фильтр с проверкой по алгоритму Луна и тест на него.
  • В России операции идут через НСПК, требования приходят от эквайера, а персональные данные вокруг платежа остаются под 152-ФЗ вместе с требованием первичного хранения в стране.

Что почитать дальше