ZEVRO
РАЗБОР ПРОЕКТА
006 // ЗЕВРО СИСТЕМЫ ДЛЯ ВОЛОС

Витрина и бэк-офис Где деньги и акции остаются правильными

Следующий.js 16Реагировать 19TypeScriptPostgreSQL (неон)Рабочие CloudflareПолосаCloudinaryCSS 4 попутного ветраGSAPLenis
zhr.artursdev.co.uk
КОРОТКО

Zevro продает мужские системы для волос по фиксированному каталогу моделей, размеров и цветов, причем каждый заказ поступает от поставщика после его оплаты. Магазин должен был принимать заказы по всему миру, безопасно принимать платежи по картам и никогда не продавать товар, которого нет на складе.

РЕШЕНИЕ

Я создал общедоступную витрину и отдельный бэк-офис для персонала в виде двух бессерверных приложений Next.js, работающих на одной базе данных PostgreSQL, с Stripe для платежей, Cloudinary для изображений и поиска адресов на стороне сервера.

РЕЗУЛЬТАТ

Гости и владельцы счетов могут делать заказы и оплачивать их картами из 240 стран. Сотрудники управляют каталогом, запасами, доставкой и контентом как данными, и им не нужно присматривать за серверами.

Пользовательский интерфейс для живого производства // ШЕЙДЕР СМЕЩЕНИЯ: АКТИВНЫЙ
zhr.artursdev.co.uk
ZEVRO главный экран сайта
┌─[КРАТКОЕ РЕЗЮМЕ]───────────────────────┐

Zevro Hair Systems — это полноценная платформа электронной коммерции: общедоступная витрина и CMS для персонала, каждая из которых Следующий.js 16 приложение развернуто как Облачный работник, встречаясь только в одном PostgreSQL база данных.

Нет ни отдельного API-сервера, ни Redis, ни очереди, ни собственного файлового хранилища. Схема благоприятствует корректности в тех случаях, когда речь идет о деньгах и запасах: цены пересчитываются на сервере, запасы резервируются для каждого склада в транзакции, а заказ помечается как оплаченный только после проверка с помощью Stripe.

└──────────────────────────────────────────┘
РОЛЬFull-Stack разработчик
ТИПВитрина электронной коммерции + CMS
РАМКИNext.js 16/React 19/TypeScript
БАЗА ДАННЫХPostgreSQL на Neon, простой SQL через pg
ВРЕМЯ РАБОТЫРабочие Cloudflare через OpenNext
ПЛАТЕЖИStripe Checkout (хостинг)
//Ключевые факты и показатели
2
Бессерверные приложения
Витрина + CMS, одна база данных
~120
Таблицы базы данных
65 версий SQL-миграции
~240
Страны доставки
Способы доставки – это данные.
0
Детали карты обработаны
Оформление заказа на Stripe
//Ограничения
01
ОГРАНИЧЕНИЕ 01

Никогда не отмечайте заказ как оплаченный на доверии

Магазин принимает оплату картами от гостей и владельцев счетов в любой точке мира. Возвращение на страницу успеха само по себе ничего не должно доказывать.

02
ОГРАНИЧЕНИЕ 02

Никогда не продавайте акции, которых нет

Фиксированный каталог моделей, каждая из которых представлена ​​на складах в нескольких базовых размерах и цветах волос. Тот же вариант с разными вариантами акций.

03
ОГРАНИЧЕНИЕ 03

Одно место для персонала для управления магазином

Продукты, изображения, запасы, клиенты, заказы, контент и разрешения — всем должен был управлять персонал без участия разработчика.

04
ОГРАНИЧЕНИЕ 04

Дешево в эксплуатации, нет серверов

В среде выполнения Workers нет повторного использования соединений между запросами и нет собственных модулей, поэтому доступ к базе данных, хеширование паролей и электронная почта — все это должно было соответствовать этому.

//Архитектура системы
[АУДИТОРИЯ]
КлиентыГости + владельцы счетов
ПерсоналРоли + разрешения
Будущие клиентыТот же API через HTTP
↓
[ОБЛАЧНЫЕ РАБОТНИКИ]
Витрина магазинаNext.js + /api/v1
CMSNext.js + /api/v1/cms
API внутри приложенияВызывается в процессе по страницам
↓
[ДАННЫЕ]
PostgreSQL (неон)Объединенная конечная точка
Пять схемкоммерция, клиент, cms…
65 МиграцийЕдинственный способ изменить схему
↓
[ИНТЕГРАЦИИ]
ПолосаХостинговые платежи
CloudinaryПодписанные прямые загрузки
SMTP + ГеоапифайЭлектронная почта + поиск адреса
//Путешествие Ордена
  1. 01

    Корзина

    При добавлении товара проверяется общее количество этого варианта в каждой строке корзины, поэтому интерфейс никогда не предлагает количество, от которого сервер отказался бы.

  2. 02

    Проверить

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

  3. 03

    Заказ размещен

    Строки запасов блокируются, а запасы резервируются по складам в одной транзакции. Заказ принимает идентификатор корзины, поэтому двойная отправка возвращает тот же заказ.

  4. 04

    Полоса

    На сервере создается сеанс на основе сохраненного заказа с закрепленной версией API и ключом идемпотентности, полученным из заказа, номера попытки и хеша запроса.

  5. 05

    Подтвержденный

    Заказ помечается как оплаченный только после того, как Stripe сообщит о сеансе как оплаченном и его номер, сумма и валюта совпадут. Письма отправляются после подтверждения платежа.

//Инженерные действия
01 /Два приложения, одна база данных
Одно приложение на каждую аудиторию

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

API живет внутри приложения

Каждое приложение содержит свой API в виде простых обработчиков запросов за небольшим маршрутизатором, доступ к которым осуществляется через HTTP через один универсальный маршрут или внутри процесса с его собственных страниц. Один набор проверки, авторизации и SQL без сетевого перехода между страницей и собственным API.

Бег рядом с данными

Worker открывает соединение с базой данных для каждого запроса или транзакции через конечную точку пула Neon. Интеллектуальное размещение запускает Worker рядом с базой данных, а не с посетителем, поскольку страница выполняет несколько запросов.

02 /Тщательно смоделированная база данных
Деньги никогда не являются плавучим средством

Суммы представляют собой ЧИСЛОВЫЕ значения в базе данных и десятичные текстовые значения в приложении с небольшим помощником по точной арифметике. На пути к Stripe ничто не округляется с плавающей запятой.

Заказы — это снимки

Позиция заказа копирует название продукта, артикул, цену и выбранные опции на момент покупки, поэтому последующие изменения каталога никогда не перезаписывают историю. Статус заказа, оплаты и выполнения — это три независимых столбца.

Только миграции

Миграции SQL с 65 версиями создают около 120 таблиц в пяти схемах. Они выполняются в строгом порядке, и каждый записывается со своей строкой истории в одной транзакции. Идентификаторы — это ULID: сортируемые, безопасные для раскрытия и неугадываемые.

03 /Акции, которые нельзя перепродать
Бронирование на склад

Размещение заказа блокирует строки запасов, резервирует запасы на каждом складе и точно записывает, что и где было зарезервировано в рамках одной транзакции. Отмена снимает именно эти оговорки.

Интерфейс, согласующийся с сервером

Страница продукта, корзина и заголовок сумки вычитают то, что уже есть в корзине, поэтому покупателю никогда не предлагается количество, от которого касса не отклонила бы.

04 /Платежи проверены с помощью Stripe
Создание идемпотентной сессии

Одновременные клики получают один и тот же сеанс Stripe, и исправленный запрос никогда не блокируется ранее неудачным. Запись платежа защищена, поэтому она происходит ровно один раз, сколько бы раз не был просмотрен заказ.

Отменено значит отменено

Отмена заказа сначала приводит к расчету или прекращению действия любого открытого сеанса Stripe, поэтому отмененный заказ никогда не может быть впоследствии оплачен.

Отладка там, где она выполняется

Оформить заказ не удалось только при развертывании. Просмотр журналов действующего Worker при воспроизведении реального запроса показал, что Stripe отклонил создание сеанса, поскольку в учетной записи не было предложено никаких способов оплаты. Причина устранена, и теперь в журнале записываются код ошибки Stripe и идентификатор запроса.

05 /Учетные записи, гости и безопасность
Гости и участники

Касса работает без аккаунта. Гость идентифицируется случайным токеном в файле cookie HttpOnly; сохраняется только его хеш. Пароли используют Argon2id в чистом JavaScript, а сеансы представляют собой хешированные строки базы данных, которые можно централизованно отозвать.

Право собственности на каждый запрос заказа

Каждый запрос проверяет вошедшую в систему учетную запись или гостя, владеющего токеном заказа. Гость может открыть заказ на любом устройстве, указав номер заказа, фамилию и почтовый индекс; все три должны совпадать, неверные данные дают один общий ответ, а результат доступен только для чтения.

Разрешения и проверка

Каждый маршрут CMS именует разрешение, роли содержат разрешения, а действия персонала записываются в журнал аудита. Тела запросов проверяются на соответствие заявленной форме, а неизвестные поля отклоняются.

06 /Доставка, адреса и электронная почта
Доставка как данные

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

Ввод адреса, который помогает

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

Электронная почта, которая не может отменить продажу

Фирменные электронные письма в формате HTML с альтернативным текстом отправляются при оплате заказа после подтверждения платежа. Соединение почтовой библиотеки по умолчанию не смогло выполнить подтверждение TLS на Workers; отслеживается из живых журналов и фиксируется путем подключения по имени хоста.

07 /Опыт витрины
Быстрый и индексируемый

Каталог и контент, отображаемые на сервере, со страницами продуктов, предлагающими галерею, выбор вариантов, ассортимент по складам и индивидуальные параметры.

Бренд и движение

A dark black-and-gold default theme with a light alternative, an animated brand mark on first load, smooth scrolling and scroll-linked transitions with GSAP and Lenis. Reduced-motion preferences are respected.

Контент, управляемый в CMS

Журнал, статьи, руководства, часто задаваемые вопросы и правила редактируются сотрудниками бэк-офиса.

08 /Качество и Операции
Тесты, где ошибки стоят денег

Комплексная проверка типов с автоматическими тестами, охватывающими электронные письма с заказами и их доставку, ограничения по количеству, регионы доставки, форму адреса и действия по заказу.

Развертывание одной командой

Каждое приложение создается с помощью адаптера OpenNext и публикуется в Cloudflare с помощью одной команды. Конфигурация находится в репозитории; секреты являются секретами рабочих, а не таковыми.

Проверено, где оно работает

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

//Компромиссы и следующие шаги

Подтверждение платежа осуществляется по запросу, а не по запросу

Оплата подтверждается, когда клиент возвращается к своему заказу.

СЛЕДУЮЩИЙ ШАГ

Вебхук Stripe, поэтому подтверждение и электронная почта не зависят от возвращения клиента.

Предложения адресов зависят от данных карты.

Картографические данные знают улицы лучше, чем отдельные квартиры.

СЛЕДУЮЩИЙ ШАГ

Поставщик почтовых регистров, подключенный к той же конечной точке для точных адресов в Великобритании.

Нет очереди фоновых заданий

Письмо отправляется один раз, после ответа.

СЛЕДУЮЩИЙ ШАГ

Очередь с повторными попытками, чтобы сделать доставку более надежной.

Обзоры проектируются, а не создаются

Отзывы о подтвержденных покупках, привязанных к доставленным, оплаченным заказам, с модерацией в CMS.

СЛЕДУЮЩИЙ ШАГ

Следующая функция, которую нужно построить

ПРОИЗВОДСТВО // ЖИТЬ
Посмотреть действующую версию сайта можно по адресу zhr.artursdev.co.uk.
zhr.artursdev.co.uk
Узнать стоимость

Есть идея проекта?

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

Откроется ваше почтовое приложение с заполненными данными. Ничего не будет отправлено, пока вы не нажмете «Отправить».