Назначение и границы
Повторный клик, повтор webhook и сбой поставщика не должны создавать второй заказ или повторную выдачу. Состояние операции должно оставаться понятным покупателю и оператору.
Магазин цифровых товаров с идемпотентными заказами, платёжным inbox и очередью выдачи. В демо можно пройти покупку, повторить событие и восстановить заказ после управляемого сбоя.
- Оплата и поставщики — явные моки; деньги не списываются, коды учебные.
- До 20 заказов на сессию и 1000 новых демонстрационных заказов суммарно. TTL один час, очистка каждые пять минут.
- Пример восстановления намеренно завершает первую попытку одного заказа ошибкой. Он не меняет глобальные режимы поставщиков.
Архитектура и схемы
- Цена и скидка вычисляются на сервере и фиксируются в заказе.
- Idempotency-Key защищён уникальным индексом; входные данные повтора сверяются с исходным намерением.
- Webhook сначала сохраняется в durable inbox. Отдельный worker обрабатывает платёж и очередь выдачи.
- Стабильный requestId поставщика сохраняется при повторе. Неоднозначный timeout не разрешает немедленно выдать второй код.
Code-map: от функции к коду
Карта привязана к версии 767226bf3c2f. Она помогает проследить изменение через интерфейс, API, модуль, данные и проверку.
| Функция | Экран / API | Модуль | Данные / проверка |
|---|---|---|---|
| Покупка | Storefront / POST orders | storefront.component; ShopService.createOrder | orders, products, promocodes; race tests |
| Оплата | POST payments/simulate → webhooks/payment | PaymentsController; acceptWebhook | payment_events; повтор event_id |
| Выдача | Order / GET orders/:id | worker.service processDeliveryJob | delivery_jobs, provider_requests, fulfillments; recovery |
| Сессия | Guard всех публичных заказов | portfolio-demo PortfolioGuard | HMAC cookie, prefix owner; tests/portfolio/run.ts |
| Сброс / TTL | POST demo/reset / timer | PortfolioCleanup.clear | только portfolio orders; ownership / expiry tests |
Как работать с картой
- Перед изменением найти функцию и пройти связи до API, состояния и хранилища.
- Проверить входы, ошибки, повторные события и зависимые сценарии.
- После изменения обновить карту и затронутые схемы вместе с кодом.
- Выполнить связанные проверки и привязать новые материалы к версии исходников.
Сценарии и API
- Выберите товар и выполните успешную тестовую оплату. Дождитесь кода и просмотрите историю.
- Повторите то же платёжное событие: код и выдача должны остаться единственными.
- Создайте заказ через сценарий сбоя, оплатите и нажмите «Восстановить мой заказ».
- Проверьте отказ оплаты, чужой UUID в другой сессии и сброс своего примера.
Проверка опубликованной версии
CI: CI ↗ · 767226bf · версия этого демо · 2026-09-07T00:49:05Z
CI: Portfolio immutable image ↗ · 767226bf · версия этого демо · 2026-09-07T00:49:05Z
- Real HTTP webhook, durable inbox, worker, single fulfillment preserved on event replay
- Foreign GET/recover return 404; other session reset preserves first order
- Payment failure; isolated first-attempt delivery fault; own recovery succeeds
Результаты описывают проверенные сценарии и конкретную версию. Они не являются оценкой коммерческого успеха или качества моделей на независимой выборке.
Измерения
- Сохранено 14 содержательных кадров фактического интерфейса.
- Дата, сценарий и версия указаны у каждого кадра.
- Максимум RAM контейнеров в коротком тесте: 218.56 MiB. Общие службы учитываются отдельно; магазин включает свою БД.
- Измерено 6 повторений сценария; медиана 129.0 мс, максимум 1939 мс. Время включает браузер и HTTPS; методика и ограничения приложены.
Запуск и эксплуатация
Исходный и публичный контуры используют одну бизнес-модель. Демонстрационный guard включается только PORTFOLIO_DEMO=true; вне этого режима исходные контракты сохраняются.
Сборка выполняется GitHub Actions из закреплённого commit SHA. На VPS загружается готовый GHCR-образ. База магазина остаётся отдельной от новых приложений.
Запуск: docker compose --env-file .env.production -f compose.production.yaml up -d. До переключения выполнить prisma migrate deploy. Секреты хранятся только на сервере. Seed нельзя использовать как безусловный сброс данных работающего магазина.
Readiness: /api/health/ready проверяет PostgreSQL. Во время инцидента сравнить историю заказа, inbox и delivery job; повторять выдачу по исходному orderId, не создавать новую покупку.
Общие правила эксплуатации: готовые файлы и закреплённые образы собираются вне VPS; секреты не входят в артефакты. Общесерверный docker prune не применяется. Логи backend ограничены ротацией, изменения log options требуют пересоздания контейнера.
Перед изменением пройти code-map от функции к экрану/API, модулю и данным. После изменения обновить карту и схемы, выполнить связанные сценарии, включая ошибку, повтор и сброс. Источник истины — конкретная версия кода, а не старая диаграмма.
Откат проверен 2026-09-07T01:07:46.255650+00:00: 20260907-verified → 20260907 → 20260907-verified. После каждого переключения подтверждены HTTPS, readiness и SHA. Это проверка конфигурации и артефактов на одном проверенном SHA; обратная миграция БД не выполнялась.
Стек
| Angular / TypeScript | Витрина, сигналы состояния, HTTP-клиент и polling заказа. |
|---|---|
| NestJS | Контракты, валидация, webhook, операции восстановления и границы демо. |
| PostgreSQL / Prisma | Снимки денег, inbox, очередь с lease, уникальные индексы и транзакции. |
| Node.js worker | HTTP-вызовы поставщиков вне короткой транзакции, retry и финальная выдача. |
Материалы и исходники
История реализации: 1–3 сентября 2026. Подготовка портфолио: 7 сентября 2026.