/rlebe[dev].dev
Мультиарендная SaaS-платформа

Магазин в Telegram поверх МойСклада. Подключение — вечер, не проект

Владелец даёт поддомен, токен бота и токен МойСклада — и получает работающую витрину: каталог с остатками, корзину, заказы с резервом, бронирование и личный кабинет прямо в мессенджере. Товары не заводятся заново: каталог живёт в МойСкладе, платформа его зеркалит и продаёт.

БОТ МАГАЗИНА · /START
Добро пожаловать! Здесь можно посмотреть каталог, оформить заказ или забронировать товар.
🛍 Открыть каталог Мои заказы Рассрочка Условия доставки Связаться с менеджером
ВИТРИНА · WEBAPP
ФОТО ИЗ КАРТОЧКИ МС
Перфоратор SDS-plus, 800 Вт
14 900 ₽ в наличии: 3
В корзину
ФОТО ИЗ КАРТОЧКИ МС
Набор бит, 32 предмета
1 250 ₽ бронь до 3 дней
В корзину
3 токена
всё, что нужно от владельца
0
контейнеров на магазин
196
тестов на инварианты
12
доменных приложений
Зачем это нужно

У магазина уже есть учёт. Нет канала, в котором покупатель и так сидит

Небольшой розничный или оптовый магазин ведёт товары и остатки в МойСкладе. Продавать при этом приходится в переписке: клиент спрашивает «есть ли в наличии», менеджер идёт смотреть, называет цену, записывает заказ в блокнот и потом переносит его в учётную систему руками.

Сайт

Дорого и долго для такого объёма

Интернет-магазин с интеграцией — это месяцы и бюджет, который не окупится на паре сотен позиций. А потом его ещё нужно кому-то поддерживать.

Маркетплейс

Забирает маржу и клиента

Комиссия, чужие правила и покупатель, который остаётся покупателем площадки, а не магазина.

Переписка

Заказ живёт в голове менеджера

Остатки не резервируются, договорённости теряются, а вечером кто-то вручную переносит всё в учёт — и ошибается.

Telegram

Канал, который уже открыт

Клиенту не нужно ставить приложение и регистрироваться. Он открывает бота, видит актуальные остатки и оформляет заказ, который сразу падает в учёт с резервом.

Подключение магазина

Тенант — это запись в базе, а не отдельная инфраструктура

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

1
владелец

Даёт поддомен, токен бота и токен МойСклада

Бот создаётся у @BotFather за минуту, токен МС берётся в настройках профиля. Секреты попадают в запись тенанта зашифрованными и никогда не лежат в конфигурации сервера.

2
платформа

Ставит вебхук Telegram с секретом в адресе

Бот работает только в webhook-режиме. Один общий пул процессов принимает апдейты всех магазинов и по секрету из адреса направляет их нужному боту — процесс на магазин не поднимается.

3
платформа

Настраивает МойСклад под себя

Заводит недостающие статусы заказа, дополнительные поля и вебхуки, выбирает организацию, склад и тип цены. Учётная система подстраивается под сервис, а не владелец под неё.

4
платформа

Импортирует ассортимент и включает синхронизацию

Товары, модификации, остатки и цены приезжают из МС. Дальше изменения прилетают вебхуками, а ночная сверка ловит то, что вебхук не донёс.

5
результат

Витрина на поддомене, бот отвечает на /start

Дальше владелец сам собирает меню бота, правит категории витрины и анкеты заявок в кабинете — без разработчика.

Самое сложное место

Платформа хозяйничает в чужом аккаунте. Аккуратно

Провижининг — это когда сервис заходит в учётную систему клиента и создаёт там объекты. Одна ошибка здесь означает испорченные данные компании, поэтому механика разложена на четыре шага: спецификация желаемого, планировщик, применение и сверка.

01 Матчинг только по идентификатору, никогда по имени. Всё, что платформа создала, помечено в собственной таблице соответствий. Совпадение названий ничего не значит — иначе сервис однажды «узнает» чужой статус заказа и перепишет его.
02 Не трогать чужое. Объекты МойСклада, которых нет в таблице соответствий, не рассматриваются вообще. Всё, что владелец завёл сам, остаётся его.
03 Дрейф детектируется, но не чинится молча. Владелец удалил наш статус руками — это не повод пересоздать его втихаря. Расхождение попадает в отдельный список и поднимает флаг в админке.
04 У справочника товаров нет операций записи. Ресурсы ассортимента собраны из миксинов так, что метода создания или изменения у них физически не существует. Каталог — источник истины клиента, платформа его только читает.
05 В учёт уходят только заказы. Категории витрины, бренды, слаги и картинки никогда не пишутся обратно: витрина живёт своей таксономией, а МойСклад остаётся складским учётом.
Проверено до кода

Модель «подключился за вечер» проверялась на живом аккаунте

До реализации на пробном аккаунте МойСклада проверено, что создание статусов и дополнительных полей доступно, вебхуки есть на тарифе, а тип цены читается по нужному пути. Если у токена не хватает прав, владелец видит человеческое объяснение, а не трассировку стека.

Категории

Товар без категории не теряется

Папки МойСклада зеркалятся, правила маппинга сводят их в категории витрины. Непокрытая папка не ломает синхронизацию: товар приезжает без категории и попадает в очередь на разбор в админке. Ручная привязка помечается и синхронизацией не перетирается.

Что происходит с заказом

Резерв ведётся в учёте, а не в голове

Заказ и бронь — разные сущности для покупателя, но одинаковые для склада: и то и другое уезжает в МойСклад как «Заказ покупателя» с резервом позиций и различается служебным полем. Механика резервирования написана один раз.

Идемпотентность

Двойного заказа не будет

Перед созданием документа проверяется, не выгружен ли он уже, а в МойСклад передаётся локальный идентификатор синхронизации. Повторный вебхук, ретрай воркера или второе нажатие кнопки не создают дубль.

Бронь

Товар держится ограниченное время

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

Статусы

Менеджер работает в МойСкладе

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

Конструктор

Меню бота собирается в админке

Дерево кнопок с автоответами, разделами и переходами в витрину — без правки кода и релиза. Кэшируется в Redis, поэтому меню открывается мгновенно.

Заявки

Анкеты владелец собирает сам

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

Тарифы

Возможности собираются из точек применения

Оператор платформы конструирует тарифы в админке: какие функции входят в какой план. Новый тариф — это настройка, а не выкатка новой версии.

Мультиарендность

Один пул процессов, много магазинов, ноль утечек между ними

Изоляция арендаторов — то, на чём такие платформы ломаются тише всего: ошибка не падает, она просто показывает одному магазину товары другого. Поэтому резолв тенанта сделан сквозным, а не точечным.

Контекст

Тенант определяется до первого обращения к данным

Веб-запрос — по поддомену, апдейт бота — по секрету в адресе вебхука, вебхук учёта — по своему секрету, фоновая задача — явным аргументом. Идентификатор кладётся в контекст корутины, и менеджеры моделей фильтруют выборки автоматически.

Диалоги

Состояния ботов не смешиваются

Один общий диспетчер обслуживает всех, но хранилище состояний разделено по идентификатору бота: два магазина с одним и тем же покупателем не путают его диалоги.

Лимиты

Свой ограничитель на каждый аккаунт учёта

Скользящее окно и счётчик параллельных запросов живут в Redis, поэтому лимит соблюдается поверх всех реплик. Активная синхронизация одного магазина не отнимает квоту у другого.

Секреты

Шифрование на уровне поля с ротацией ключей

Токены бота и учётной системы шифруются при записи, ключ живёт вне базы. Поддерживается набор ключей: новый шифрует, старые ещё расшифровывают — ротация без простоя.

Витрина

Вход проверяется, а не подразумевается

Личный кабинет и корзина доступны только изнутри Telegram: данные пользователя, которые присылает мессенджер, проверяются подписью. Открыть чужой кабинет по прямой ссылке нельзя.

Проверка

196 тестов написаны на инварианты, а не на строки

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

Стек и эксплуатация

Один образ, три роли

Веб, воркер и планировщик — это один и тот же образ с разными командами запуска. Меньше сборок, меньше расхождений между тем, что протестировано, и тем, что работает.

Ядро
Python 3.11Django ASGIuvicornaiogram 3django-mptt
Данные и фон
PostgreSQLRedistaskiqschedulerhttpx
Безопасность
Fernet / MultiFernetsecret_token вебхукапроверка initData
Эксплуатация
Docker Composenginxподдоменыpytest
Дальше

Продавайте там, где клиент уже есть

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

Витрина в мессенджереКаталог, корзина, заказы и бронь поверх вашей учётной системы.
Интеграция с учётомПровижининг, синхронизация, резервы, идемпотентность — так, чтобы данным клиента ничего не грозило.
SaaS под ключМультиарендность, тарифы, кабинет владельца и онбординг без участия разработчика.
Обсудить проект →