Клиент нажал «Отправить заявку», не увидел подтверждения и нажал ещё раз. Если бот каждый раз создаёт новую запись, в системе появятся два обращения. С похожей проблемой сталкиваются запись на время, выдача доступа и изменение заказа. Надпись на кнопке одна, а запросов к приложению может прийти несколько.
Решение строится на идемпотентности: повтор одного и того же действия не создаёт повторный результат. Это свойство вашей серверной логики. Оно не появляется автоматически от отключения кнопки на экране и не означает, что любые одинаковые сообщения пользователя следует объединять.
Разделите нажатие, операцию и результат
Нажатие inline-кнопки передаётся через callback-запрос. Сервер понимает, кто взаимодействует с интерфейсом и какое действие требуется. Но техническое событие нажатия ещё не равно заявке. Одно действие пользователя может потребовать записи в базу, обращения к CRM и отправки итогового сообщения.
Задайте отдельный идентификатор бизнес-операции. Для отправки заполненной анкеты им может быть идентификатор конкретного черновика с версией подтверждения. Для бронирования — подготовленный запрос на конкретный слот. Не используйте один общий ключ вида «создать заявку» для всех клиентов: он должен различать независимые операции.
Идентификатор callback полезен для обработки самого события, но бизнес-задача часто требует более устойчивого ключа. Пользователь может нажать ту же кнопку повторно и получить новое техническое событие. Проверка только идентификатора входящего события не обязательно защищает от двух одинаковых результатов.
Как должен выглядеть повтор для пользователя
После первого допустимого нажатия покажите понятное состояние: запрос принят, операция выполняется либо результат уже готов. Если клиент нажал снова, сообщите текущее состояние той же операции. Не отвечайте «ошибка» на корректную попытку уточнить, дошла ли заявка.
Telegram предусматривает ответ на callback-запрос. Его следует отличать от завершения долгой операции: убрать ожидание интерфейса можно раньше, чем внешняя CRM закончит обработку. В сообщении не утверждайте, что заказ создан, пока система знает только о постановке задания в очередь.
Изменение клавиатуры помогает убрать ненужное повторное действие, но остаётся лишь частью интерфейса. Старая копия сообщения, задержка сети или параллельное устройство могут привести к повторному запросу. Финальная проверка допуска должна находиться там, где выполняется изменение данных.
Последовательность обработки на сервере
Сначала проверьте отправителя и принадлежность операции. Нажатие не должно менять чужую заявку только потому, что в кнопке указан её номер. Затем найдите запись операции по согласованному ключу и определите состояние: новая, выполняется, завершена или требует разбора ошибки.
Если результат уже существует, верните его ссылку или краткое подтверждение. Если операция выполняется, не запускайте вторую копию. Если запрос новый, сохраните начало обработки способом, который выдерживает одновременные обращения. Конкретный инструмент может быть транзакцией, ограничением уникальности или иным подходящим механизмом хранилища.
После успешного выполнения сохраните результат рядом с ключом операции. Тогда повтор может вернуть тот же номер заявки, а не создавать новую. Срок хранения выбирают по назначению операции и ожидаемому периоду повторов. Слишком короткая запись, исчезающая сразу после ответа, плохо помогает при задержанном нажатии.
Условный пример: два клика по одной анкете
Предположим, пользователь заполнил форму консультации и получил сводку. Кнопка подтверждает именно этот экземпляр формы. Первое нажатие переводит анкету в обработку. Второе приходит почти одновременно, но сервер видит уже занятую операцию и не создаёт дополнительную заявку.
Когда CRM возвращает номер, он сохраняется как результат. Пользователь получает подтверждение с этим номером. Если через минуту нажмёт старую кнопку, бот сообщит, что заявка уже отправлена, и покажет существующий результат. Если человек хочет подать другой вопрос, ему предлагают начать отдельную анкету.
Этот пример не требует считать любые похожие обращения дублями. Клиент вправе отправить две разные заявки с одинаковой темой. Различие определяется экземпляром формы и подтверждённым действием, а не поиском одинакового текста в последнем сообщении.
Самый сложный момент: внешний сервис не ответил
Допустим, CRM создала обращение, но сетевое соединение оборвалось до получения ответа. Локальная система не знает результата. Немедленное повторное создание может породить дубль. Поэтому заранее выясните, поддерживает ли внешний API собственный ключ идемпотентности или поиск по вашему внешнему идентификатору.
Если поддержка есть, повтор выполняется с тем же ключом либо система запрашивает существующий результат. Если такой возможности нет, нужна согласованная процедура сверки и состояния неопределённости. Не маскируйте его сообщением «ничего не создано», когда это не подтверждено.
Для важных операций полезна отдельная очередь ручного разбора. Сотрудник проверяет внешний результат и завершает связь с локальной записью. Это лучше автоматического бесконечного повтора. При этом клиент должен видеть, что запрос находится на проверке, а не заполнять анкету снова из-за внутренней неопределённости интеграции.
Повтор после ошибки и новое действие
Нужно различать повтор доставки и повторное решение пользователя. Если человек изменил содержание анкеты после неудачи, возможно, это уже другая версия операции. Правило зависит от процесса: либо старая заявка редактируется, либо создаётся новая после явного подтверждения. Скрытое переключение между ними затруднит поддержку.
Не разрешайте кнопку «попробовать снова» до выяснения, безопасен ли повтор. Для чтения справки повтор обычно прост. Для списания средств, выдачи доступа или бронирования требуется более строгая проверка результата. Статья описывает общий принцип обработки; конкретные платёжные правила нужно брать из документации используемого провайдера.
Также не доверяйте данным кнопки как готовому разрешению. Клиентский интерфейс сообщает намерение, а сервер сверяет актуальную цену, доступность слота и права пользователя. Старое сообщение может содержать уже неактуальный вариант, который больше нельзя выполнить на прежних условиях.
Как проверить отсутствие двойной заявки
В тестовой среде отправьте два параллельных запроса подтверждения одной формы. Проверьте количество записей в основном хранилище и во внешней системе. Одного сообщения «успешно» в интерфейсе недостаточно: дубль мог появиться в CRM, даже если пользователю показали только одно подтверждение.
Затем испытайте повтор после завершения, перезапуск приложения в середине операции и потерю ответа внешнего API. Для каждого случая определите допустимый результат заранее. Отдельно проверьте два разных экземпляра анкеты одного пользователя: оба должны работать, если бизнес-процесс разрешает две независимые заявки.
Хороший результат проверки — одна выполненная операция и объяснимый ответ на каждый повтор. Пользователь не обязан угадывать, достаточно ли одного нажатия при плохой связи. Надёжность обеспечивается сохранённым состоянием и контролем результата, а интерфейс лишь показывает этот процесс человеку.
Что записывать при разборе двойной операции
Для диагностики полезны идентификатор операции, входящего события и внешнего результата, время переходов и причина повторной попытки. Полный текст клиентской анкеты нужен далеко не всегда. Сначала установите последовательность: когда получено нажатие, кто начал выполнение и в какой момент появился окончательный результат.
Если дубль всё же создан, не удаляйте следы сразу после исправления карточки. Сохраните техническое объяснение причины и добавьте проверку соответствующего случая в тестовый набор. Например, ошибка могла возникать только после перезапуска работника очереди между внешним запросом и сохранением ответа. Удалённый вручную дубль решает текущую проблему клиента, но без исправления перехода следующий похожий запрос повторит тот же сбой.
При передаче задачи другому разработчику приложите воспроизводимый тест, а не только описание жалобы. Два запроса с одним идентификатором операции и ожидаемая одна запись позволяют проверить исправление предметно. Если внешний сервис участвует в сценарии, проверка должна охватывать и его результат, иначе локальный тест пропустит повторное создание за пределами приложения.
Источники
Формат нажатий описан в Bot API: CallbackQuery, ответ интерфейсу — в answerCallbackQuery. Идемпотентность бизнес-операций и связь с CRM реализует приложение, а не сама кнопка Telegram.