Зелёный 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 минут».

Источники

  1. Martin Fowler — Patterns of Enterprise Application Architecture — фундаментальная работа по архитектурным паттернам, включая распределённые транзакции и Saga
  2. Toxiproxy — TCP proxy для симуляции сетевых проблем — инструмент для хаос-инженерии на уровне сети
  3. Stripe API Documentation — Idempotency — пример реализации идемпотентности в платёжном API
  4. Google SRE Book — Monitoring Distributed Systems — подходы к мониторингу распределённых систем от Google
  5. Cindy Sridharan — Testing in Production — о безопасном тестировании в продакшене
  6. Adrian Cockcroft — Chaos Architecture — доклад о принципах хаос-инженерии
  7. PACT — Consumer-Driven Contract Testing — инструмент для контрактного тестирования
  8. VCR — Record and Replay HTTP Interactions — библиотека для записи и воспроизведения HTTP-взаимодействий

Генерация изображения

  • Модель: qwen-image-2.0-pro
  • Провайдер: alibaba