Формы оплаты на фронтенде: три подхода и цена каждого
Собрать поле для номера карты вручную — значит попасть под полный объём требований PCI DSS. Разбираем три способа принять оплату во фронтенде и считаем, чем платишь за каждый.

Задача звучит просто: пользователь вводит данные карты, нажимает «Оплатить», деньги списываются. Очевидное решение — сверстать поля input для номера карты, срока и CVV, отправить их на свой бэкенд и дальше в банк. Как только данные карты прошли через ваш код, вы попадаете под полный объём требований PCI DSS уровня SAQ D — с аудитами, сканированием инфраструктуры и обязательствами по хранению. Для большинства проектов это неподъёмно. Поэтому реальный выбор — не «как сверстать поле», а «как вообще не дать карте попасть в ваш периметр».
Разберём три подхода, которые закрывают эту задачу по-разному: редирект на страницу провайдера, iframe-виджет (drop-in) и встроенные поля через SDK (hosted fields / Elements). У каждого своя цена — в контроле над версткой, в объёме PCI-обязательств и в количестве кода.
Что вообще решаем
Все три подхода объединяет одно: настоящие данные карты не касаются вашего JavaScript и вашего сервера. Разница — в том, где физически рендерится поле ввода и насколько вы управляете его видом.
- Редирект — вы уводите пользователя на домен платёжного провайдера, он платит там, возвращается обратно с результатом.
- Drop-in виджет — провайдер отдаёт готовый блок (обычно iframe), вы вставляете его на страницу почти без настройки.
- Hosted fields / Elements — провайдер даёт отдельные поля-iframe, которые вы расставляете и стилизуете сами, собирая из них форму, неотличимую от собственной.
Токенизация — общий механизм для всех трёх. Провайдер принимает карту на своей стороне и возвращает вам одноразовый или многоразовый токен1, которым вы уже оперируете на бэкенде. Ваш сервер видит токен, а не PAN.
Редирект: минимум ответственности
Классическая схема. Вы формируете запрос на создание платежа на своём бэкенде, получаете URL и отправляете туда пользователя — через window.location.assign(url) или обычную форму с method="post". Оплата целиком происходит на стороне провайдера, назад пользователь возвращается на ваш return_url, а факт оплаты вы подтверждаете вебхуком, а не редиректом (полагаться только на возврат браузера нельзя — пользователь может закрыть вкладку).
Плюс: PCI-обязательства сводятся к минимальной анкете (обычно SAQ A), потому что данные карты вообще не проходят через ваш фронтенд. Минус: вы теряете контроль над версткой шага оплаты и над воронкой — часть пользователей отваливается на переходе.
Drop-in виджет: быстро и в вашем UI
Провайдер отдаёт JS-скрипт, который монтирует готовую форму в указанный контейнер. Пользователь остаётся на вашем домене, поля рендерятся внутри iframe провайдера, вы получаете токен через колбэк. Кода минимум, при этом воронка не рвётся редиректом.
<!-- Схематично: монтирование drop-in виджета в контейнер -->
<div id="payment-widget"></div>
<script>
// Точные имена методов, объектов и параметров смотрите
// в документации конкретного провайдера — они различаются
const widget = createPaymentWidget({
publicKey: 'YOUR_PUBLIC_KEY',
container: '#payment-widget',
onSuccess: (token) => {
// токен уходит на ВАШ бэкенд, а не напрямую в банк
fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentToken: token })
});
},
onError: (err) => console.error(err)
});
</script>Компромисс: виджет ограничивает стилизацию тем, что провайдер разрешил в своих настройках. Обычно можно поменять цвета, шрифт и радиусы, но не переставить поля произвольно.
Hosted fields / Elements: свой UI, чужой iframe
Самый гибкий вариант. Провайдер даёт SDK, который создаёт отдельные iframe для каждого чувствительного поля — номер карты, срок, CVV. Вы расставляете их в собственной разметке и стилизуете через API SDK, а не через свой CSS (напрямую до содержимого iframe вы не дотянетесь из-за политики безопасности домена). Для пользователя это выглядит как единая ваша форма.
При сабмите вы вызываете метод SDK, который забирает данные из iframe, отправляет их провайдеру и возвращает токен. Ваш JS в момент ввода карту не видит.
Главный обман интуиции: поле выглядит вашим, но им не является. Стилизовать содержимое чужого iframe напрямую нельзя — только через параметры, которые заложил провайдер. Если дизайн требует чего-то, чего SDK не поддерживает, вы упрётесь в стену уже после того, как приняли решение.
PCI-обязательства здесь чуть выше редиректа: обычно применяется расширенная анкета (часто SAQ A-EP), потому что ваша страница управляет загрузкой платёжного скрипта. Точный уровень зависит от провайдера и от того, как именно вы интегрируетесь, — сверяйте с требованиями вашего эквайрера.
Сравнение
| Критерий | Редирект | Drop-in виджет | Hosted fields |
|---|---|---|---|
| Контроль над версткой | Нет | Ограниченный | Почти полный |
| Объём кода | Минимум | Мало | Средний |
| PCI-нагрузка | Самая низкая (SAQ A) | Низкая | Выше (часто SAQ A-EP) |
| Пользователь остаётся на сайте | Нет | Да | Да |
| Потери на переходе | Есть | Нет | Нет |
| Стоимость поддержки | Низкая | Низкая | Средняя |
Когда что выбирать
- Редирект — когда важнее всего минимизировать PCI-обязательства и скорость запуска, а единичные потери на переходе допустимы. Разовые платежи, донаты, оплата счёта по ссылке.
- Drop-in — когда нужен платёж на своём домене без возни, а фирменный стиль формы не критичен. Стандартная корзина интернет-магазина среднего размера.
- Hosted fields — когда форма оплаты часть тщательно выверенного UX и потери на любом лишнем шаге стоят дорого. Готовьтесь платить объёмом интеграции и повышенными PCI-требованиями.
Что ломается
Независимо от подхода, несколько мест регулярно приводят к инцидентам.
- Доверие к редиректу вместо вебхука. Пользователь оплатил и закрыл вкладку до возврата — заказ остался неоплаченным в вашей базе, хотя деньги списаны. Источник истины по статусу платежа — только вебхук с проверкой подписи на бэкенде.
- Двойное списание. Пользователь жмёт «Оплатить» дважды или обновляет страницу. Блокируйте кнопку на время запроса и используйте идемпотентный ключ, если провайдер его поддерживает.
- 3-D Secure. Подтверждение через банк открывает ещё один редирект или модальное окно. Если вы жёстко управляете навигацией во фронтенде (SPA-роутер), 3DS-возврат может «потеряться». Тестируйте сценарий с картой, требующей подтверждения.
- CSP. Строгая Content-Security-Policy блокирует загрузку скрипта и iframe провайдера. Домены провайдера нужно явно добавить в
script-srcиframe-src— иначе форма молча не отрисуется. - Отсутствие обработки отказа. Недостаточно средств, банк отклонил, истёк срок сессии оплаты — всё это нормальные исходы, а не исключения. Обрабатывайте каждый явно, иначе пользователь увидит зависшую кнопку.
Что делать дальше
Начните с двух вопросов к бизнесу: насколько критична фирменная верстка шага оплаты и какой уровень PCI вы готовы поддерживать. Ответ отсекает лишние варианты. Дальше — откройте документацию выбранного провайдера и найдите три вещи: какой SAQ применяется к этому способу интеграции, как проверяется подпись вебхука и как в песочнице протестировать 3-D Secure и отказ. Проверку статуса заказа переносите на серверный вебхук с первого дня, а не «потом». Именно это, а не верстка формы, отличает работающую оплату от источника постоянных спорных заказов.
Карточки для маркетплейсов: Таблица товаров и папка фотографий на входе — комплект слайдов и описаний на выходе, по требованиям Wildberries и Ozon.
Спросить за 5 ₽Источники: PCI Security Standards Council — Self-Assessment Questionnaires
Частые вопросы
Можно ли всё-таки собрать поле ввода карты самому и не использовать iframe провайдера?
А если у меня SPA на React или Vue — что-то меняется?
Форма не отрисовывается, в консоли ошибки про заблокированные ресурсы — что делать?
Насколько сильно можно стилизовать hosted fields под свой дизайн?
Оплата прошла, деньги списались, а заказ в базе висит неоплаченным — почему?
Комиссии и точные PCI-требования — где смотреть?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.