Витрина и бэк-офис
Где деньги и акции остаются правильными
Zevro продает мужские системы для волос по фиксированному каталогу моделей, размеров и цветов, причем каждый заказ поступает от поставщика после его оплаты. Магазин должен был принимать заказы по всему миру, безопасно принимать платежи по картам и никогда не продавать товар, которого нет на складе.
Я создал общедоступную витрину и отдельный бэк-офис для персонала в виде двух бессерверных приложений Next.js, работающих на одной базе данных PostgreSQL, с Stripe для платежей, Cloudinary для изображений и поиска адресов на стороне сервера.
Гости и владельцы счетов могут делать заказы и оплачивать их картами из 240 стран. Сотрудники управляют каталогом, запасами, доставкой и контентом как данными, и им не нужно присматривать за серверами.

Zevro Hair Systems — это полноценная платформа электронной коммерции: общедоступная витрина и CMS для персонала, каждая из которых Следующий.js 16 приложение развернуто как Облачный работник, встречаясь только в одном PostgreSQL база данных.
Нет ни отдельного API-сервера, ни Redis, ни очереди, ни собственного файлового хранилища. Схема благоприятствует корректности в тех случаях, когда речь идет о деньгах и запасах: цены пересчитываются на сервере, запасы резервируются для каждого склада в транзакции, а заказ помечается как оплаченный только после проверка с помощью Stripe.
Никогда не отмечайте заказ как оплаченный на доверии
Магазин принимает оплату картами от гостей и владельцев счетов в любой точке мира. Возвращение на страницу успеха само по себе ничего не должно доказывать.
Никогда не продавайте акции, которых нет
Фиксированный каталог моделей, каждая из которых представлена на складах в нескольких базовых размерах и цветах волос. Тот же вариант с разными вариантами акций.
Одно место для персонала для управления магазином
Продукты, изображения, запасы, клиенты, заказы, контент и разрешения — всем должен был управлять персонал без участия разработчика.
Дешево в эксплуатации, нет серверов
В среде выполнения Workers нет повторного использования соединений между запросами и нет собственных модулей, поэтому доступ к базе данных, хеширование паролей и электронная почта — все это должно было соответствовать этому.
- 01
Корзина
При добавлении товара проверяется общее количество этого варианта в каждой строке корзины, поэтому интерфейс никогда не предлагает количество, от которого сервер отказался бы.
- 02
Проверить
Браузер отправляет идентификаторы и количество, но не цены. Сервер перечитывает каждую цену, стоимость доставки и валюту из базы данных.
- 03
Заказ размещен
Строки запасов блокируются, а запасы резервируются по складам в одной транзакции. Заказ принимает идентификатор корзины, поэтому двойная отправка возвращает тот же заказ.
- 04
Полоса
На сервере создается сеанс на основе сохраненного заказа с закрепленной версией API и ключом идемпотентности, полученным из заказа, номера попытки и хеша запроса.
- 05
Подтвержденный
Заказ помечается как оплаченный только после того, как Stripe сообщит о сеансе как оплаченном и его номер, сумма и валюта совпадут. Письма отправляются после подтверждения платежа.
Витрина и CMS представляют собой отдельные развертывания с отдельными сеансами, файлами cookie и поверхностями для атак. Проблема в паблике не может выставить штатные функции, так как их нет в одном приложении.
Каждое приложение содержит свой API в виде простых обработчиков запросов за небольшим маршрутизатором, доступ к которым осуществляется через HTTP через один универсальный маршрут или внутри процесса с его собственных страниц. Один набор проверки, авторизации и SQL без сетевого перехода между страницей и собственным API.
Worker открывает соединение с базой данных для каждого запроса или транзакции через конечную точку пула Neon. Интеллектуальное размещение запускает Worker рядом с базой данных, а не с посетителем, поскольку страница выполняет несколько запросов.
Суммы представляют собой ЧИСЛОВЫЕ значения в базе данных и десятичные текстовые значения в приложении с небольшим помощником по точной арифметике. На пути к Stripe ничто не округляется с плавающей запятой.
Позиция заказа копирует название продукта, артикул, цену и выбранные опции на момент покупки, поэтому последующие изменения каталога никогда не перезаписывают историю. Статус заказа, оплаты и выполнения — это три независимых столбца.
Миграции SQL с 65 версиями создают около 120 таблиц в пяти схемах. Они выполняются в строгом порядке, и каждый записывается со своей строкой истории в одной транзакции. Идентификаторы — это ULID: сортируемые, безопасные для раскрытия и неугадываемые.
Размещение заказа блокирует строки запасов, резервирует запасы на каждом складе и точно записывает, что и где было зарезервировано в рамках одной транзакции. Отмена снимает именно эти оговорки.
Страница продукта, корзина и заголовок сумки вычитают то, что уже есть в корзине, поэтому покупателю никогда не предлагается количество, от которого касса не отклонила бы.
Одновременные клики получают один и тот же сеанс Stripe, и исправленный запрос никогда не блокируется ранее неудачным. Запись платежа защищена, поэтому она происходит ровно один раз, сколько бы раз не был просмотрен заказ.
Отмена заказа сначала приводит к расчету или прекращению действия любого открытого сеанса Stripe, поэтому отмененный заказ никогда не может быть впоследствии оплачен.
Оформить заказ не удалось только при развертывании. Просмотр журналов действующего Worker при воспроизведении реального запроса показал, что Stripe отклонил создание сеанса, поскольку в учетной записи не было предложено никаких способов оплаты. Причина устранена, и теперь в журнале записываются код ошибки Stripe и идентификатор запроса.
Касса работает без аккаунта. Гость идентифицируется случайным токеном в файле cookie HttpOnly; сохраняется только его хеш. Пароли используют Argon2id в чистом JavaScript, а сеансы представляют собой хешированные строки базы данных, которые можно централизованно отозвать.
Каждый запрос проверяет вошедшую в систему учетную запись или гостя, владеющего токеном заказа. Гость может открыть заказ на любом устройстве, указав номер заказа, фамилию и почтовый индекс; все три должны совпадать, неверные данные дают один общий ответ, а результат доступен только для чтения.
Каждый маршрут CMS именует разрешение, роли содержат разрешения, а действия персонала записываются в журнал аудита. Тела запросов проверяются на соответствие заявленной форме, а неизвестные поля отклоняются.
Для каждого метода доставки указаны страны, которые он обслуживает, поэтому регионы и цены изменяются персоналом, а не кодом. Поскольку каждый заказ формируется после покупки, магазин предлагает одно заявленное обещание доставки, при этом методы доставки отключены, но готовы.
Поле почтового индекса названо в честь страны. При вводе почтового индекса указывается город, а номер дома и список улиц указываются ближайшие совпадения. Поиск выполняется на сервере, поэтому ключ поставщика никогда не раскрывается, а адрес всегда можно ввести вручную.
Фирменные электронные письма в формате HTML с альтернативным текстом отправляются при оплате заказа после подтверждения платежа. Соединение почтовой библиотеки по умолчанию не смогло выполнить подтверждение TLS на Workers; отслеживается из живых журналов и фиксируется путем подключения по имени хоста.
Каталог и контент, отображаемые на сервере, со страницами продуктов, предлагающими галерею, выбор вариантов, ассортимент по складам и индивидуальные параметры.
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.
Журнал, статьи, руководства, часто задаваемые вопросы и правила редактируются сотрудниками бэк-офиса.
Комплексная проверка типов с автоматическими тестами, охватывающими электронные письма с заказами и их доставку, ограничения по количеству, регионы доставки, форму адреса и действия по заказу.
Каждое приложение создается с помощью адаптера OpenNext и публикуется в Cloudflare с помощью одной команды. Конфигурация находится в репозитории; секреты являются секретами рабочих, а не таковыми.
Наблюдение включено для обоих рабочих процессов со структурированным журналированием ошибок при каждой интеграции. Исправления были подтверждены в развернутом приложении с реальными запросами, а не только на машине разработки.
Подтверждение платежа осуществляется по запросу, а не по запросу
Оплата подтверждается, когда клиент возвращается к своему заказу.
Вебхук Stripe, поэтому подтверждение и электронная почта не зависят от возвращения клиента.
Предложения адресов зависят от данных карты.
Картографические данные знают улицы лучше, чем отдельные квартиры.
Поставщик почтовых регистров, подключенный к той же конечной точке для точных адресов в Великобритании.
Нет очереди фоновых заданий
Письмо отправляется один раз, после ответа.
Очередь с повторными попытками, чтобы сделать доставку более надежной.
Обзоры проектируются, а не создаются
Отзывы о подтвержденных покупках, привязанных к доставленным, оплаченным заказам, с модерацией в CMS.
Следующая функция, которую нужно построить
Есть идея проекта?
Расскажи мне, чего ты пытаешься достичь. Вам не нужно знать, какая технология вам нужна: я помогу определить технический подход, объем, сроки и стоимость до начала разработки.
- ЭЛЕКТРОННАЯ ПОЧТА // info@artursdev.co.uk
- ТЕЛЕФОН // 07460 162893
- ПРЕДПОЧИТАЕТЕ ОБСУДИТЬ ГОЛОСОМ? // Напишите, чтобы договориться о звонке
- ЛИНКЕДИН // арадионовы ↗
- МЕСТОПОЛОЖЕНИЕ // Барнет, Северный Лондон, Великобритания. Работа с клиентами где угодно.