Skip to content
13КейсSW · 13 из 10

Коммерция «на заказ» для рынка, которому нужен собственный стек.

Типовые ecommerce-стеки не способны описать финансовые и логистические примитивы, которых требует этот рынок. Split-платёж Monobank, доставка Nova Post, мультивалютность на уровне товара — построено с нуля для украинского maker-бренда, отгружающего в 7 локалей.

КлиентКонфиденциально
Год2026
Длительность1 yrs
СтекTypeScript · Next.js · React · Postgres · Vercel · AWS
Hero image for maker-brand-commerce-platformFIG 01 · Главное изображение

Ограничение maker-бренда.

Изделия ручной работы из Украины с доставкой в семь стран. Финансовая и логистическая инфраструктура, на которой держится этот рынок, — Monobank для платежей, Nova Post для доставки, ежедневные курсы Национального банка Украины для ценообразования — не имеет аналога в типовом европейском ecommerce-стеке.

Одна только платёжная модель исключает Stripe. Split-платёж Monobank берёт предоплату при подтверждении заказа и инициирует списание остатка, когда заказ переходит в статус «готов к отгрузке». Это два платёжных этапа, привязанных к производственному состоянию, с подписями вебхуков по ECDSA на каждом переходе. У Stripe нет примитива для этого. Пришлось бы имитировать это двумя несвязанными списаниями и самостоятельно сверять состояние — а любой разрыв в сверке становится поверхностью для мошенничества.

Логистическая модель исключает Shippo. Nova Post, доминирующий перевозчик Украины, отдаёт справочник городов, отделений и классификаторов, который нужно синхронизировать и запрашивать локально. Накладные формируются программно в момент отгрузки, а итоговый PDF-этикетки должен храниться там, откуда его можно получить. Ничто в мире абстракций типовых перевозчиков не ложится на это чисто.

Модель ценообразования исключает валютные настройки на уровне региона. Часть товаров maker продаёт по всему миру в EUR, другие — только в UAH, потому что цепочка поставок локальна. У нескольких товаров есть явные переопределения в UAH или USD, отличающиеся от базы, пересчитанной по курсу. Правила на уровне региона не могут выразить «у этого конкретного товара своя цена в UAH независимо от локали покупателя». Переопределения на уровне товара — могут.

Связка Stripe + Shippo + WooCommerce не способна описать «предоплата сейчас в UAH, остаток по готовности в EUR, доставка Nova Post» без кастомного кода на каждом стыке. Кастомная разработка была не предпочтением — это было требование рынка.

Что мы построили.

Одна платформа: клиентская витрина на 7 локалей, производственный жизненный цикл (draft → in-progress → ready → shipped) и операционная панель, которой основатель управляет в одиночку.

Платёжный слой реализует полный split-платёжный сценарий Monobank. Предоплата собирается при подтверждении заказа. Списание остатка инициируется, когда состояние заказа переходит в «готов к отгрузке». Каждый вебхук проверяется по кэшированному публичному PEM-ключу Monobank через ECDSA SHA256 с ротацией. Платёжные этапы — это записи в базе данных, привязанные к состоянию заказа, а не сверяемые постфактум.

Слой ценообразования сначала читает переопределения валюты на уровне товара и лишь затем переходит к базе, пересчитанной по курсу. У каждой строки товара есть базовая цена в EUR и опциональные колонки переопределений в UAH и USD. Когда переопределение задано, витрина использует его. Когда его нет, ежедневный курс Национального банка Украины пересчитывает базу в EUR. Курс NBU синхронизируется по cron каждый день.

Логистический слой работает на синхронизированном справочнике Nova Post. Города, отделения и классификаторы постранично подтягиваются из API Nova Post и хранятся локально — выбор адреса при оформлении запрашивает локальную копию, а не живой API. В момент отгрузки платформа формирует накладную через API Nova Post и сохраняет итоговый PDF-этикетки как presigned-объект в Cloudflare R2.

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

F · 01Split-платёж Monobank
Предоплата собирается при подтверждении заказа; остаток срабатывает по статусу «готов к отгрузке». Платёжные этапы — записи в базе, привязанные к состоянию заказа. Проверка вебхуков по ECDSA SHA256 с ротацией PEM-ключа.
F · 02Мультивалютность на уровне товара
Базовая цена в EUR с опциональными колонками переопределений в UAH и USD для каждого товара. Резолвер цены сначала читает переопределение и лишь затем переходит к ежедневному курсу NBU. Управление на уровне maker, а не региона.
F · 03Интеграция с Nova Post
Справочник городов и отделений синхронизируется локально плановой задачей. Накладные формируются в момент отгрузки через API Nova Post. PDF-этикетки хранятся как presigned-объекты в Cloudflare R2.
F · 04Производственный жизненный цикл
Машина состояний заказа из пяти состояний (draft → in-progress → ready → shipped → cancelled) с журналом событий и лентой фото прогресса. Каркас для платёжных этапов и формирования накладных — не центральный объект.
F · 05Витрина на 7 локалей
Клиентский магазин на семи языках. Переопределения валюты на уровне товара и резервный курс NBU означают, что в каждой локали отображается контекстно корректная цена без настройки на уровне региона.
F · 06Операционная панель под задачу
Производственный канбан, спарклайн выручки в EUR за 30 дней, индикаторы состояния интеграций (Monobank / Nova Post / R2 / почта), отображение свежести cron и полоса внимания. Один экран для всей операции.

Инженерные решения финтех-уровня.

Проверка вебхуков по ECDSA. Monobank подписывает платёжные вебхуки приватным ключом; получатель обязан проверять их по кэшированному публичному ключу, обновлять кэш, когда Monobank публикует новый, и отклонять любой вебхук, не прошедший проверку подписи. Игрушечная аутентификация — это проверка значения заголовка. Настоящая аутентификация — это ECDSA SHA256 против PEM-ключа с ротацией.

Платёжные этапы на уровне данных. Split-платёжная модель — это не конфигурация платёжного шлюза. Это модель данных: у заказа один или несколько платёжных этапов, у каждого — сумма, валюта, статус и триггер. Этап предоплаты срабатывает при подтверждении заказа. Этап остатка срабатывает при переходе состояния. Журнал событий несёт аудиторский след. Сверять два несвязанных API-списания постфактум означало бы, что источником истины становится панель Monobank, а не ваша собственная база данных. Мы отказались от такого размена.

Переопределения валюты на уровне товара. В таблице цен помимо базы в EUR есть три опциональные колонки: UAH, USD и флаг «цена по запросу». Резолвер цены читает их в порядке приоритета: явное переопределение → база, пересчитанная по курсу → запрос. Maker управляет ценой на уровне товара, а не на уровне региона.

Специфика локального рынка — это ров. Конкурент может скопировать дизайн витрины. Но он не сможет запустить ту же платёжную модель, не переписав платёжный слой под конкретный API Monobank.

Синхронизация справочника Nova Post. Обращение к живому API Nova Post при каждом вводе адреса на оформлении связало бы процесс покупки с внешней зависимостью, чей SLA платформа не контролирует. Синхронизация справочника выполняется как плановая задача: страницы городов, отделений и классификаторов записываются локально, а оформление запрашивает локальную копию. Единственный живой вызов Nova Post в критическом пути покупки — это формирование накладной в момент отгрузки, и происходит он уже после оплаты заказа.

Результат.

Основатель управляет всей операцией с одного экрана. Производственный канбан по состоянию заказа, спарклайн выручки за 30 дней в EUR, индикаторы состояния интеграций для Monobank, Nova Post, R2 и почты, отображение свежести cron и полоса внимания, выводящая всё, что требует действий. Около 1–2K строк компонента панели — построено под задачу, а не типовое решение.

145 коммитов за 3 недели. 659 тестовых файлов, покрывающих путь Monobank ECDSA, сверку справочника Nova Post, семантику бандлов в корзине, фиксацию валютных снимков и переходы состояний платёжных этапов. Трейсы Sentry на 5% в продакшене со скруббером PII. Axiom для структурированных событий heartbeat по cron. Наблюдаемость продакшен-уровня не прикручивалась задним числом — она была частью первого спринта.

Цитата / 04
Стек кастомный не потому, что нам хотелось писать кастомный код. Он кастомный потому, что у рынка нет готовых рельсов.
ОсновательКоммерция maker-бренда
Outcome
Build time
3 weeks
Locales
7
Test files
659
Commits
145
NEXTКейс 01SW · 01 из 10
Real estate2022 — 2024

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