Повтор платежа после таймаута или ошибки не должен повторно уменьшать бонусный баланс. Двойное списание появляется, когда бонусы изменяются при каждой попытке оплаты, а система не различает новую бизнес-операцию и повтор того же запроса. Надежная схема использует неизменяемый ledger и уникальный ключ операции.

Не исправляйте баланс одной командой до сверки истории. Найдите заказ, все payment attempt, bonus operation и webhook по времени и идентификаторам. Определите, было ли второе списание отдельной записью или одно изменение применилось дважды.

Что проверить в первую очередь

Начните с одного воспроизводимого сценария. Зафиксируйте точное время, идентификатор объекта, пользователя или операции, версию приложения и последнее известное рабочее состояние. Не меняйте несколько параметров одновременно: один контролируемый шаг должен подтверждать или исключать одну гипотезу. Перед работой с данными и конфигурацией подготовьте резервную копию и проверенный способ отката.

  • Сопоставьте order ID, payment attempt ID, idempotency key и bonus operation ID.
  • Проверьте, списываются бонусы при создании платежа, подтверждении или обоих событиях.
  • Уточните состояние первой попытки: failed, unknown, authorized или captured.
  • Проверьте повторную доставку webhook и ручной retry пользователя.
  • Сверьте текущий баланс с суммой неизменяемых операций ledger.

Почему возникает проблема

Внешний симптом часто появляется дальше по цепочке, чем первичная ошибка. Интерфейс может показывать неверное состояние из-за backend, очереди, кеша, прав доступа, фоновой задачи или внешнего API. Поэтому важно проследить данные от источника до результата и найти первую точку расхождения, а не исправлять только последнее сообщение об ошибке.

  • Каждый retry создает новую bonus debit без ссылки на исходный заказ.
  • Frontend повторяет запрос после таймаута с новым ключом.
  • Webhook и синхронный callback оба подтверждают списание.
  • Проверка существующей операции и вставка выполняются вне одной транзакции.
  • Резерв бонусов не освобождается после окончательной ошибки оплаты.

Пошаговая диагностика

Диагностику проводите на тестовой записи или отдельном окружении. В журналах скрывайте токены, пароли, персональные данные и содержимое документов. Для каждого шага сохраняйте измеримый результат: код ответа, версию записи, идентификатор события, состояние процесса, контрольную сумму или время выполнения. Так можно отличить подтвержденную причину от случайного совпадения.

  • Постройте временную линию всех событий и изменения баланса по одному заказу.
  • Проверьте уникальные индексы на business operation key и тип операции.
  • Смоделируйте timeout после записи в базе, но до ответа клиенту.
  • Отправьте один webhook повторно и проверьте отсутствие нового эффекта.
  • Сравните вычисленный по ledger баланс с кешированным полем пользователя.

Резервирование и списание бонусов

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

  • Reserve блокирует доступную сумму по уникальному order bonus key.
  • Capture превращает существующий резерв в окончательный debit один раз.
  • Release освобождает резерв после подтвержденного отказа или истечения срока.
  • Ledger не редактируется задним числом; исправление создается компенсирующей записью.
  • Текущий баланс выводится из ledger или регулярно с ним сверяется.

Как исправить проблему

Исправление делите на небольшие обратимые изменения. Сначала устраните подтвержденную причину, затем повторите исходный сценарий и проверьте соседние функции. Массовое обновление данных запускайте на ограниченной выборке с отчетом и только после сверки расширяйте на весь объем. Не отключайте авторизацию, проверку сертификатов, валидацию или аудит ради быстрого исчезновения симптома.

  • Введите стабильный idempotency key для бонусной операции на уровне заказа.
  • Добавьте уникальное ограничение и выполняйте проверку с записью в одной транзакции.
  • Сделайте webhook и callback командами перехода состояния, безопасными при повторе.
  • Разделите bonus reserved, captured и released.
  • Восстановите пострадавшие балансы компенсирующими credit после dry-run отчета.

Безопасный порядок внедрения

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

Как проверить результат

Разовый успешный тест недостаточен. Повторите операцию, проверьте крайние значения, одновременные действия и восстановление после перезапуска или временного сбоя. Для важного сценария сохраните автоматический тест либо короткий регрессионный чек-лист. Итог должен подтверждаться не только интерфейсом, но и состоянием базы, очереди, внешнего сервиса и журналом действий.

  • Двойной клик и сетевой retry создают один резерв.
  • Повторный webhook не меняет captured-операцию.
  • Неуспешный платеж освобождает бонусы только один раз.
  • Повторная реальная оплата того же заказа использует существующий резерв по правилам сценария.
  • Баланс пользователя сходится с суммой ledger и отчетом программы лояльности.

Типичные ошибки при исправлении

  • Проверять дубль только в приложении без уникального ограничения базы.
  • Использовать payment attempt ID как ключ бизнес-списания, если попыток может быть несколько.
  • Перезаписывать сумму старой ledger-записи.
  • Возвращать бонусы по таймауту до выяснения состояния платежа.
  • Считать webhook одноразовым и не тестировать повторную доставку.

Как предотвратить повторение

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

  • Применяйте idempotency к каждому денежному и бонусному эффекту.
  • Мониторьте несколько debit одного типа на один заказ.
  • Регулярно сверяйте агрегированный баланс с ledger.
  • Добавьте chaos-тесты таймаутов между записью и ответом.
  • Храните correlation ID от интерфейса до платежа и бонусной операции.

Что контролировать после выпуска

  • Количество успешных и ошибочных операций в разрезе версии, канала и типа сценария.
  • Возраст необработанных записей, длину очередей, число повторных попыток и долю окончательных отказов.
  • Расхождение между пользовательским статусом и фактическим состоянием в базе или внешней системе.
  • Появление новых кодов ошибок после релиза и изменение времени выполнения ключевой операции.
  • Сигналы от поддержки и бизнес-метрики, которые могут показать скрытый частичный сбой.

Что подготовить для технического разбора

  • Описание ожидаемого и фактического поведения с точной последовательностью действий.
  • Время проблемы, идентификатор тестового объекта и версии затронутых компонентов.
  • Фрагменты журналов до и после ошибки без секретов и персональных данных.
  • Перечень последних изменений и уже выполненных проверок.
  • Безопасный доступ к тестовой среде либо способ воспроизвести сбой без влияния на клиентов.

Частые вопросы

Можно ли просто запретить кнопку после первого клика?

Это улучшает интерфейс, но не защищает от сетевого retry, webhook и параллельных запросов.

Как вернуть ошибочно списанные бонусы?

Компенсирующей credit-операцией после сверки, сохраняя исходную историю.

Когда освобождать резерв при неизвестном платеже?

После проверки состояния у провайдера или по безопасной процедуре сверки, а не только по клиентскому таймауту.

Когда нужна помощь специалиста

Если бонусы списываются повторно, я могу восстановить цепочку заказа, оплаты и ledger, внедрить идемпотентные переходы и подготовить корректирующий отчет. Для оценки нужны обезличенные ID одного заказа и связанные операции.