Зелёный CI не спасает платежи: 5 граблей интеграции без песочницы
Команда добавляла новый способ оплаты через банк: клиент уходил по ссылке в приложение банка, подтверждал платёж, а дальше система должна была получить уведомление о результате. Тесты были зелёные, но после третьей волны запуска у части мерчантов деньги у плательщика уже были списаны, а платёж у продавца зависал. Для бизнеса это не история про «технический долг» — это прямые потери на миллионы рублей и испорченные отношения с партнёрами.
Платёжные интеграции — это всегда работа с внешними системами, которые живут по своим правилам. Банк может прислать callback через минуту, а может через час. Формат ответа может незначительно измениться без предупреждения. Сетевая инфраструктура между дата-центрами иногда ведёт себя непредсказуемо. И когда разработчики полагаются только на юнит-тесты и моки, они неизбежно пропускают целые классы проблем, которые проявляются только в реальном взаимодействии с живой системой.
Почему зелёный CI создаёт ложное чувство безопасности
Стандартный подход к тестированию платёжных интеграций выглядит так: разработчик пишет код, оборачивает внешние вызовы в интерфейсы, создаёт моки, которые возвращают заранее заготовленные ответы, и запускает тесты в пайплайне. CI горит зелёным — можно деплоить. Проблема в том, что моки отражают наше представление о том, как должен работать внешний сервис, а не то, как он работает на самом деле.
В случае с банковской интеграцией команда замокала успешный ответ от банка, замокала ошибку авторизации, даже замокала таймаут. Но они не учли, что банк может прислать HTTP 200 с телом, в котором статус платежа указан как pending, а не success. Или что банк может прислать callback на URL, который уже успел измениться из-за особенностей маршрутизации в Kubernetes. Или что банк может дублировать уведомления, и идемпотентность должна работать не на уровне «один запрос — один ответ», а на уровне бизнес-логики.
Реальные сценарии, которые не ловятся моками
Первый сценарий — гонка состояний. Платёж может быть подтверждён пользователем в приложении банка, но уведомление придёт раньше, чем пользователь вернётся на страницу мерчанта. Если система не готова обработать callback до того, как пользовательский браузер сделает редирект, возникает состояние гонки, которое в моках просто не воспроизводится.
Второй сценарий — частичная недоступность. Банк может отвечать на запросы создания платежей, но не обрабатывать запросы статуса. Или наоборот. В тестах с моками всё всегда либо полностью работает, либо полностью не работает. В реальности же внешние системы деградируют частично, и именно эти частичные отказы создают самые сложные для диагностики проблемы.
Третий сценарий — изменение формата данных без изменения версии API. Банк добавляет новое поле в ответ, которое не описано в документации. Ваш парсер, написанный с расчётом на строгую структуру, падает с ошибкой десериализации. Мок такого поля не содержит, потому что разработчик мока ориентировался на документацию, а не на реальные ответы.
Песочница как единственный способ увидеть реальность
Песочница — это не тестовый стенд банка с урезанным функционалом. Это полноценная среда, максимально приближенная к боевой, где можно проводить интеграционное тестирование с реальными сценариями. В идеальном мире песочница банка позволяет создавать платежи, проводить их через все состояния и получать те самые неожиданные ответы, которые никогда не попали бы в моки.
Но даже если банк предоставляет песочницу, её использование требует дисциплины. Недостаточно просто прогнать happy path и убедиться, что платёж создаётся и подтверждается. Нужно целенаправленно провоцировать граничные условия: обрывать соединение на разных этапах, отправлять запросы с невалидными подписями, симулировать двойные уведомления, проверять поведение системы при длительных задержках.
Как выстроить процесс тестирования с песочницей
Первый шаг — автоматизировать не только юнит-тесты, но и интеграционные сценарии в песочнице. Это должны быть тесты, которые запускаются регулярно, а не разово перед релизом. Внешние системы меняются постоянно, и то, что работало вчера, может сломаться сегодня из-за изменений на стороне банка.
Второй шаг — добавить в тестовые сценарии хаос-инженерию на уровне сетевого взаимодействия. Искусственно замедлять ответы, обрывать соединения, возвращать некорректные HTTP-статусы. Современные инструменты, такие как Toxiproxy, позволяют делать это в контролируемой среде, не затрагивая боевые системы.
Третий шаг — логировать все взаимодействия с песочницей и автоматически сравнивать формат ответов с ожидаемым. Если банк добавил новое поле или изменил тип существующего, вы должны узнать об этом из алерта, а не из инцидента на проде.
Что делать, когда песочницы нет
Реальность такова, что многие банки и платёжные провайдеры не предоставляют полноценной песочницы. Есть тестовые стенды, которые эмулируют только успешные сценарии, или вообще только документация и боевой API. В таких условиях приходится выкручиваться.
Один из подходов — запись и воспроизведение трафика. Вы проводите ручное тестирование на боевом API с тестовыми суммами, записываете все запросы и ответы, а затем используете эти записи для автоматизированного тестирования. Инструменты вроде VCR или самописные прокси позволяют создать «песочницу на основе реальности».
Другой подход — контрактное тестирование. Вы фиксируете контракт взаимодействия с внешним сервисом в виде формальной спецификации и проверяете, что ответы сервиса соответствуют этому контракту. Если банк меняет API без предупреждения, контрактные тесты это обнаружат. Но здесь есть нюанс: контракт должен описывать не только структуру, но и допустимые значения, включая те, которые не описаны в документации, но встречаются на практике.
Стратегия тестирования без полноценной песочницы
Если песочница ограничена, нужно максимально использовать то, что есть. Даже тестовый стенд с урезанным функционалом лучше, чем полностью замоканное окружение. Комбинируйте тестовый стенд для базовых сценариев с записанными ответами для граничных случаев.
Создайте отдельный набор тестов, который запускается против реального API с минимальными суммами. Эти тесты не должны быть частью CI-пайплайна, потому что они зависят от доступности внешнего сервиса и реальных денег. Но они должны запускаться регулярно, например раз в сутки, и алертить о любых отклонениях.
Архитектурные решения, которые снижают риски
Проблемы интеграции — это не только вопрос тестирования, но и вопрос архитектуры. Система должна быть спроектирована так, чтобы некорректное поведение внешнего сервиса не приводило к потере данных или денег.
Первый принцип — идемпотентность на всех уровнях. Каждая операция должна быть безопасной для повторного выполнения. Если банк прислал два одинаковых уведомления о платеже, система должна обработать только одно, а второе проигнорировать. Это требует хранения идентификаторов обработанных операций и проверки перед применением изменений.
Второй принцип — асинхронная обработка с гарантией доставки. Нельзя полагаться на то, что callback от банка дойдёт и будет обработан синхронно. Нужна очередь с персистентным хранением, механизмом повторов и мониторингом глубины очереди. Если callback не пришёл в ожидаемое время, система должна主动 запросить статус платежа.
Третий принцип — изоляция состояний. Платёж должен проходить через явно определённые состояния, и переходы между ними должны быть атомарными. Не должно быть ситуации, когда деньги списаны, а платёж в системе находится в неопределённом состоянии.
Реализация паттерна Saga для распределённых транзакций
Платёжная операция — это распределённая транзакция, в которой участвуют ваша система, банк и иногда ещё несколько сервисов. Классический подход с двухфазным коммитом здесь не работает, потому что внешние системы не поддерживают распределённые транзакции. Вместо этого нужно использовать паттерн Saga, где каждый шаг имеет компенсирующую операцию.
Например, если платёж был создан в банке, но подтверждение от пользователя не получено в течение таймаута, система должна отменить платёж через API банка. Если отмена не удалась, нужно повторить попытку с экспоненциальной задержкой и в конечном счёте алертить оператора для ручного разрешения ситуации.
Мониторинг, который видит проблемы до того, как их заметят пользователи
Даже с идеальным тестированием проблемы в проде неизбежны. Вопрос в том, как быстро вы о них узнаете. Мониторинг платёжных систем должен быть многоуровневым.
Первый уровень — технический мониторинг: доступность API, время ответа, количество ошибок. Это базовые метрики, которые должны быть у любого сервиса.
Второй уровень — бизнес-мониторинг: количество успешных платежей, количество платежей в подвешенном состоянии, распределение по статусам. Если доля платежей в статусе pending резко выросла, это повод для расследования, даже если технические метрики в норме.
Третий уровень — сверка с внешними системами. Раз в час или раз в день нужно сравнивать реестр платежей в вашей системе с реестром в банке. Расхождения — это либо потерянные платежи, либо дубли, либо ошибки в бизнес-логике.
Какие метрики действительно важны
Не стоит собирать всё подряд. Для платёжной системы критичны несколько ключевых метрик: время от создания платежа до получения финального статуса, доля платежей, требующих ручного вмешательства, количество расхождений при сверке с банком, частота срабатывания компенсирующих операций.
Эти метрики должны быть визуализированы на дашборде, который видит вся команда, а не только DevOps-инженеры. Когда разработчик видит, что после его релиза выросла доля платежей в неопределённом статусе, он может среагировать быстрее, чем если бы он узнал об этом из тикета от поддержки.
Культура работы с инцидентами
Платёжные инциденты — это не просто баги. Это события, которые стоят денег и репутации. Поэтому процесс работы с ними должен отличаться от обычного баг-трекинга.
Каждый инцидент должен завершаться post-mortem, в котором команда отвечает на вопросы: что произошло, почему мы это не заметили на этапе тестирования, как сделать так, чтобы подобное не повторилось. И ответ «добавим ещё один тест» — это только часть решения. Часто проблема глубже: в архитектуре, в процессе, в коммуникации с внешним провайдером.
Важно создать культуру, в которой о проблемах говорят открыто, без поиска виноватых. Если разработчик боится признаться, что его код вызвал потерю платежей, он будет скрывать проблемы до последнего, и масштаб инцидента вырастет. Если же команда воспринимает инциденты как возможность улучшить систему, каждый такой случай делает продукт надёжнее.
Как проводить post-mortem с пользой
Хороший post-mortem — это не отчёт для руководства, а рабочий документ команды. Он должен содержать хронологию событий с точностью до минуты, анализ корневой причины, список действий, которые уже предприняты, и список превентивных мер, которые нужно реализовать.
Превентивные меры должны быть конкретными и измеримыми. Не «улучшить тестирование», а «добавить интеграционный тест, который проверяет обработку дублирующихся callback-уведомлений с одинаковым идентификатором платежа». Не «настроить мониторинг», а «добавить алерт, который срабатывает, если количество платежей в статусе pending превышает 5% от общего числа за последние 15 минут».
Источники
- Martin Fowler — Patterns of Enterprise Application Architecture — фундаментальная работа по архитектурным паттернам, включая распределённые транзакции и Saga
- Toxiproxy — TCP proxy для симуляции сетевых проблем — инструмент для хаос-инженерии на уровне сети
- Stripe API Documentation — Idempotency — пример реализации идемпотентности в платёжном API
- Google SRE Book — Monitoring Distributed Systems — подходы к мониторингу распределённых систем от Google
- Cindy Sridharan — Testing in Production — о безопасном тестировании в продакшене
- Adrian Cockcroft — Chaos Architecture — доклад о принципах хаос-инженерии
- PACT — Consumer-Driven Contract Testing — инструмент для контрактного тестирования
- VCR — Record and Replay HTTP Interactions — библиотека для записи и воспроизведения HTTP-взаимодействий
Генерация изображения
- Модель:
qwen-image-2.0-pro - Провайдер:
alibaba