Если бот не создает заказ, сначала нужно определить последний подтвержденный этап. Сообщение пользователя могло не дойти до webhook, обработчик мог не распознать callback, состояние диалога могло истечь, корзина — не пройти валидацию, база — откатить транзакцию, а внешняя CRM — принять заказ, но не вернуть ответ. Все эти случаи выглядят одинаково: пользователь нажимает кнопку и не видит результата. Исправление без трассировки часто приводит к повторным заказам вместо восстановления оформления.
Надежная диагностика связывает одно действие пользователя с одним attempt_id или correlation_id и проводит его по всей цепочке: входящее событие, состояние сессии, данные корзины, расчет цены, запись заказа, интеграция, очередь уведомлений и ответ в чат. Только после этого видно, заказ действительно не создан, застрял в промежуточном статусе или уже существует, но бот не смог показать подтверждение.
Сначала исключите повторные заказы и сохраните следы
Не просите пользователя многократно нажимать «Оформить» и не запускайте массовую повторную обработку очереди. Если внешний сервис получил первый запрос, но ответ потерялся, каждая повторная попытка способна создать новый заказ, счет или бронь. До изменений сохраните журналы, состояние очереди и запись сессии конкретного диалога.
- запишите точное время, платформу, обезличенные chat_id и user_id;
- сохраните update_id, event_id или иной идентификатор входящего события;
- зафиксируйте команду, callback и шаг сценария без содержимого персональных полей;
- проверьте заказы по user_id, телефону, внешнему ключу и интервалу времени;
- снимите состояния основной, retry и dead-letter очередей;
- не очищайте webhook, offset, FSM-хранилище и кэш до диагностики;
- не передавайте токен бота, платежный ключ, cookie и полный payload клиента.
Если заказы влияют на оплату, склад или запись по времени, временно отключите только финальную кнопку либо переведите сценарий в понятный режим обслуживания. Остальные команды бота могут продолжать работать. Пользователь должен видеть честное сообщение, что оформление временно недоступно, а не бесконечный индикатор.
Разделите путь заказа на наблюдаемые этапы
Фраза «бот не создает заказ» слишком широкая для поиска причины. Нарисуйте фактическую последовательность и отметьте, какой шаг имеет запись в журнале или базе. В хорошем сценарии каждый переход оставляет технический результат и не зависит только от сообщения, отправленного пользователю.
incoming update received -> command or callback recognized -> conversation state loaded -> cart loaded and validated -> price and availability recalculated -> order transaction committed -> external system synchronized -> confirmation queued -> confirmation delivered- нет входящего события — проверяйте webhook, polling, токен и маршрутизацию;
- событие есть, handler не выбран — проверяйте фильтры, callback_data и порядок обработчиков;
- handler вызван, состояние пустое — проверяйте FSM, TTL, ключ сессии и конкурентные процессы;
- корзина отклонена — ищите отсутствующее поле, цену, остаток, доставку или минимальную сумму;
- транзакция откатилась — проверяйте SQL, ограничения и исключение;
- CRM ответила тайм-аутом — сначала сверяйте внешний заказ, затем решайте вопрос повтора;
- заказ есть, подтверждения нет — проверяйте очередь отправки и ошибку мессенджера.
Добавьте один correlation_id в структурированные логи каждого этапа. Идентификатор не должен включать телефон, имя или текст сообщения. Он нужен, чтобы собрать события из webhook, worker, базы, CRM и отправщика ответа в одну временную линию.
Проверьте, получает ли бот событие оформления
При webhook платформа отправляет обновление на публичный HTTPS-адрес. DNS, сертификат, reverse proxy, WAF или приложение могут отклонить запрос до обработчика. При long polling проблема может быть в остановленном процессе, конфликте двух экземпляров, неверном offset или сетевом доступе. Проверять нужно входящее событие, а не только сообщения, которые бот умеет отправлять. Исходящие сообщения могут работать при сломанном приеме обновлений.
- Сверьте время действия пользователя с access log endpoint webhook или журналом polling worker.
- Убедитесь, что запрос попал на нужный домен, путь, приложение и окружение.
- Проверьте HTTP-код ответа и длительность обработки.
- Сравните тип входящего update с типами, которые пропускает конфигурация бота.
- Убедитесь, что один update не забирает другой экземпляр polling.
- Проверьте backlog и ошибки retry, если события сначала попадают в очередь.
- Сверьте версию приложения: webhook мог вести на старый deployment.
Webhook должен быстро подтвердить прием и передать тяжелую работу в надежную очередь. Если в HTTP-обработчике выполняется расчет, запрос к CRM, генерация документа и отправка нескольких сообщений, платформа может повторить событие после тайм-аута. Это создает одновременно пропуски, задержки и дубли.
Убедитесь, что кнопка ведет в правильный обработчик
Кнопка может быть обычной клавиатурой, inline callback, ссылкой на WebApp или платежным действием. Для каждого типа приходит свое событие. Обработчик текста не получит callback, а callback handler не увидит данные, отправленные Mini App. После изменения формата callback_data старые сообщения в чатах продолжают содержать прежнюю версию и могут больше не проходить фильтр.
- сравните фактический тип update с ожидаемым handler;
- проверьте значение callback_data и его версию без персональных данных;
- убедитесь, что более общий обработчик не перехватывает событие раньше;
- проверьте регистрацию роутеров и порядок middleware после деплоя;
- подтверждайте callback быстро, но не считайте это созданием заказа;
- для WebApp проверяйте подпись init data на сервере и связывайте отправку с пользователем;
- обрабатывайте старые версии кнопок либо явно просите открыть актуальное меню.
Ложное ощущение успеха возникает, когда бот убирает индикатор кнопки или пишет «обрабатываю», но создание заказа запускается позже и падает. UI-ответ отделяют от бизнес-результата: сначала «запрос принят», затем подтверждение с номером только после надежной фиксации заказа.
Проверьте состояние диалога и корзину
Многошаговый бот хранит этап оформления в памяти, Redis или базе. Состояние может исчезнуть после перезапуска, истечь по TTL, записаться под другим ключом или быть перезаписано параллельным сообщением. Тогда кнопка оформления существует в старом сообщении, но handler не находит корзину, адрес или способ доставки. Ошибка должна переводиться в восстановимый сценарий, а не молча завершать обработку.
- проверьте ключ сессии: платформа, bot_id, tenant_id, chat_id и user_id;
- сопоставьте TTL состояния с реальной длительностью оформления;
- убедитесь, что deploy не очищает in-memory FSM;
- проверьте сериализацию после обновления модели данных;
- не смешивайте корзины разных магазинов, пользователей и ботов;
- проверьте конкурентные сообщения и порядок записи состояния;
- восстанавливайте корзину из долговечного источника, если временная сессия потеряна.
Перед созданием заказа сервер повторно загружает товары и пересчитывает стоимость. Нельзя доверять цене, скидке и итоговой сумме из сообщения или WebApp. Если товар удален, остаток закончился или тариф изменился, бот должен показать конкретную причину и предложить исправить корзину.
Найдите скрытую ошибку валидации
Часто заказ не создается из-за одного обязательного поля, но исключение перехватывается общим обработчиком. Адрес не соответствует выбранной доставке, телефон хранится в локальном формате, идентификатор варианта товара устарел, промокод больше не действует или сумма стала ниже минимума. Пользователь видит тишину, хотя backend корректно отказал.
- логируйте код валидации и имя поля, но не значение персонального поля;
- проверяйте товары, количество, валюту, цену и доступность на сервере;
- нормализуйте телефон и адрес отдельно от отображаемого формата;
- сверяйте доставку с регионом, весом, габаритами и расписанием;
- пересчитывайте промокод и бонусы в момент commit;
- проверяйте согласие и необходимые реквизиты только там, где они действительно нужны;
- возвращайте пользователю исправимый шаг вместо общего «что-то пошло не так».
Валидация должна быть детерминированной: одинаковые входные данные и версия каталога дают одинаковый результат. Если бот и сайт используют разные правила цены или доставки, вынесите расчет в общий сервис либо единую библиотеку. Иначе корзина выглядит корректной в боте, но отклоняется системой заказов.
Проверьте транзакцию создания заказа
Заказ обычно состоит из заголовка, позиций, адреса, скидок, платежного намерения и истории статусов. Если эти записи создаются отдельными несогласованными запросами, частичный сбой оставляет пустой заказ или теряет позиции. Основные данные фиксируют одной транзакцией, а внешние побочные эффекты запускают после commit через outbox или очередь.
BEGIN INSERT order(attempt_id, user_id, total, status) INSERT order_items(order_id, product_id, qty, price) INSERT outbox(event_id, type, order_id) COMMIT worker: outbox -> CRM/payment/notification reconciliation: compare local order and external result- проверьте исключение и rollback, а не только последнюю строку лога;
- ищите нарушения уникальных ограничений и внешних ключей;
- проверьте длину колонок, null и несовместимый тип данных;
- сопоставьте deadlock и повтор транзакции;
- не отправляйте подтверждение до commit;
- не создавайте платеж до появления устойчивого локального order_id;
- сохраняйте исходный attempt_id для последующей сверки.
Если заказ создается только во внешней CRM, локально все равно полезно хранить попытку и внешний ключ. Без этого тайм-аут оставляет неопределенный результат: бот не знает, повторять запрос или нет. Минимальная локальная запись позволяет проверить внешний сервис и продолжить сценарий без дубля.
Обработайте тайм-аут CRM или внешнего API без дублей
Тайм-аут не означает, что внешний сервис ничего не сделал. Запрос мог завершиться на стороне CRM, а ответ потеряться по дороге. Автоматический retry с новым идентификатором создаст второй заказ. Для операции создания нужен стабильный idempotency key, который повторяется для одной пользовательской попытки и имеет уникальное ограничение на принимающей стороне или в промежуточном реестре.
- Создайте attempt_id до первого обращения к внешней системе.
- Передавайте его как внешний номер или idempotency key, если API это поддерживает.
- При тайм-ауте установите статус unknown, а не failed.
- Перед повтором выполните поиск внешнего заказа по тому же ключу.
- Если заказ найден, привяжите его к локальной попытке.
- Если результат не удалось определить, отправьте задачу в reconciliation, а не в бесконечный retry.
- Разделяйте временные и постоянные ошибки и ограничивайте число попыток.
Retry должен использовать backoff и jitter, а permanent-ошибка — попадать в контролируемую очередь разбора. Нельзя бесконечно повторять неверный адрес, закрытый товар или отказ авторизации. Такие ошибки требуют исправления данных или конфигурации, а не времени.
Отделите создание заказа от уведомления
Заказ может быть успешно записан, но бот не прислал номер из-за лимита платформы, блокировки пользователем, неверного chat_id, ошибки форматирования или падения отправщика. Если отправка сообщения входит в ту же транзакцию, временная ошибка мессенджера способна откатить бизнес-операцию. Если она выполняется без учета состояния, бот может ошибочно предложить создать заказ заново.
- фиксируйте заказ до постановки уведомления;
- храните статус доставки подтверждения отдельно от статуса заказа;
- повторяйте отправку безопасно по notification_id;
- показывайте пользователю список последних заказов по команде или в личном кабинете;
- не меняйте статус заказа на failed из-за ошибки сообщения;
- при блокировке бота используйте согласованный альтернативный канал, если он есть;
- не включайте персональные данные и секреты в технический текст ошибки.
Проверяйте заказ в источнике истины, а не по наличию сообщения в чате. Подтверждение — представление результата, но не сам результат. Это различие особенно важно при оплате, бронировании и резервировании остатка.
Проверьте платежный сценарий отдельно
В ботах заказ может создаваться до оплаты, после подтверждения платежа или в два этапа. Ошибка появляется, когда обработчик ожидает событие не того типа, связывает платеж с временной сессией вместо устойчивого order_id либо считает успешным только сообщение пользователя. Нужно описать состояния pending_payment, paid, cancelled, expired и правила переходов между ними.
- создавайте устойчивый локальный заказ или payment intent до отправки счета;
- связывайте payload платежа с order_id и проверяйте подпись на сервере;
- не доверяйте сумме и статусу из клиентского интерфейса;
- обрабатывайте повторное платежное событие идемпотентно;
- не создавайте второй заказ после возврата пользователя из платежной формы;
- проверяйте валюту, сумму и назначение перед переводом в paid;
- имейте сверку зависших платежей с провайдером.
Если платеж прошел, а заказ не создан, автоматический возврат или ручное восстановление должны опираться на журнал платежного события. Не просите клиента оплачивать повторно, пока не проверены идентификатор транзакции и состояние у провайдера.
Пошаговый порядок диагностики и исправления
Работайте от факта к следующей границе. Один контрольный заказ и один correlation_id полезнее десятков случайных повторов. Все изменения сначала проверяйте на тестовом боте или отдельном окружении с тестовыми товарами и платежами.
- Найдите попытку по времени, пользователю и update_id, затем убедитесь, что готового заказа нет.
- Проверьте прием события webhook или polling и HTTP-ответ обработчика.
- Убедитесь, что событие попало в правильный router, middleware и handler.
- Восстановите состояние диалога и содержимое корзины по безопасным техническим полям.
- Запустите серверную валидацию и зафиксируйте конкретный код отказа.
- Сопоставьте попытку с транзакцией базы, rollback и ограничениями.
- Проверьте outbox, очередь, retry и dead-letter записи.
- При вызове CRM или платежного API найдите результат по idempotency key.
- Отдельно проверьте постановку и доставку сообщения с номером заказа.
- Исправьте один доказанный разрыв и добавьте тест этого сценария.
- Повторите тест с новым attempt_id и убедитесь, что двойное нажатие не создает дубль.
- Включите наблюдение за ошибками и временем каждого этапа после выпуска.
Не используйте реальные платежные данные и персональные адреса в тестах. Подготовьте тестовый каталог, тестового пользователя и sandbox платежного провайдера. Если внешняя CRM не имеет тестовой среды, используйте согласованный тестовый признак и обязательную очистку по документированному сценарию.
Как проверить результат после исправления
Успешный тест должен подтверждать не только появление сообщения, но и согласованность данных. Проверяйте номер, позиции, цену, скидку, доставку, пользователя, внешний идентификатор и историю статусов. Затем повторяйте негативные сценарии: истекшая корзина, недоступный товар, тайм-аут и двойное нажатие.
- один callback создает ровно один заказ;
- двойное нажатие возвращает тот же результат и не создает дубль;
- после restart worker незавершенная попытка восстанавливается;
- невалидное поле возвращает понятный шаг исправления;
- изменение цены приводит к пересчету и подтверждению пользователем;
- тайм-аут CRM переводит попытку в unknown и запускает сверку;
- повторное внешнее событие обрабатывается идемпотентно;
- заказ остается созданным при временной ошибке уведомления;
- платеж связан с правильным order_id и не меняет чужой заказ;
- логи позволяют пройти путь по correlation_id без персональных данных;
- метрики и алерты видят рост ошибок до жалоб пользователей.
Отдельно проверьте разные платформы и типы чатов, если один backend обслуживает несколько ботов. Личный чат, группа, WebApp и канал могут давать разные идентификаторы и права. Tenant_id или bot_id должен входить в ключи состояния и уникальности.
Типичные ошибки при ремонте бота
- считать отсутствие сообщения доказательством отсутствия заказа;
- повторять создание после любого тайм-аута новым идентификатором;
- очищать webhook, offset или FSM до сохранения диагностических данных;
- хранить состояние оформления только в памяти процесса;
- доверять цене и итогу из callback или WebApp;
- перехватывать все исключения без кода ошибки и request ID;
- отправлять «заказ создан» до commit базы;
- включать вызов CRM и отправку сообщения в одну длинную транзакцию;
- не различать retryable, permanent и unknown результат;
- логировать токен бота, телефон, адрес и платежный payload;
- проверять только счастливый сценарий и одно нажатие;
- исправлять рабочую базу ручными INSERT без связей и истории.
Временная ручная запись может восстановить конкретный заказ, но не устраняет разрыв. Если она необходима, используйте штатную сервисную команду, которая выполняет те же проверки, транзакцию и аудит, что обычный сценарий. После этого отдельно исправьте первопричину.
Как предотвратить повторение
Оформление заказа — бизнес-процесс, а не один handler кнопки. Его полезно моделировать конечным автоматом с явными статусами и допустимыми переходами. События могут приходить повторно и не по порядку, процессы — перезапускаться, внешние API — отвечать поздно. Архитектура должна восстанавливать результат из долговечного состояния.
- назначайте attempt_id до создания заказа и сохраняйте его во всех системах;
- ставьте уникальное ограничение на бизнес-ключ попытки;
- используйте транзакцию для заказа и позиций, outbox — для внешних действий;
- делайте webhook быстрым, а тяжелую обработку переносите в очередь;
- задавайте TTL сессии с учетом реального поведения пользователей;
- версионируйте callback_data и формат состояния;
- показывайте пользователю восстановимый статус последней попытки;
- добавьте reconciliation для CRM, платежей и неизвестных результатов;
- измеряйте число попыток, созданных заказов, unknown, retry и DLQ;
- алертируйте по расхождению «оформление начато — заказ создан»;
- тестируйте restart, тайм-аут, повтор события и двойное нажатие.
Минимальный полезный dashboard показывает воронку по этапам и p95 времени: update received, handler selected, validation passed, order committed, external sync completed, confirmation delivered. Резкое падение между двумя соседними шагами сразу указывает область сбоя.
Когда нужна помощь с заказами в боте
Если бот не создает заказ, я могу проследить одну попытку от webhook или polling до базы, CRM, платежа и сообщения пользователю, найти потерянное состояние, скрытую валидацию, rollback, тайм-аут или ошибку очереди. Затем исправлю подтвержденную причину, добавлю идемпотентность и проверю двойное нажатие, restart и неопределенный ответ внешнего API без риска дублей. Для первичной оценки достаточно платформы бота, времени сбоя, обезличенных update_id и user_id, версии приложения и короткого фрагмента ошибки — токен бота, платежные ключи и данные клиента присылать не нужно.