гора.
Pharmacy Delivery Platform

Бизнес-логика

Как устроена платформа доставки изнутри: заказ, оплата, водители, лояльность.

~15 мин чтения · 2938 слов

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.

Клиент может комбинировать: например, частично оплатить кошельком (списать кэшбэк), частично — наличными.

Checkout — выбор адреса, времени, способа оплаты

Шаг 5. Промокод

Поле для промокода. Система валидирует код — проверяет срок действия, лимит использований, минимальную сумму, привязку к конкретному клиенту. Поддерживается шесть типов промокодов: процентная скидка, фиксированная сумма, бесплатный товар в подарок, кэшбэк на кошелёк, military discount (скидка для военнослужащих с верификацией), первый заказ.

Шаг 6. Заказ создан

Клиент жмёт «Оформить». Заказ создаётся в базе со статусом PENDING. И сразу же запускается каскад уведомлений.

Через сервис Pushover диспетчерам уходит push-уведомление с приоритетом 2 — это значит, что телефон диспетчера будет звонить громко и продолжительно (до 30 секунд), пока тот не подтвердит. Параллельно бот в Telegram присылает короткое сообщение со ссылкой на заказ.

Шаг 7. Диспетчер назначает водителя

Диспетчер видит заказ либо в админке, либо в своём Telegram-mini-app. Внутри — состав, адрес, время, способ оплаты, заметки клиента. Диспетчер выбирает водителя из списка свободных и назначает заказ.

Статус меняется: PENDINGCONFIRMEDREADYASSIGNED. Водителю прилетает 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

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, и всё, что под капотом.