Бизнес-логика
Как устроена платформа доставки изнутри: заказ, оплата, водители, лояльность.
TL;DR
Pharmacy Delivery Platform — это last-mile платформа доставки на дом для регулируемой ниши (товары категории, требующей возрастной верификации). Регион — Майами и пригороды, Флорида. Клиент оформляет заказ через сайт или Telegram, и в течение 30–90 минут товар привозит водитель.
Платформа multi-tenant: одна кодовая база обслуживает несколько магазинов в одном регионе. Внутри живут шесть ролей пользователей, пять интерфейсов взаимодействия, три Telegram-бота, программа лояльности с кошельком и кэшбэком, реферальная система с QR-кампаниями, шесть зон доставки с собственными правилами и шесть способов оплаты — от наличных до криптовалюты.
Ключевые отличия от типового магазина с курьерами: реальный real-time трекинг водителя, авто-сверка наличных в конце смены, premium-подписка с бонусами и обходом ограничений, аналитика на уровне session replay и audit trail. Ниша требует строгого соответствия правилам — отсюда возрастная верификация, маскировка персональных данных и регистрация в Twilio A2P.
Pharmacy Delivery Platform — бизнес-логика
Что это за платформа
Представьте обычный продуктовый магазин у дома. Вы зашли, выбрали товары, оплатили, унесли. Теперь представьте, что между вами и магазином встал слой технологии: вы не приходите ногами — вы открываете сайт или Telegram, корзина собирается в телефоне, оплата уходит в один тап, а через час курьер звонит в дверь. Это и есть last-mile delivery: «последняя миля» от склада магазина до двери клиента.
Pharmacy Delivery Platform делает именно это, но в нише, где каждый шаг плотно регулируется. Клиент должен пройти возрастную верификацию. Магазин должен соблюдать территориальные ограничения. Водитель должен везти не только товар, но и наличные обратно — а значит, платформа считает каждый доллар с точностью до сдачи.
Архитектурно это multi-tenant система: одна и та же кодовая база обслуживает несколько магазинов одновременно. Каждый магазин видит только свои заказы, своих клиентов, свои товары и своих сотрудников. Под капотом это означает, что любой запрос к базе данных фильтруется по store_id, и ни один сотрудник одного магазина не может случайно посмотреть данные другого.
С платформой взаимодействуют через пять разных интерфейсов. Основной канал — публичный сайт-витрина, через который заказывает большинство клиентов. Дальше идут четыре Telegram-приложения: одно для покупателей (мини-магазин внутри Telegram), второе для диспетчеров (видят все входящие заказы, назначают водителей), третье для водителей (получают свои доставки, отмечают статусы), и админ-панель для бизнеса — там менеджеры и владельцы видят отчёты, управляют товарами, ценами и сотрудниками.
В системе шесть ролей: platform_admin (владелец платформы — может всё), store_admin (администратор конкретного магазина), manager (управленец без некоторых прав), dispatcher (диспетчер — распределяет заказы по водителям), driver (водитель), customer (покупатель).
Параллельно с интерфейсами работают три Python-бота в Telegram — для клиентов, водителей и диспетчеров. Боты не показывают красивые экраны: их задача — мгновенно доставить уведомление и предоставить минимальный набор команд.
Как работает заказ от и до
Шаг 1. Витрина и каталог
Клиент заходит на сайт. На главной — герой-блок, баннеры дня, категории товаров (всего 10 категорий). Клиент кликает на категорию, попадает в листинг, дальше — карточка товара с фотографиями, описанием, отзывами и вариантами (например, разные дозировки или объёмы).
Корзина устроена интересно: до момента оформления заказа она живёт в localStorage в браузере клиента. Это значит, что незарегистрированный пользователь может насобирать корзину, закрыть вкладку, вернуться через час — и всё на месте. В момент checkout корзина уезжает на сервер и привязывается к пользователю (после регистрации/входа).
Шаг 2. Адрес и зона доставки
Клиент вводит адрес. Система берёт ZIP-код и ищет его в таблице зон доставки. Зон шесть: центр Майами, Корал-Гейблз и ещё четыре. У каждой зоны свои правила — минимальная сумма заказа, ориентировочное время доставки, дополнительная плата за расстояние.
Если ZIP не покрывается ни одной зоной — клиент видит дружелюбное сообщение «мы пока не доставляем в ваш район» и предложение оставить email, чтобы узнать о расширении.
Зона доставки — это не просто полигон на карте. Это бизнес-правило: «по этому ZIP-коду от такой-то суммы, с такой-то скоростью, с такими-то ограничениями».
Шаг 3. Время доставки
Дальше — выбор времени. Два режима. Первый — express (ASAP): водитель забирает заказ как только может, доставка ориентировочно 30–90 минут. Второй — scheduled time slot: 15-минутные слоты на ближайшие дни. У каждого слота есть лимит вместимости (capacity) — после Х заказов слот закрывается, чтобы не перегружать водителей.
Поверх этого работают traffic windows — окна с пробками. Это правила в админке: «ZIP 33129 не доставляется с 17:00 до 19:00 по будням». Если клиент выбирает время и зону, попадающие в такое окно, его слот будет недоступен.
Шаг 4. Оплата
Способы оплаты — это отдельная история. Платформа принимает шесть типов: Cash on Delivery (наличные курьеру), Zelle, CashApp, Venmo, внутренний кошелёк (wallet), и криптовалюту в пяти видах — BTC (через xpub-ключ), ETH, USDT, USDC, USDT TRC-20.
Клиент может комбинировать: например, частично оплатить кошельком (списать кэшбэк), частично — наличными.
Шаг 5. Промокод
Поле для промокода. Система валидирует код — проверяет срок действия, лимит использований, минимальную сумму, привязку к конкретному клиенту. Поддерживается шесть типов промокодов: процентная скидка, фиксированная сумма, бесплатный товар в подарок, кэшбэк на кошелёк, military discount (скидка для военнослужащих с верификацией), первый заказ.
Шаг 6. Заказ создан
Клиент жмёт «Оформить». Заказ создаётся в базе со статусом PENDING. И сразу же запускается каскад уведомлений.
Через сервис Pushover диспетчерам уходит push-уведомление с приоритетом 2 — это значит, что телефон диспетчера будет звонить громко и продолжительно (до 30 секунд), пока тот не подтвердит. Параллельно бот в Telegram присылает короткое сообщение со ссылкой на заказ.
Шаг 7. Диспетчер назначает водителя
Диспетчер видит заказ либо в админке, либо в своём Telegram-mini-app. Внутри — состав, адрес, время, способ оплаты, заметки клиента. Диспетчер выбирает водителя из списка свободных и назначает заказ.
Статус меняется: PENDING → CONFIRMED → READY → ASSIGNED. Водителю прилетает push (тоже Pushover, тоже громко) — «новый заказ».
Шаг 8. Водитель в пути
Водитель открывает свой mini-app, видит детали — адрес магазина откуда забрать, адрес клиента куда везти, маршрут на карте. Едет в магазин, отмечает «Picked up» (статус PICKED_UP). Едет к клиенту.
Всё это время клиент видит в своём приложении real-time tracking: точку водителя на карте и расчётное время прибытия (ETA). Геопозиция водителя летит по WebSocket каждые несколько секунд, ETA пересчитывается по геокодингу.
Шаг 9. Доставка и отзыв
Водитель приехал, отдал товар, забрал оплату (если COD), нажал «Delivered». Статус — DELIVERED. Диспетчеру падает push с приоритетом 1 (нормальный, без сирены): «заказ доставлен».
Клиенту через несколько минут приходит запрос на отзыв: оценить заказ и/или конкретные товары.
Программа лояльности и удержания
Удержание клиентов — это не одна фича, а целый набор переплетённых механик. Я расскажу про каждую отдельно, но в реальности они работают вместе.
Wallet — внутренний кошелёк
После каждого заказа клиенту начисляется кэшбэк — процент от суммы. Этот процент зависит от уровня клиента (про уровни ниже). Кэшбэк падает на внутренний кошелёк и может быть потрачен в следующем заказе как часть оплаты.
Каждая операция с кошельком — отдельная транзакция в базе. Начисление, списание, возврат, корректировка от администратора. Полную историю видит и сам клиент в личном кабинете, и администратор в админке.
Кошелёк — это не просто бонусные баллы. Это настоящие деньги в долларах, которые клиент уже потратил, но получил часть обратно как мотивацию вернуться.
Affiliate program — реферальная программа
Каждому зарегистрированному клиенту автоматически генерируется уникальный реферальный код. Клиент может поделиться ссылкой вида ?ref=CODE — друг переходит, регистрируется, делает заказ, и приглашающий получает комиссию на свой кошелёк.
QR-коды генерируются автоматически. На главной странице героя для авторизованных клиентов есть готовый QR — наведи телефон, поделись.
Customer plans / Premium tier — уровни клиентов
Три уровня: bronze, silver, gold. Bronze — стартовый, дают всем при регистрации. Silver и gold — платные подписки или достигаются по обороту. Чем выше уровень, тем больше плюшек:
- Повышенный процент кэшбэка
- Доступ к уникальным методам оплаты (например, криптовалюта может быть закрыта для bronze)
- 24/7 заказы — bypass обычных часов работы магазина
- Bypass ZIP-restrictions — расширенная зона доставки
- Бонусные продукты при подключении уровня
Promo codes в массовых батчах
Маркетолог может одной кнопкой сгенерировать 1000 уникальных промокодов и выгрузить их CSV-файлом. Сценарий: распечатать на флаере и раздать на улице, прикрепить к заказу как сюрприз, использовать в рекламной кампании. Каждый код — single-use или multi-use, процент или сумма, с настраиваемым лимитом по сумме заказа.
QR-кампании
Отдельный механизм для оффлайн-маркетинга. Маркетолог создаёт кампанию, привязывает к ней промокод, получает короткий URL вида /c/CODE. Клиент сканирует QR — попадает на сайт — промокод уже применён, источник трафика записан.
Дальше — аналитика: сколько сканов, сколько конверсий, сколько выручки принесла кампания, средний чек по этой кампании. Можно сравнивать кампании между собой и понимать, какой канал окупается.
Day-of-week акции
Простое, но рабочее правило: «понедельник — 15% скидка на всё». Настраивается в админке: день недели, процент скидки, минимальная сумма заказа. Опционально: добавить бесплатный товар или начислить дополнительный кэшбэк.
Mix & Match — пакетные скидки
Тип скидки «купи N — получи %». Несколько тиров: купил 3 — 20% скидка, купил 5 — 30%. Привязка по категориям или конкретным товарам. Можно собрать «возьми три любых из этой категории — получи скидку».
Banners — динамические баннеры
На главной — баннеры, разные для мобильной и десктопной версии. У каждого баннера свой target URL: ведёт либо на категорию, либо на промо-страницу. Менеджер управляет баннерами в админке без участия разработчика.
Reviews
Клиент оставляет отзывы на заказы и отдельно — на конкретные товары. Рейтинги отображаются в карточках товаров и формируют «лейтмотив доверия» — соцдоказательство для новых клиентов.
Каналы коммуникации с клиентом
WhatsApp-first messaging
Основной канал для транзакционных сообщений (подтверждение заказа, статусы доставки) — WhatsApp. Под капотом — multi-node gateway: до 5 нод (физических устройств с WhatsApp Business), которые работают по схеме round-robin с sticky session — то есть один и тот же клиент всегда получает сообщения с одной и той же ноды.
Если WhatsApp не дошёл (клиент удалил приложение, заблокировал номер, нет интернета), система автоматически делает fallback на SMS.
SMS — два канала
SMS отправляются двумя путями. Первый — Twilio A2P: коммерческая регистрация кампании в США, нужная для соответствия правилам американских операторов. На момент описания платформы кампания подана и проходит TCR vetting (2–3 недели).
Второй канал — SMS Gate: Android-приложение на физических устройствах, которые отправляют SMS как с обычных номеров. Тоже round-robin между устройствами. Используется в первую очередь для OTP-кодов при регистрации и входе.
Email — для forgot-password и редких массовых рассылок. Не основной канал — большая часть клиентов получает информацию в WhatsApp/Telegram.
Pushover для команды
Команда (диспетчеры, водители, владельцы) получает уведомления через Pushover. Это не клиентский канал — это внутренний инструмент. Три уровня приоритета:
- Новый заказ → диспетчерам, priority=2 (громкая сирена 30 секунд до подтверждения — критично, чтобы заказы не терялись)
- Заказ назначен → водителю, priority=2 (громкое уведомление)
- Заказ доставлен → диспетчерам, priority=1 (нормальный звук)
Telegram-боты
Три отдельных бота:
- Бот покупателя — лёгкий канал заказа без захода на сайт
- Бот диспетчера — запасной канал, если Pushover недоступен
- Бот водителя — оперативные уведомления о новых доставках
Авторизация
Несколько способов входа: OTP через SMS, Google OAuth, Telegram WebApp с HMAC-валидацией (для mini-apps — сервер проверяет подпись Telegram, чтобы убедиться, что initData настоящая).
Бизнес-метрики и аналитика
Здесь самая интересная часть для владельца — что он видит в админке, открыв её утром.
Daily KPIs и Lifetime KPIs
Первое, что грузится на главной админки — карточки с цифрами за сегодня: collection (выручка), sales (продажи), orders (заказы), users (новые пользователи). Рядом — те же метрики за всё время существования магазина (lifetime).
Reconciliation report
Главный финансовый отчёт — сверка. Раскладывает выручку по слагаемым: gross revenue (валовая выручка), discounts (скидки), wallet used (списано с кошельков клиентов), rewards used (бесплатные товары и бонусы), net revenue (чистая выручка), tax (налог), delivery fees (плата за доставку), COGS (cost of goods sold — себестоимость), profit margin (маржа).
Открыв отчёт, владелец видит не «магазин заработал X», а полную картину: сколько денег пришло, сколько ушло на скидки и подарки, сколько осталось после налогов и закупки.
Cash flow report
Отчёт движения денег. Особенно важен в нише, где много COD-заказов. Раскладка: COD-продажи (сколько заказов оплачено наличкой), наличные в конверте у водителя (что физически собрано), digital payments (что прошло через Zelle/CashApp/Venmo/крипту), change-to-wallet (сдача, которая ушла клиенту в кошелёк вместо мелочи), owner's cash (то, что в итоге у владельца).
Driver report
За выбранный период — таблица по каждому водителю: количество доставок, среднее время на доставку, выручка, наличные собрано. Помогает понять, кто работает эффективно, кому нужна помощь, кому пора платить премию.
End-of-day report
Финальный отчёт за смену. Закрытие дня — все цифры, все остатки, все расхождения.
COG report
Отчёт по себестоимости. По каждому товару — закупочная цена, цена продажи, маржа в долларах и в процентах. Помогает понять, какие товары на самом деле приносят деньги, а какие — продаются в убыток.
Affiliate report
Топ-рефералов: кто привёл больше всего клиентов, сколько комиссии им начислено, сколько выплачено. Можно посмотреть конкретного реферала — его рефералов, их заказы, его кошелёк.
Funnel-аналитика
Воронки конверсии в трёх местах:
- Registration funnel: отправили OTP → ввели код → завершили регистрацию → сделали первый заказ. На каждом шаге — процент перехода и абсолютное число.
- Login funnel: похожая логика для входа.
- Checkout funnel: добавил в корзину → перешёл к checkout → выбрал адрес → выбрал оплату → нажал «Оформить» → оплата прошла.
Воронки показывают, где клиенты отваливаются. Если 80% людей доходят до выбора оплаты, но только 40% доходят до «Оформить» — значит, на этом шаге что-то ломается.
Conversion analytics
Отдельный экран — общая воронка от посещения до заказа: Sessions → Product Views → Cart Adds → Checkout → Orders. С процентами на каждом шаге. Это уже не про конкретного клиента — это про общее здоровье магазина.
Activity Log + session replay
Самая необычная часть аналитики. Платформа записывает действия клиентов: добавил товар в корзину, удалил, изменил количество, перешёл на страницу, отправил поиск без результатов, получил ошибку, упал JS, не доехал до checkout по причине X.
Поверх этого — rrweb session replay: запись сессий клиентов с masked inputs (все ввода маскируются — никто не увидит, что клиент написал в поле «адрес» или «номер телефона»). Только авторизованные сессии, не дольше 15 минут на запись. В админке можно открыть конкретную сессию и пересмотреть весь путь клиента — как видеоплеер.
Когда клиент жалуется «у вас ничего не работало» — открываешь его сессию, смотришь, что он реально делал, и понимаешь причину за 30 секунд.
Audit Trail
Журнал изменений для платформенного администратора. Кто и когда поменял цену товара, остаток, кошелёк клиента. Изменения в пределах двух секунд группируются в одну запись (чтобы не засорять журнал, если один редактор сохранил три поля подряд).
Issues monitor
Топ-проблемы дня. Самые частые причины отвала на checkout, JS-ошибки в браузерах клиентов, падения API-эндпоинтов. Помогает быстро увидеть, что прямо сейчас ломается.
Что делает проект сложным изнутри
Со стороны платформа выглядит как обычный магазин с курьерами. Внутри — десятки нетривиальных задач, каждая из которых может стоить денег или клиентов, если решить её плохо.
Inventory race conditions
Сценарий: на складе один последний товар. Два клиента одновременно жмут «Оформить». Без защиты — оба получат подтверждение, а товара на складе нет. Один клиент будет очень расстроен.
Решение — транзакции в базе данных с условием WHERE inventory >= qty. Если первый клиент успел — второму уйдёт ошибка ещё до создания заказа, и он увидит «товар закончился» вместо подтверждения.
Multi-tenant изоляция
Один store_id фильтрует всё: товары, заказы, клиенты, водители. Если этот фильтр потерять хотя бы в одном запросе — магазин А увидит данные магазина Б. В regulated-нише это не просто баг, это потенциальное юридическое последствие.
Real-time tracking
WebSocket-соединение между приложением водителя и сервером. Водитель шлёт геопозицию каждые несколько секунд. Сервер хранит последнюю позицию, рассчитывает ETA через геокодинг и пересылает клиенту. Если соединение разорвалось — нужно переподключение без потери контекста.
Compliance
Возрастная верификация для категории — обязательное условие. Маскировка персональных данных в screen recordings (rrweb maskAllInputs). NDA на уровне платформы. Регистрация в Twilio A2P для соответствия правилам американских операторов SMS.
Pushover priority=2
Не техническая, а операционная штука. Если новый заказ упал в систему, а диспетчер этого не заметил — заказ задержится. Пушовер с приоритетом 2 звонит телефон громкой сиреной 30 секунд, пока диспетчер не подтвердит. Это не «удобно» — это критично, чтобы заказы не пропадали.
Cash drop reconciliation
Водители возят наличные. В конце смены платформа сверяет три цифры: сколько ожидаем по COD-заказам, сколько фактически в конверте у водителя, сколько ушло клиентам как сдача в формате «положим разницу на кошелёк, а не дадим мелочью». Если три цифры не сходятся — расхождение эскалируется владельцу.
Time slots с capacity
15-минутные слоты с лимитом по числу заказов. Когда лимит достигнут — слот «гаснет» и не показывается клиентам. Это защита водителей: иначе один слот можно переполнить и сорвать всю доставку.
Traffic windows
Правила «ZIP 33129 не доставляется с 17:00 до 19:00 по будням». Каждый ZIP может иметь свои окна. В админке менеджер открывает экран «Traffic windows» и редактирует — без помощи разработчика.
Локализация
Платформа поддерживает три локали: английский, русский, испанский. Около 700 ключей перевода. Все три должны быть поддержаны и проверены — клиент в Майами может говорить на любом из этих языков.
Резюме
Платформа — это не «сайт с корзиной». Это связка из шести ролей пользователей, пяти интерфейсов, трёх ботов, шести зон доставки, шести способов оплаты и десятка переплетённых механик удержания. Каждая мелочь — от приоритета push-уведомления до правила traffic window — продумана так, чтобы заказ не потерялся, наличные не разошлись, а клиент вернулся.
Проект работает в Майами, обслуживает регулируемую нишу, и каждое решение в коде должно быть согласовано с операционной реальностью: курьерами на улицах, диспетчерами с громкими телефонами, владельцами, которые в конце дня сводят отчёты до доллара.
Связанные разделы
- Admin panel — глубокое погружение в админку: что внутри, как менеджер работает, какие отчёты строятся.
- Tech stack — технологическая часть: монорепо, NestJS, Next.js, PostgreSQL, Docker, GitHub Actions, real-time, очереди, Pushover, и всё, что под капотом.