АналитикаСистемный анализСистемный аналитик

Как спроектировать API операцию создания платежа, чтобы повторная доставка одного запроса не создала второй...

Как спроектировать API-операцию создания платежа, чтобы повторная доставка одного запроса не создала второй платеж?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Операцию нужно сделать идемпотентной: клиент передаёт уникальный ключ операции, а сервер надёжно связывает его с результатом первого выполнения. Повторный запрос с тем же ключом должен вернуть тот же результат или текущее состояние операции, но не запускать создание платежа заново.

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

Исторический контекст

Идемпотентность стала необходимой из-за ненадёжности распределённых взаимодействий: ответ может потеряться после успешной обработки, соединение может прерваться, а клиент или посредник — повторить запрос. Для денежных и иных критичных операций простой повтор вызова опасен.

Подход с ключом идемпотентности решает исходную проблему неопределённости: клиент может безопасно повторить попытку, не зная, успел ли сервер выполнить операцию.

Постановка проблемы

Пусть сервер создал платёж, но ответ не дошёл до клиента. Клиент повторяет тот же запрос, а сервер без специальной защиты создаёт второй платёж.

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

Подробное решение

Клиент генерирует уникальный ключ для одной логической операции и сохраняет его при повторных попытках. Сервер атомарно резервирует этот ключ в надёжном хранилище вместе с отпечатком значимых параметров запроса.

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

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

Запись ключа и создание бизнес-операции должны быть согласованы: обычно их защищают одной транзакцией или механизмом надёжной фиксации состояния. Если платёжный процесс затрагивает внешнюю систему, одной локальной транзакции недостаточно; применяют отдельный журнал операции, устойчивые статусы, повторяемую отправку и сверку результата.

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

Ситуация из практики

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

Поиск по бизнес-полям был отклонён: два законных платежа могут иметь одинаковую сумму, а параллельные запросы создают состояние гонки. Запрет повторов на клиенте не защищал от повторной доставки на сетевом или серверном уровне.

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

Что кандидаты часто упускают

  1. Что делать, если первый запрос завершился тайм-аутом, а операция ещё выполняется?

Нельзя считать тайм-аут доказательством неуспеха. Сервер должен вернуть состояние вроде «обрабатывается» либо предоставить безопасный способ получить результат по тому же ключу; клиенту следует повторять запрос с тем же ключом, а не создавать новый.

  1. Почему недостаточно хранить только ключ идемпотентности?

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

  1. Где должна обеспечиваться уникальность ключа при параллельных запросах?

На стороне устойчивого хранилища или транзакционного механизма, а не только в прикладной проверке «сначала найти, потом вставить». Параллельные запросы могут одновременно пройти такую проверку, поэтому требуется атомарное ограничение уникальности и однозначное определение победившего выполнения.