
2 сентября 2026 г.4 минуты чтенияАдминистратор
Тайм-аут после списания: где заканчивается защита идемпотентности
Как связать ключ API с реальной операцией, пережить тайм-аут и не превратить повтор запроса в новое списание.
Самый дорогой ответ платёжного API иногда выглядит как отсутствие ответа. Сервер не дождался результата, но это ничего не говорит о том, успел ли провайдер выполнить списание. Новый случайный ключ помогает получить новый HTTP-ответ и одновременно может создать вторую операцию. Чтобы повтор был безопасным, сначала придётся договориться, что именно остаётся тем же самым.
Два сотрудника делают один возврат
Представим более тихую ошибку: поддержка возвращает 20 евро, а финансовый оператор почти одновременно возвращает ещё 20 по тому же обращению. Ключи API разные, оба запроса технически корректны. Идемпотентность провайдера здесь не распознает, что сотрудники выполняют одно поручение. Защита должна начинаться с внутреннего дела возврата и уникального разрешения на его исполнение.
Для согласованного возврата заведите запись до обращения к провайдеру. Она связывает обращение клиента, исходный платёж, сумму и основание. Второй сотрудник должен увидеть уже созданное действие, а не создать новую сущность. При этом два действительно отдельных частичных возврата законны: например, за два возвращённых товара. Система обязана различать повтор поручения и новое поручение, поэтому сравнения одной суммы недостаточно.
Начните с определения одной операции
Предлагаемая модель разделяет номер заказа и номер действия над платежом. Заказ может включать авторизацию, несколько разрешённых частичных списаний и отдельные возвраты. Поэтому «номер заказа» слишком широк для всех запросов сразу, а новый случайный ключ при каждом HTTP-вызове слишком узок. Сначала создайте устойчивую внутреннюю запись конкретного действия.
Например, условный заказ Q-731 предполагает списание 48 евро. Внутренний бизнес-ключ обозначает «Q-731, списание, часть 1». С ним связаны сумма, валюта, получатель, аккаунт провайдера и версия намерения. Для обращения к API создаётся отдельный непрозрачный ключ, который сохраняется до первой отправки. Этот же ключ используют повторный клик, фоновый процесс и восстановившийся после сбоя сервер. Персональные данные в него включать не нужно.
Документация Stripe по идемпотентным запросам показывает, почему важно учитывать версию API: для API v1 повтор возвращает сохранённый результат после начала выполнения, включая некоторые ошибочные ответы. Правила срока хранения ключа и сравнения параметров также входят в контракт. Не считайте любой ключ бессрочным запретом на повторную операцию.
Состояние «результат неизвестен» должно быть явным
- 1.Подготовлено. Платформа сохранила действие, его параметры и ключ провайдера. Ограничение уникальности не позволяет двум обработчикам создать одно намерение независимо.
- 2.Отправлено. Один исполнитель получил право выполнить запрос; остальные видят существующую операцию и не создают новую.
- 3.Результат неизвестен. Ответ потерян или недостаточен. Это отдельное рабочее состояние, а не автоматически подтверждённый отказ.
- 4.Результат установлен. Ответ, проверенное событие или предусмотренная сверка подтверждают исход. Платформа фиксирует его и выполняет положенное бизнес-действие.
Названия состояний здесь условные. В конкретной системе понадобится различать авторизацию, списание, отмену и другие этапы. Существенно другое: команда должна видеть незавершённость и знать разрешённый способ её устранения. Не заменяйте неизвестность новым ключом ради получения «свежего» ответа.
Что именно можно повторить
Перед повтором проверьте, что речь идёт о той же операции, с теми же значимыми параметрами и в пределах правил провайдера. У PayPal поддержка PayPal-Request-Id и срок хранения зависят от API. Повтор может вернуть актуальный статус предыдущего запроса. Это ещё одна причина проверять контракт конкретного метода, а не предполагать одинаковое поведение любых платёжных SDK.
Если клиент изменил состав заказа и сумма стала 53 евро, это уже требует нового решения о платеже. Сначала выясните судьбу прежнего действия на 48 евро. Только затем определите, нужна ли отмена, доплата или новая операция в соответствии с продуктовым процессом и согласием клиента. Простая замена суммы в повторном запросе скрывает важное изменение бизнес-намерения.
Для собственного сервиса полезно сохранять отпечаток значимых параметров и отклонять конфликтующие обращения с одним внутренним ключом. Заранее решите, какие поля значимы: сумма и получатель точно требуют внимания, а технический идентификатор трассировки обычно не меняет смысл платежа. Дайте поддержке понятное сообщение о конфликте, не раскрывая секреты и полные платёжные реквизиты.
Граница защиты проходит по договору API
Adyen отдельно описывает область действия ключей, включая отсутствие общей проверки дубликатов между некоторыми региональными конечными точками. Поэтому перенос запроса на другой маршрут нельзя автоматически считать тем же защищённым повтором. Тем более одинаковая строка ключа у двух независимых провайдеров сама по себе не создаёт общей координации.
Храните внутреннюю историю дольше, чем живёт сетевой запрос, и определите процедуру для старых неразрешённых операций. После окончания гарантированного окна провайдера сначала нужна сверка. Если исход установить не удаётся, назначьте владельца исключения и эскалацию вместо бесконечных попыток.
Не прячьте ручные исправления
Оператору бывает удобнее снять блокировку зависшего действия, чем ждать сверку. Сделайте такой переход заметным: кто разрешил продолжение, какое доказательство установило прежний исход, какая новая операция заменяет прежнюю. Простое редактирование статуса в базе уничтожает причинную связь и оставляет следующей смене правдоподобную, но неверную историю.
Полезный приём на проектном разборе: перечислить все места, способные запустить действие. Кнопка покупателя, задача по расписанию, повтор SDK, консоль поддержки, восстановление очереди и ручной скрипт должны встречаться в одной модели намерений. Иначе каждый компонент будет правильно предотвращать свои дубли, а система в целом продолжит создавать чужие.
При следующем разборе интеграции начните с самого простого вопроса: где хранится разрешённое поручение на возврат или списание? Если ответом служит только ключ в HTTP-запросе, защита начинается слишком поздно.
Комментарии · 0
Комментариев пока нет — будьте первым.
Войти · войдите, чтобы оставить комментарий.