Если бот не создает заказ, сначала нужно определить последний подтвержденный этап. Сообщение пользователя могло не дойти до 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 или сетевом доступе. Проверять нужно входящее событие, а не только сообщения, которые бот умеет отправлять. Исходящие сообщения могут работать при сломанном приеме обновлений.

  1. Сверьте время действия пользователя с access log endpoint webhook или журналом polling worker.
  2. Убедитесь, что запрос попал на нужный домен, путь, приложение и окружение.
  3. Проверьте HTTP-код ответа и длительность обработки.
  4. Сравните тип входящего update с типами, которые пропускает конфигурация бота.
  5. Убедитесь, что один update не забирает другой экземпляр polling.
  6. Проверьте backlog и ошибки retry, если события сначала попадают в очередь.
  7. Сверьте версию приложения: 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, который повторяется для одной пользовательской попытки и имеет уникальное ограничение на принимающей стороне или в промежуточном реестре.

  1. Создайте attempt_id до первого обращения к внешней системе.
  2. Передавайте его как внешний номер или idempotency key, если API это поддерживает.
  3. При тайм-ауте установите статус unknown, а не failed.
  4. Перед повтором выполните поиск внешнего заказа по тому же ключу.
  5. Если заказ найден, привяжите его к локальной попытке.
  6. Если результат не удалось определить, отправьте задачу в reconciliation, а не в бесконечный retry.
  7. Разделяйте временные и постоянные ошибки и ограничивайте число попыток.

Retry должен использовать backoff и jitter, а permanent-ошибка — попадать в контролируемую очередь разбора. Нельзя бесконечно повторять неверный адрес, закрытый товар или отказ авторизации. Такие ошибки требуют исправления данных или конфигурации, а не времени.

Отделите создание заказа от уведомления

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

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

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

Проверьте платежный сценарий отдельно

В ботах заказ может создаваться до оплаты, после подтверждения платежа или в два этапа. Ошибка появляется, когда обработчик ожидает событие не того типа, связывает платеж с временной сессией вместо устойчивого order_id либо считает успешным только сообщение пользователя. Нужно описать состояния pending_payment, paid, cancelled, expired и правила переходов между ними.

  • создавайте устойчивый локальный заказ или payment intent до отправки счета;
  • связывайте payload платежа с order_id и проверяйте подпись на сервере;
  • не доверяйте сумме и статусу из клиентского интерфейса;
  • обрабатывайте повторное платежное событие идемпотентно;
  • не создавайте второй заказ после возврата пользователя из платежной формы;
  • проверяйте валюту, сумму и назначение перед переводом в paid;
  • имейте сверку зависших платежей с провайдером.

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

Пошаговый порядок диагностики и исправления

Работайте от факта к следующей границе. Один контрольный заказ и один correlation_id полезнее десятков случайных повторов. Все изменения сначала проверяйте на тестовом боте или отдельном окружении с тестовыми товарами и платежами.

  1. Найдите попытку по времени, пользователю и update_id, затем убедитесь, что готового заказа нет.
  2. Проверьте прием события webhook или polling и HTTP-ответ обработчика.
  3. Убедитесь, что событие попало в правильный router, middleware и handler.
  4. Восстановите состояние диалога и содержимое корзины по безопасным техническим полям.
  5. Запустите серверную валидацию и зафиксируйте конкретный код отказа.
  6. Сопоставьте попытку с транзакцией базы, rollback и ограничениями.
  7. Проверьте outbox, очередь, retry и dead-letter записи.
  8. При вызове CRM или платежного API найдите результат по idempotency key.
  9. Отдельно проверьте постановку и доставку сообщения с номером заказа.
  10. Исправьте один доказанный разрыв и добавьте тест этого сценария.
  11. Повторите тест с новым attempt_id и убедитесь, что двойное нажатие не создает дубль.
  12. Включите наблюдение за ошибками и временем каждого этапа после выпуска.

Не используйте реальные платежные данные и персональные адреса в тестах. Подготовьте тестовый каталог, тестового пользователя и 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, версии приложения и короткого фрагмента ошибки — токен бота, платежные ключи и данные клиента присылать не нужно.