Skip to content
13Кейс-стадіSW · 13 з 10

Комерція на замовлення для ринку, якому потрібен власний стек.

Універсальні ecommerce-стеки не могли змоделювати фінансові й логістичні примітиви, яких вимагає цей ринок. Розділена оплата Monobank, фулфілмент Nova Post, мультивалютність на рівні товару — побудовано з нуля для українського maker-бренду з доставкою у 7 локалей.

КлієнтКонфіденційно
Рік2026
Тривалість1 yrs
СтекTypeScript · Next.js · React · Postgres · Vercel · AWS
Hero image for maker-brand-commerce-platformМАЛ 01 · ГЕРОЙ

Обмеження майстра.

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

Сама лише платіжна модель виключає Stripe. Модель розділеної оплати Monobank стягує депозит на момент підтвердження замовлення й ініціює списання залишку, коли замовлення доходить до статусу «готове до відправки». Це два платіжні етапи, прив'язані до стану виробництва, з ECDSA-підписами вебхуків на кожному переході. У Stripe немає примітиву для цього. Довелося б імітувати це двома непов'язаними списаннями й самостійно звіряти стан — а будь-який розрив у звірці є поверхнею для шахрайства.

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

Модель ціноутворення виключає налаштування валюти на рівні регіону. Деякі товари майстер продає глобально в EUR, інші — лише в UAH, бо ланцюг постачання локальний. Кілька товарів мають явні перевизначення в UAH або USD, що відрізняються від бази, конвертованої за курсом. Правила на рівні регіону не можуть виразити «цей конкретний товар має власну ціну в UAH незалежно від локалі покупця». Перевизначення на рівні товару — можуть.

Зв'язка Stripe + Shippo + WooCommerce не здатна змоделювати «депозит зараз у UAH, залишок при готовності в EUR, фулфілмент через Nova Post» без кастомного коду на кожному стику. Кастомна розробка була не вподобанням — вона була вимогою ринку.

Що ми побудували.

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

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

Шар ціноутворення читає перевизначення валюти на рівні товару, перш ніж відкотитися до бази, конвертованої за курсом. Кожен рядок товару має базову ціну в EUR і необов'язкові колонки перевизначення в UAH та USD. Коли перевизначення присутнє, вітрина використовує його. Коли його немає, щоденний курс Національного банку України конвертує базу в EUR. Курс НБУ синхронізується через cron щодня.

Логістичний шар працює на синхронізованому довіднику Nova Post. Міста, відділення й класифікатори посторінково підвантажуються з API Nova Post і зберігаються локально — вибір адреси на оформленні замовлення опитує локальну копію, а не живий API. На момент відправки платформа генерує накладну через API Nova Post і зберігає отриманий PDF-файл наклейки як presigned-об'єкт у Cloudflare R2.

Виробничий життєвий цикл — це каркас, що зв'язує платіжні етапи й доставку. Машина станів замовлення — п'ять станів, журнал подій, стрічка фото прогресу — існує для того, щоб перехід від депозиту до залишку мав чітко визначений тригер, а генерація накладної — чітко визначений момент. Машина станів — не продукт. Це несучий каркас для фінансових і логістичних потоків.

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

Інженерні рішення у сфері фінтеху.

ECDSA-верифікація вебхуків. Monobank підписує платіжні вебхуки приватним ключем; отримувач має верифікувати їх проти кешованого публічного ключа, оновлювати кеш, коли Monobank публікує новий, і відхиляти будь-який вебхук, що не проходить перевірку підпису. Іграшкова автентифікація — це звірка значення заголовка. Справжня автентифікація — це ECDSA SHA256 проти PEM-ключа з ротацією.

Платіжні етапи на рівні даних. Модель розділеної оплати — це не конфігурація платіжного шлюзу. Це модель даних: замовлення має один або більше платіжних етапів, кожен зі своєю сумою, валютою, статусом і тригером. Етап депозиту ініціюється при підтвердженні замовлення. Етап залишку ініціюється при переході стану. Журнал подій несе аудиторський слід. Звірка двох непов'язаних списань через API постфактум означала б, що джерелом істини є панель Monobank, а не ваша власна база даних. Ми відмовилися від такого компромісу.

Перевизначення валюти на рівні товару. Таблиця ціноутворення має три необов'язкові цінові колонки понад базу в EUR: UAH, USD і прапорець «ціна за запитом». Резолвер ціноутворення читає їх у порядку пріоритету: явне перевизначення → база, конвертована за курсом → запит. Майстер контролює ціноутворення на рівні товару, а не на рівні регіону.

Специфіка локального ринку — це рів. Конкурент може скопіювати дизайн вітрини. Він не зможе запустити ту саму платіжну модель без переписування платіжного шару під конкретний API Monobank.

Синхронізація довідника Nova Post. Виклик живого API Nova Post при кожному введенні адреси на оформленні замовлення прив'язав би процес покупки до зовнішньої залежності без SLA, який контролює платформа. Синхронізація довідника працює як заплановане завдання: сторінки міст, відділень і класифікаторів записуються локально, оформлення замовлення опитує локальну копію. Єдиний живий виклик Nova Post у критичному шляху покупки — це генерація накладної на момент відправки, і вона відбувається вже після того, як замовлення оплачене.

Результат.

Засновник керує всією операцією з одного екрана. Виробничий канбан за станом замовлення, 30-денний спарклайн виручки в EUR, індикатори стану інтеграцій для Monobank, Nova Post, R2 та email, відображення свіжості cron і смуга уваги, що виводить усе, що потребує дії. Близько 1–2K рядків компонента панелі — створеного під задачу, а не універсального.

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

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

AR-застосунок для перегляду нерухомості для покупців, які не можуть бути в кімнаті.