Кейс · действующий магазин

Владелец спит. Магазин принимает заказ.

Euphoria — семейный магазин арабской парфюмерии на Next.js, который я собрал и технически поддерживаю. Сайт и Telegram Mini App работают из единой базы. Вместо списка технологий здесь одна ночь из жизни магазина: путь настоящего заказа через код, который сейчас в проде.

в кадре: Veneno Bianco · French Avenue · 100 мл · 2650 ₴ · реальный товар из каталога

Сцены смонтированы для рассказа. Код настоящий: каждый шаг подписан функцией из продакшена.

23:47

Из инсты на витрину

Покупатель пришёл по ссылке в шапке профиля. Продавец в этот момент не нужен: карточка отвечает на вопросы сама.

Пирамида нот от верхних до базы, объём, цена и живой остаток из базы данных. Всё, что в директе спрашивают по десять раз за вечер, здесь уже написано.

Ждать тоже никого не заставили: 98/100 PageSpeed на десктопе и 100 во всех четырёх категориях Lighthouse.

111 ароматов рендерятся из одной базы: и сайт, и витрина в Telegram.

euphoria-parfum.com.ua
Карточка товара Veneno Bianco в магазине Euphoria
products · Supabase
{
  "name":      "Veneno Bianco",
  "brand":     "French Avenue",
  "price_uah": 2650,
  "volume_ml": 100,
  "in_stock":  true,
  "notes": {
    "top":   "neroli, bergamot",
    "heart": "tiare, ylang-ylang",
    "base":  "vanilla, coconut, gaiac"
  }
}

Карточка не свёрстана руками: она отрендерена из этой записи.

23:52

Корзина без лишних вопросов

Гостевой заказ без регистрации: имя, телефон, город, отделение. Оплата картой или наложенным платежом.

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

Онлайн-эквайринга здесь осознанно нет: для магазина, который продаёт из инсты, перевод на карту и наложка покрывают всех покупателей, а комиссия платёжки резала бы каждый чек. Ветка под эквайринг в коде при этом уже написана: обработчик заказа знает статус „online“ и ждёт подтверждение оплаты от платёжки. Подключение: вопрос ключей и договора, не переделки магазина.

Депозит с секретом

Для наложки магазин просит депозит: 200 гривен и копейки. Копейки от 01 до 99 не случайны: свободные выбираются так, чтобы сумма в банковской выписке однозначно называла заказ. Выписка сверяет платежи сама, владелец ничего не сличает руками.

euphoria-parfum.com.ua
Корзина магазина Euphoria
app/api/orders/route.ts
// kopiykas 01-99 are the order's code:
// the amount in the bank statement
// identifies the order
const freeKopecks: number[] = [];
for (let k = 1; k <= 99; k++) {
  if (!takenKopecks.has(k)) freeKopecks.push(k);
}
depositAmount = freeKopecks.length > 0
  ? (20000 + freeKopecks[Math.floor(
      Math.random() * freeKopecks.length)]) / 100
  : 200;

Реальный фрагмент из обработчика заказа.

23:54

Оплата прошла. Дальше работает код

Пока покупателю грузится страница „Дякуємо“, магазин делает семь дел подряд. Порядок и имена функций: ровно как в коде.

  1. Заказ записан в базу

    Статус pending, за заказом закреплён депозит с его уникальными копейками.

    orders.insert() → #EU-3B7F19C2
  2. Позиции зафиксированы

    По ценам, которые проверил сервер, а не прислал браузер.

    order_items.insert()
  3. Остаток списан

    Этого флакона на витрине стало на один меньше: сразу и на сайте, и в Telegram.

    rpc("decrement_stock")
  4. Владельцу уходит Telegram-сообщение

    Если Telegram не ответил: одна повторная попытка через 2 секунды.

    sendTelegramToOwner()
  5. Клиенту уходит письмо-подтверждение

    Не доставилось: запись в failed_emails, cron дошлёт позже.

    sendOrderConfirmationEmail()
  6. Meta получает Purchase с сервера

    event_id равен номеру заказа: браузерный дубль Meta склеит сама.

    after(sendMetaPurchaseEvent)
  7. Покупателю открывается страница подтверждения

    К этому моменту всё выше уже сделано.

    201 → { order_id }
Почему шаг 6 непростой: баг, который я поймал в проде

Симптом

В Ads Manager покупки задваивались, а часть Purchase не долетала вовсе. Реклама оптимизировалась по мусорным данным.

Диагноз

Serverless замораживает функцию сразу после ответа. Запрос к Conversions API уходил без ожидания и не успевал улететь. А браузер и сервер слали одно событие без общего event_id.

Фикс

after() из next/server: событие уходит после ответа, но до заморозки функции. Один event_id на браузер и сервер, Meta склеивает дубль сама.

Проверка

Test Events в Events Manager: один заказ = один Purchase. Серверные события доезжают, задвоение исчезло.

app/api/orders/route.ts
- // fire and forget: dropped on freeze- void sendMetaPurchaseEvent({ orderId: order.id })+ after(async () => {+   await sendMetaPurchaseEvent({+     orderId: order.id,   // = event_id+     value: verifiedTotal,+   })+ })
08:10

Утро владельца

Пока страница „Дякуємо“ дорисовывалась, у владельца в Telegram уже лежало вот это.

E
Euphoria
бот магазина
🛍 Нове замовлення #EU-3B7F19C2 👤 Ірина К. 📞 +380 •• ••• 12 47 📍 Київ, Відділення №23 💳 Накладений платіж ⚠️ очікує депозит 200.47 ₴ 🧴 Veneno Bianco 100мл × 1 — 2650 ₴ 💰 Разом: 2650 ₴ 💬 без коментарів
✅ Депозит отримано📦 Відправлено
23:54

Формат сообщения: дословно из кода. Имя и телефон вымышлены.

Депозит уже на карте, копейки в выписке совпали с заказом. Дальше две кнопки прямо под сообщением: подтвердить депозит и отметить отправку. В админку можно не заходить.

Наличие тоже живёт в этом чате: бот перечисляет товары кнопками, одно нажатие, и аромат помечен как распроданный. Витрина обновится сама.

Telegram-вебхук пишет в ту же базу, из которой рендерится сайт.

Итог ночи

Из чего сделана эта ночь

Машина

  • КаркасNext.js 15, App Router
  • ТипизацияTypeScript, strict
  • БазаSupabase, 11 таблиц
  • Продажисайт + Telegram Mini App
  • ОплатаMonobank + наложенный платёж
  • ДоставкаNova Poshta
  • ХостингVercel
332 теста · 20 сьютов 111 ароматов 98/100 PageSpeed Lighthouse 100×4 3 языка витрины

Метод

Магазин собран в паре с ИИ-агентом. Проект описан в CLAUDE.md на 350 строк: карта базы, роуты и правила, агент читает её перед каждой сессией.

332 автотеста держат поведение. Агент может смело менять код: если что-то сломалось, я узнаю об этом раньше покупателя.

Это семейный магазин, который я собрал и технически поддерживаю. Кейс без прикрас: что в проде, то и на странице.

MaegamiDev

Хотите, чтобы ваш магазин тоже работал ночью?

Соберу такой же механизм под ваш товар: витрина, корзина, оплата, доставка, Telegram. И покажу процесс так же честно, на живом примере.