Legan Studio
Все статьи
~ 14 мин чтения

Бот для интернет-магазина в MAX: воронка от каталога до доставки

Разбираем сценарии бота для онлайн-магазина в MAX: каталог, корзина, оплата, статусы заказа и удержание клиентов. Архитектура и узкие места.

  • MAX
  • e-commerce
  • сценарии

Магазин в мессенджере — не замена сайту, а отдельная воронка с другой механикой. Пользователь уже залогинен, не нужно регистрироваться, доставка и оплата идут в одном окне. В MAX эта воронка строится на Bot API и инлайн-кнопках, а конверсия упирается в скорость отклика и качество карточек товара.

Каталог и навигация

Каталог в боте редко повторяет каталог сайта один в один. Глубокая иерархия из 4–5 уровней утомляет в чате — лучше плоская витрина с поиском и фильтрами через инлайн-клавиатуру. Карточка товара — это один-два изображения, цена, краткое описание, две кнопки: «В корзину» и «Подробнее». Всё остальное скрывается за разворачиваемыми секциями.

Под капотом каталог обычно лежит в Postgres с индексами по категории и популярности. Кэш горячих позиций уходит в Redis, чтобы не дёргать БД при каждом скролле. Картинки прогоняем через CDN — MAX не любит долгих ответов от бота, и если карточка грузится дольше пары секунд, пользователь уходит.

Mini App vs встроенный inline-каталог

Для каталога есть два архитектурных пути: показать товары прямо в чате через сообщения с инлайн-кнопками или открыть Mini App (WebView со своим UI). Выбор сильно влияет на разработку, конверсию и индексацию.

КритерийInline-каталогMini App
UX навигацииЛинейный, одно сообщение = один экранПривычный SPA, вкладки, фильтры
Глубина каталогаДо 200–300 SKU комфортно10 000+ SKU без проблем
Время до первого экрана300–600 мс1.5–3 с (загрузка WebView)
Сложность разработки2.5–3× (фронт + бэк + initData)
SEO/индексацияНетНет (закрытая среда мессенджера)
Конверсия в чек-аут8–14%6–11% (просадка на загрузке)
Брошенные корзиныЛучше recoveryХуже видимость через бота
АналитикаСобытия ботаWeb-аналитика + события

Практическое правило: если SKU меньше 300 и каталог плоский — берите inline. Если 1000+ позиций, нужны фасетные фильтры или сравнение товаров — Mini App. Гибрид тоже работает: inline для популярных подборок и быстрых сценариев, Mini App для глубокого поиска.

Модель запасов и резервирование

Самая болезненная ошибка в магазине — продать товар, которого уже нет. Источник правды по остаткам — товароучётная система (МойСклад, 1С), но опираться на её актуальные значения при каждом клике нельзя: API медленный и квотированный. Поэтому остатки кэшируются в Postgres + Redis с инвалидацией по webhook от учётки.

Ключевой паттерн — soft-reserve: при добавлении в корзину или начале чек-аута товар резервируется на N минут (обычно 10–20). Резерв не списывает остаток, но уменьшает «доступно к продаже».

# Псевдокод soft-reserve с защитой от race condition
async def reserve(sku: str, qty: int, user_id: int, ttl_sec: int = 900):
    async with db.transaction():
        # SELECT FOR UPDATE блокирует строку остатка на время транзакции
        stock = await db.fetchrow(
            "SELECT available, reserved FROM stock WHERE sku=$1 FOR UPDATE",
            sku,
        )
        free = stock["available"] - stock["reserved"]
        if free < qty:
            raise OutOfStock(sku, free)
        await db.execute(
            "UPDATE stock SET reserved = reserved + $1 WHERE sku=$2",
            qty, sku,
        )
        await redis.setex(
            f"reserve:{user_id}:{sku}", ttl_sec,
            json.dumps({"qty": qty, "ts": time.time()}),
        )

Срок резерва истёк — фоновая job снимает его и возвращает товар в продажу. Оплата прошла — резерв конвертируется в фактическое списание. Без SELECT FOR UPDATE или эквивалентного advisory-lock два параллельных запроса увидят одно и то же значение и оба «успешно» зарезервируют последний экземпляр — классический race на чек-ауте Чёрной пятницы.

Корзина, оплата, чек

Корзина живёт в FSM-состоянии пользователя. Хранить её на клиенте нельзя — пользователь может зайти с другого устройства. Используем сессию в Redis с TTL в несколько суток: брошенные корзины потом пригодятся для возврата.

Оплата — это либо платёжная ссылка на эквайринг банка, либо встроенный платёжный шлюз через Bot API, если доступен в MAX. После успешной оплаты бот:

  • отправляет чек по 54-ФЗ через ОФД,
  • создаёт заказ в учётной системе (1С, МойСклад, retailCRM),
  • ставит задачу складу,
  • запускает таймер на статусы доставки.

Промокоды, скидки и бонусные программы

Промокоды легко выглядят простыми, пока не появляются комбинированные правила. Минимальный набор типов:

  • Процентная скидка-15% на корзину или категорию.
  • Фиксированная сумма-500 ₽ при заказе от 3000 ₽.
  • Подарок — бесплатный товар при достижении суммы.
  • Бесплатная доставка — снимает стоимость доставки.
  • Реферальный — двусторонний (приглашающий получает бонусы, приглашённый — скидку).

Хранение в БД отдельной таблицей с лимитами:

CREATE TABLE promo (
    code         TEXT PRIMARY KEY,
    type         TEXT NOT NULL,          -- percent | fixed | gift | free_shipping
    value        NUMERIC NOT NULL,
    min_total    NUMERIC DEFAULT 0,
    valid_from   TIMESTAMPTZ,
    valid_until  TIMESTAMPTZ,
    max_uses     INTEGER,                -- глобальный лимит
    max_per_user INTEGER DEFAULT 1,
    used_count   INTEGER DEFAULT 0,
    categories   TEXT[],                 -- ограничение по категориям
    is_active    BOOLEAN DEFAULT true
);

CREATE TABLE promo_usage (
    code     TEXT REFERENCES promo(code),
    user_id  BIGINT,
    order_id BIGINT,
    used_at  TIMESTAMPTZ DEFAULT now(),
    PRIMARY KEY (code, user_id, order_id)
);

Проверка промокода — атомарная операция: INSERT ... ON CONFLICT DO NOTHING в promo_usage плюс UPDATE promo SET used_count = used_count + 1 WHERE used_count < max_uses. Если апдейт затронул 0 строк — лимит исчерпан, откатываем. Без этого в момент окончания акции вы выпустите кодов больше, чем планировали.

Бонусные программы (cashback) удобно вести отдельным «кошельком» пользователя: начисление 3–10% от суммы заказа после получения товара (не оплаты — иначе бонусы уйдут на возвраты), списание ограничено долей корзины (обычно до 30%).

Чек-аут и способы оплаты

Чек-аут — это шаги от «Оформить заказ» до «Оплачено». Оптимальная глубина — 3–4 экрана:

  1. Контакты и доставка — телефон (если ещё не сохранён), регион, способ доставки.
  2. Адрес или ПВЗ — для курьера полный адрес, для ПВЗ — выбор точки на карте или из списка.
  3. Оплата — выбор метода, применение промокода/бонусов, итоговая сумма.
  4. Подтверждение и платёж — переход в платёжную форму банка.

Способы оплаты в России в 2026:

  • СБП (QR / по номеру телефона) — комиссия 0.4–0.7%, моментальное зачисление, лучшая конверсия на мобильных.
  • Карта (3-D Secure) — комиссия 1.5–2.5%, привычно для аудитории 35+.
  • YooMoney / ЮKassa / T-Pay — агрегаторы, удобны для одной интеграции «всё в одном».
  • Долями / Подели — BNPL, повышает средний чек на 15–25%, но комиссия 4–6%.
  • Наличные/карта при получении — для регионов с низким доверием к онлайну, но конверсия в выкуп падает на 20–40%.

Частичная оплата (предоплата 30% + остаток при получении) реализуется как два отдельных платёжных события на один order_id. Webhook от платёжной системы должен быть идемпотентным: один и тот же payment_id не должен дважды списать товар или дважды отправить чек. Простая защита — уникальный индекс на (payment_id, event_type) в таблице payment_events.

Мульти-регион и доставка

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

Регион определяется в три приёма: сохранённое значение из профиля → IP-геолокация (грубо, до города) → явный выбор пользователем. Последнее обязательно — IP врёт у VPN и корпоративных сетей.

Расчёт доставки:

  • СДЭК — REST API /v2/calculator/tariff, возвращает массив тарифов с ценой и сроком. Кэшируем на 1 час по ключу (from_pvz, to_postal, weight, dimensions).
  • Boxberry — XML API, медленнее СДЭК, но широкая сеть ПВЗ в малых городах.
  • Почта Россииtariff.russianpost.ru, нестабильный, лучше дублировать данными из своего справочника.
  • Самовывоз — фиксированно 0 ₽, требует выбора точки и слота.
  • Курьер собственный — простая зональная сетка (район → цена).

Ограничения по регионам — отдельная таблица region_restrictions с правилами вида «крупногабарит не доставляется в Калининградскую область», «алкоголь не продаётся онлайн нигде». Проверка делается перед чек-аутом, иначе клиент дойдёт до оплаты и получит отказ — резервный сценарий с худшей конверсией.

Статусы заказа и доставка

Самая частая жалоба — «где мой заказ». Бот закрывает её сам: подписка на статусы, кнопка «Отследить» в карточке заказа, push-уведомление при смене статуса. Источников статусов обычно несколько: учётная система генерирует «собран» и «передан в доставку», курьерская служба (СДЭК, Почта России, Boxberry) — этапы маршрута.

Архитектурно это webhook-приёмник на стороне нашего сервиса: каждая внешняя система пушит статус, мы нормализуем его в общий словарь и отправляем сообщение пользователю. Без нормализации в чате будет каша из «processed», «оформлен», «передан в обработку» — пользователь не поймёт.

Возвраты, отказы и претензии

Возврат — это отдельный пользовательский сценарий, который часто откладывают «на потом» и собирают на коленке. По 26-ФЗ ЗоЗПП клиент вправе вернуть непродовольственный товар надлежащего качества в течение 14 дней (для дистанционной торговли — 7 дней с момента получения), а товар ненадлежащего качества — в течение гарантийного срока. Бот должен закрывать оба сценария.

Минимальный флоу:

  1. В карточке заказа кнопка «Оформить возврат» доступна, если статус = delivered и прошло меньше 14 дней.
  2. Пользователь выбирает позиции и причину (брак, не подошёл размер, не соответствует описанию, передумал).
  3. Прикладывает фото при браке (опционально, но повышает скорость рассмотрения).
  4. Получает инструкцию: курьерский забор, ПВЗ, почта.
  5. После приёмки на складе — возврат денег тем же способом, которым оплачивали (по 161-ФЗ и правилам платёжных систем).

Частичный возврат (один товар из заказа) считается отдельной транзакцией. Сумма к возврату = позиция × цена − пропорциональная скидка − пропорциональная стоимость доставки (если возврат не по вине магазина). Шаблон сообщения клиенту:

Возврат №R-2026-04829 принят.
Товар: Чайник Bosch TWK-3A013
Сумма: 2 850 ₽
Срок зачисления: до 10 рабочих дней на карту *4521.

При вопросах — нажмите «Связаться с менеджером».

Чарджбек (оспаривание операции через банк) — отдельный риск. Если клиент жалуется в банк вместо обращения в магазин, эквайер списывает сумму со счёта продавца и берёт штраф 1500–2500 ₽. Сократить чарджбеки помогает быстрая реакция на запросы возврата (до 48 часов) и понятная контактная кнопка в боте — клиент скорее напишет вам, чем в банк.

Интеграции с товароучёткой

Бот сам по себе не источник правды — он витрина и точка приёма заказа. Реальные остатки, цены, заказы, отгрузки живут в товароучётной системе. Три самые частые в РФ:

МойСклад — REST JSON API (api.moysklad.ru/api/remap/1.2). Лёгкий старт, хорошие webhooks, разумные лимиты (45 запросов/3 секунды). Синхронизация:

  • В магазин (бот): товары, остатки, цены, скидки → раз в 5–15 минут или по webhook.
  • В МойСклад: заказы покупателей, оплаты, отгрузки → синхронно при создании заказа.

RetailCRM — REST + хорошие триггеры маркетинга. Силён в коммуникациях, посредственный складской контур, поэтому часто ставится поверх 1С. Webhooks нативные, лимиты 300 запросов/минуту.

1С:Розница / 1С:УТ — классика. Обмен через CommerceML 2.x (XML по HTTP), либо через одностраничные REST-обёртки (Хорошо, 1С-Битрикс адаптеры). Грабли:

  • CommerceML загружает большой каталог одним архивом — ломает онлайн-обновления.
  • Кодировка windows-1251 в старых конфигурациях — обязательно проверять.
  • Нумерация заказов в 1С идёт с префиксами, легко конфликтует с собственной БД магазина.
  • Расхождение справочников единиц измерения (шт vs штук vs pcs).

Универсальное правило: всегда есть внутренняя БД заказов в магазине, и обмен с учёткой — асинхронный (очередь + ретраи). Если 1С упала на час, магазин продолжает принимать заказы и копит их в очереди, а синхронизация догоняется потом. Прямой синхронный запрос «создать заказ в 1С при оплате» — гарантированный downtime магазина в момент любой проблемы у бухгалтерии.

Лимиты и нагрузка

Типичная архитектура «Postgres + Redis + Go/Python бот + 1 worker» комфортно держит:

  • 5 000–20 000 SKU в Postgres с индексами и pg_trgm для поиска по названию.
  • 300–500 одновременно активных пользователей в боте (RPS до 200 на 2 vCPU / 4 GB).
  • 30 000–50 000 заказов в месяц без шардинга и без выноса аналитики.

Когда переходить на отдельную поисковую базу:

  • Каталог растёт за 30 000 SKU и фасетный поиск (бренд + категория + цена + наличие) начинает занимать больше 200 мс.
  • Появляется опечаточный поиск, морфология, синонимы.
  • Нужна ранжировка по поведению (популярность, конверсия карточки).

Кандидаты — Meilisearch (проще в эксплуатации, до 1 млн документов на одной машине), Typesense (быстрее на крупных индексах, лучше с фильтрами), OpenSearch (для тех, кто уже живёт в экосистеме Elastic).

Для нагрузки от 1000+ одновременных пользователей и пиков на распродажах — горизонтальное масштабирование бота (несколько инстансов за общим Redis для FSM), вынос отправки сообщений в очередь (NATS, RabbitMQ), отдельная реплика Postgres под чтение каталога.

Возврат брошенных корзин

Брошенная корзина — корзина, в которой больше суток нет активности и нет оплаты. Простая механика: через 2 часа напомнить, через 24 часа предложить промокод, через 72 часа — снять с актуальных. Текст не должен звучать как давление: «вы оставили в корзине X — оформить можно тут».

Метрика, на которую смотрим, — recovery rate: сколько брошенных корзин возвращается. У качественно настроенного бота это 10–20%, что заметно выше email-рассылок.

Удержание после первой покупки

Боты выигрывают у email на втором цикле. После первой покупки запускаем сценарии:

  1. Через 3–7 дней — запрос отзыва.
  2. Через 14–30 дней — рекомендация сопутствующего товара.
  3. Перед предполагаемым закономерным повторным заказом — напоминание (особенно работает для расходников).

Сегментация по истории покупок строится на стороне CRM, бот выступает каналом доставки. Отписка обязательна — иначе попадаем под 38-ФЗ о рекламе.

Чек-лист готовности магазина к запуску

  • Каталог отдаёт карточку быстрее 1–2 секунд (95-й перцентиль).
  • Корзина переживает перезапуск бота (Redis с персистом AOF/RDB).
  • Soft-reserve остатков с TTL и фоновой очисткой просроченных резервов.
  • Идемпотентные webhook оплаты (уникальный индекс по payment_id).
  • Чек по 54-ФЗ уходит в ОФД даже при задержке ответа банка.
  • Все исходящие сообщения логируются с message_id для аналитики.
  • Промокоды защищены от двойного применения транзакцией с проверкой лимита.
  • Возврат оформляется через бот без звонка менеджеру для типовых случаев.
  • Расчёт доставки кэшируется и не падает при недоступности API курьера.
  • Региональные ограничения проверяются ДО формы оплаты.
  • Синхронизация с учёткой асинхронная, с очередью и ретраями.
  • Есть ручка для оператора, чтобы написать в чат пользователя.
  • Включены метрики: time-to-first-byte бота, recovery rate, conversion checkout.
  • Юридические документы на месте: оферта, согласие на ПДн, политика возвратов.
  • Нагрузочное тестирование под 3× ожидаемый пик (Black Friday).

Итого

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

Частые вопросы

Чем бот-магазин в MAX отличается от сайта интернет-магазина?

Это не замена сайту, а отдельная воронка с другой механикой. Пользователь уже залогинен в мессенджере — не нужна регистрация. Доставка, оплата и статусы идут в одном окне без переключений между приложениями. Сильная сторона — удержание и сервис: корзина, оплата, статусы заказа, повторные продажи. Слабая сторона — глубокий каталог: иерархия из 4–5 уровней утомляет в чате, поэтому каталог делается плоской витриной с поиском и фильтрами через инлайн-клавиатуру, а не повторяет сайт один в один.

Как сделать каталог товаров в боте MAX?

Карточка товара — один-два изображения, цена, краткое описание, две кнопки: «В корзину» и «Подробнее». Всё остальное скрывается за разворачиваемыми секциями, чтобы не перегружать чат. Под капотом каталог в PostgreSQL с индексами по категории и популярности. Кэш горячих позиций в Redis, чтобы не дёргать БД при каждом скролле. Картинки через CDN — MAX не любит долгих ответов, и если карточка грузится дольше пары секунд, пользователь уходит. Глубокая иерархия заменяется поиском и фильтрами.

Где хранится корзина пользователя в боте MAX?

В FSM-состоянии пользователя на стороне сервера, не на клиенте. Клиентское хранилище не подходит, потому что пользователь может зайти с другого устройства и потерять корзину. Используется сессия в Redis с TTL в несколько суток — брошенные корзины потом пригодятся для возврата клиента через напоминания. После оплаты бот отправляет чек по 54-ФЗ через ОФД, создаёт заказ в учётной системе (1С, МойСклад, retailCRM), ставит задачу складу и запускает таймер на статусы доставки.

Как настроить уведомления о статусах заказа в боте?

Через webhook-приёмник на стороне бота. Источников несколько: учётная система генерирует «собран» и «передан в доставку», курьерская служба (СДЭК, Почта России, Boxberry) — этапы маршрута. Все статусы нормализуются в общий словарь, чтобы в чате не было каши из «processed», «оформлен», «передан в обработку» — пользователь не поймёт. У пользователя кнопка «Отследить» в карточке заказа и push-уведомления при смене статуса. Это закрывает самую частую жалобу — «где мой заказ».

Как работает возврат брошенных корзин в боте MAX?

Брошенная корзина — это корзина без активности и оплаты больше суток. Простая механика: через 2 часа напомнить, через 24 часа предложить промокод, через 72 часа — снять с актуальных. Текст не должен звучать как давление: «вы оставили в корзине X — оформить можно тут». Главная метрика — recovery rate (сколько брошенных корзин возвращается). У качественно настроенного бота это 10–20%, что заметно выше email-рассылок благодаря мгновенной доставке и одному окну с оплатой.

Какие сценарии удержания работают в боте после первой покупки?

Три классических. Через 3–7 дней — запрос отзыва (когда впечатление свежее). Через 14–30 дней — рекомендация сопутствующего товара на основе истории. Перед предполагаемым повторным заказом — напоминание (особенно работает для расходников: косметика, корм для животных, фильтры для воды). Сегментация по истории покупок строится на стороне CRM, бот выступает каналом доставки. Отписка от рассылок обязательна — иначе попадаем под 38-ФЗ о рекламе и можем получить претензию от ФАС.

Как защитить промокоды от перерасхода и двойного применения?

Промокоды хранятся в отдельной таблице с лимитами (max_uses глобально, max_per_user на пользователя, valid_from/valid_until по сроку, categories по категориям). Применение — атомарной транзакцией: INSERT в promo_usage с уникальным ключом (code, user_id, order_id) плюс UPDATE promo SET used_count = used_count + 1 WHERE used_count < max_uses. Если апдейт затронул 0 строк — лимит исчерпан, транзакция откатывается. Без этого в момент окончания акции вы выпустите кодов больше, чем планировали, особенно если код вирусится в соцсетях за пару часов до дедлайна.

Как организовать возврат товара через бот в MAX?

В карточке доставленного заказа кнопка «Оформить возврат» доступна 14 дней (по 26-ФЗ ЗоЗПП — 7 дней для дистанционной торговли с момента получения для товара надлежащего качества, и в пределах гарантийного срока для брака). Пользователь выбирает позиции и причину, при браке прикладывает фото. Получает инструкцию по способу возврата (курьерский забор, ПВЗ, почта). После приёмки на складе деньги возвращаются тем же способом, которым оплачивали (по 161-ФЗ). Частичный возврат — отдельная транзакция: сумма равна цене позиции минус пропорциональная скидка и пропорциональная доставка, если возврат не по вине магазина. Быстрая обработка возвратов через бот сокращает чарджбеки, потому что клиент пишет вам, а не в банк.

Как синхронизировать бот-магазин с МойСклад, RetailCRM или 1С?

Главное правило — обмен асинхронный, через очередь с ретраями. У магазина всегда своя БД заказов, а синхронизация с учёткой идёт фоновой задачей. МойСклад — REST JSON API с хорошими webhooks и лимитом 45 запросов/3 секунды, синхронизация остатков и цен раз в 5–15 минут. RetailCRM — REST с лимитом 300/минуту, силён в маркетинговых триггерах. 1С:Розница и 1С:УТ — обмен через CommerceML 2.x (XML по HTTP) или REST-обёртки; типичные грабли — большой архив каталога ломает онлайн-обновления, кодировка windows-1251 в старых конфигурациях, конфликты префиксов нумерации заказов, расхождения справочников единиц измерения. Прямой синхронный «создать заказ в 1С при оплате» — гарантированный downtime магазина при любой проблеме у бухгалтерии.