Как повторить заказ, не создавая дубликаты?
Что проверяют
Понимаете ли вы, откуда берутся дубли: двойной клик, повторная отправка после таймаута, кнопка «назад» и обновление страницы. И знаете ли, что надёжная защита живёт на сервере, а фронтенд её правильно использует. Следом часто спрашивают, что делать, если ответ потерялся, а заказ на сервере уже создан.
Как отвечать
Сначала уточните сценарий. «Повторить заказ» обычно значит собрать новую корзину из старого заказа и дать пользователю её подтвердить. Сам новый заказ при этом должен создаться ровно один раз, и тут работает связка мер:
- ключ идемпотентности: при открытии оформления клиент генерирует
crypto.randomUUID()и отправляет его в заголовкеIdempotency-Key. Сервер, увидев знакомый ключ, возвращает созданный ранее заказ вместо нового. Ключ один на попытку оформления, а не на клик; - повтор с тем же ключом: если запрос упал по таймауту или из-за сети, повторная отправка идёт с тем же ключом, и кнопка «попробовать снова» становится безопасной;
- блокировка интерфейса: кнопка неактивна, пока запрос в полёте. В TanStack Query это
isPendingуuseMutation, в React 19 для форм естьuseFormStatusиuseActionState. Это улучшение UX, а не защита: вторая вкладка её обойдёт; - после успеха: переход на страницу заказа через
router.replaceили редирект после POST, чтобы «назад» и F5 не отправили форму повторно; - корзина: товары из старого заказа сверяются с текущими ценами и наличием, расхождения показываются до подтверждения.
Где спотыкаются
Ищут дубликат на клиенте по составу через JSON.stringify(items). Это запрещает честный повторный заказ тех же товаров, зависит от порядка полей и не видит запросов из другой вкладки.
Считают отключённую кнопку достаточной защитой. Она убирает двойной клик, но не повтор после обрыва сети.
Генерируют новый ключ идемпотентности на каждую попытку отправки. Тогда повтор после таймаута создаёт второй заказ, и смысл ключа пропадает.