PandaPay - платёжная платформа для QR-оплаты
- финтех
- разработка бэкенда
- QR-оплата
О проекте
PandaPay - финтех-платформа для бесконтактной оплаты по QR-коду, встроенная в экосистему мессенджера MAX. Покупатель открывает клиентский Mini App прямо в мессенджере, показывает QR-код кассиру - кассовая система сканирует его и инициирует списание. Средства списываются через банк-эквайер, бонусы начисляются автоматически, уведомление приходит в реальном времени. Весь сценарий от сканирования до подтверждения занимает несколько секунд.
Команда Вебдока отвечала за два ключевых направления: архитектурное проектирование всей системы и разработку бэкенда - NestJS API, базы данных, инфраструктуры и интеграций.
Задачи
Клиент пришёл с концепцией продукта, техническим заданием и требованиями, однако без готового технического фундамента. Перед нами стояли задачи:
- спроектировать архитектуру системы с нуля и задокументировать её до начала реализации;
- разработать бэкенд, покрывающий три аудитории одновременно - покупателей, мерчантов, кассовые системы;
- реализовать нетривиальные механики: оплата в offline-режиме без интернета, real-time уведомления, каскадный fallback при отказе банка, система лояльности с бонусными баллами;
- создать API-контракт и типизированный shared-пакет так, чтобы внешняя команда, ведущая Merchant Dashboard, могла стартовать параллельно без блокеров;
- выстроить инфраструктуру: Docker Compose, Nginx, CI/CD, деплой на Ubuntu VPS.
Описание кейса
Мы начали с проектирования по методологии C4 - структурированного подхода к документированию архитектуры программных систем на четырёх уровнях детализации.
System Context (уровень 1)
На первом уровне мы определили всех внешних акторов системы и типы взаимодействий:
- Покупатель - работает через клиентский Mini App в WebView мессенджера MAX, подключается по HTTPS и WebSocket;
- Мерчант - управляет бизнесом через отдельный Merchant Dashboard в браузере;
- Кассир - работает в интерфейсе внешней кассовой системы (POS), которая взаимодействует с платформой по REST;
- Команда платформы - мониторинг через Admin API и Bull-board;
- MAX - мессенджер, в WebView которого живёт клиентский Mini App; интеграция через MAX Bridge API, SSO, Bot API и Channel API;
- Банк-эквайер - двусторонняя интеграция: HTTPS REST для charge/refund и HTTPS Webhook для колбеков о результате транзакции;
- Кассовые системы (POS) - инициируют платежи через POS API, получают результат через webhook или long polling.
Containers (уровень 2)
На втором уровне мы декомпозировали систему на развёртываемые контейнеры:
- Backend API (NestJS) - единый модульный монолит, обслуживающий все аудитории: Client API, Merchant API, POS API, Webhook API, Admin API и WebSocket Gateway;
- PostgreSQL 16 - основное хранилище с ACID-гарантиями для всех финансовых операций;
- Redis 7 - кэш, очереди Bull, rate limiting, session storage, WebSocket adapter для горизонтального масштабирования;
- Mini App (React) - клиентский SPA в WebView MAX, только для покупателей;
- Merchant Dashboard (React) - отдельный SPA в браузере, разрабатывается внешней командой на основе нашего API-контракта.
Components (уровень 3)
Третий уровень - детальное проектирование внутренней структуры NestJS-монолита: границы модулей, их ответственности, направления зависимостей, типы взаимодействий (синхронные HTTP, асинхронные очереди, события).
Что дало проектирование
Полный набор архитектурных артефактов до начала реализации позволил:
- стартовать разработку Merchant Dashboard параллельно силами внешней команды без блокеров;
- согласовать 13 спорных архитектурных решений с клиентом до написания кода;
- зафиксировать все расхождения между исходной спецификацией и финальными архитектурными решениями в явном виде;
- обеспечить единый язык между командами через shared-пакет
@pandapay/sharedс TypeScript-типами и Zod-схемами.
Выбор архитектурного стиля: модульный монолит
Для MVP мы выбрали модульный монолит вместо микросервисов. Это осознанное и обоснованное решение:
Скорость разработки. Микросервисная архитектура добавляет значительный оверхед: управление межсервисным транспортом, service discovery, распределённые транзакции, separate deployments. Для MVP эти затраты не окупаются.
Чистые границы без сетевых издержек. Модульный монолит с правильно спроектированными границами даёт все преимущества изоляции бизнес-логики без накладных расходов на сетевое взаимодействие. Каждый модуль - self-contained единица с чёткой зоной ответственности.
Путь к масштабированию без рефакторинга. Когда придёт время масштабировать, модули с чистыми границами выносятся в отдельные сервисы без переписывания бизнес-логики. Bull-воркеры уже сейчас можно запустить в отдельном процессе на том же образе.
Бэкенд: архитектура модулей
Auth Module
Модуль аутентификации обслуживает все четыре контекста с изолированными guards:
- ClientAuthGuard - JWT Bearer для покупателей, полученный через SSO с MAX;
- MerchantAuthGuard - JWT Bearer для мерчантов, выпускаемый по email/password;
- PosApiKeyGuard - API-key аутентификация для кассовых систем (
X-API-Key); - AdminAuthGuard - отдельный Bearer-токен из environment variables для команды платформы.
MVP использует mock-SSO для MAX для ускорения разработки. В production - WebAppData HMAC-SHA256 валидация с BOT_TOKEN, которая подтверждает, что запрос действительно пришёл из MAX. JWT access token живёт 15 минут, refresh token - 30 дней. Реализован reuse detection для refresh tokens: при повторном использовании одного refresh token вся семья токенов инвалидируется.
Авторизация мерчантов построена на классическом email/password с argon2id для хранения паролей. Новый мерчант регистрируется через публичный эндпоинт и получает статус PENDING - активацию производит команда платформы через Admin API. Это даёт контроль над онбордингом и фильтрует мусорные регистрации.
Payment Module
Ядро системы. Payment Module реализует оркестрацию всего платёжного цикла:
- Приём команды от POS API (
POST /v1/pos/payments/init); - Создание платежа в PostgreSQL и установка
expires_at; - Переход в состояние
AWAITING_CONFIRMи push checkout-события клиенту через WebSocket; - Обработка подтверждения от клиента и отправка charge в банк;
- Приём webhook от банка и переход в финальное состояние;
- Асинхронный запуск side effects: уведомление POS, начисление бонусов, уведомление в MAX.
Идемпотентность реализована на всех платёжных эндпоинтах. Для POS init используется составной уникальный ключ (pos_id, idempotency_key) - кассовая система генерирует его сама (UUID или составной ключ типа order-123-attempt-1). При повторном запросе с тем же ключом возвращается 409 с данными существующего платежа.
Каскадный fallback: если основной метод оплаты отклонён банком, и у пользователя настроен резервный метод, и пользователь явно разрешил cascade_payment_enabled - создаётся вторая попытка (PAYMENT_ATTEMPT.kind = FALLBACK). Решение о fallback принадлежит пользователю, не мерчанту.
Auto-confirm: пользователь может настроить автоматическое подтверждение платежей без интерактивного confirm до определённого лимита суммы (auto_confirm_limit, default 500 ₽). Это ускоряет оплату для малых сумм.
QR Module
Модуль управляет двумя режимами генерации QR-кодов:
Online QR (session token). Бэкенд генерирует случайный session token и сохраняет в Redis qr:session:{token} со структурой {user_id, payment_method_id} и TTL 40 секунд. Mini App обновляет QR каждые ~35 секунд. Если пользователь меняет метод оплаты - PATCH /v1/client/qr/source обновляет payment_method_id в Redis без перегенерации самого QR-кода.
Offline QR (signed token pack). Пользователь запрашивает пакет из 20 самодостаточных HMAC-подписанных токенов с TTL 15 минут. Бэкенд не хранит неиспользованные токены в Redis - они полностью самодостаточны. Redis задействован только для replay protection: атомарный SET qr:used:{token} 1 NX EX <ttl>. При валидации проверяются подпись, TTL, device binding и отсутствие токена в qr:used:*. Это чистая signed-model без гибридной stateful/stateless сложности.
Loyalty Module
Система лояльности работает как side effect успешного платежа:
- cashback_percent - процент от суммы транзакции, настраивается мерчантом (например, 5.00% или 12.50%);
- welcome_bonus - приветственный бонус при первой покупке у конкретного мерчанта;
- балансы по мерчантам - бонусы у каждого мерчанта учитываются отдельно;
- списание перед деньгами - при оплате сначала списываются бонусы, затем денежные средства;
- отмена при возврате - при успешном refund начисленные бонусы сторнируются, ранее списанные - возвращаются.
Начисление бонусов выполняется асинхронно через Bull-очередь loyalty-accrual, не блокируя основной платёжный flow.
Merchant Module
Полный цикл работы мерчанта:
- Онбординг - регистрация с юридическими данными (ИНН, расчётный счёт, БИК, наименование);
- Управление точками продаж - CRUD POS с уникальными API-ключами для каждой кассы;
- POS API-ключи - один активный ключ на POS, plaintext выдаётся один раз, в базе хранится только bcrypt-хэш; при выпуске нового ключа предыдущий автоматически отзывается;
- Настройки лояльности -
cashback_percent,welcome_bonus, опциональный MAX-канал; - Статические QR для печати - генерация PDF с QR-кодами для размещения в точках продаж.
В MVP реализована только роль OWNER. Схема данных и RBAC (OWNER/MANAGER/ACCOUNTANT/CASHIER) заложены в Prisma-модель заранее, чтобы при post-MVP расширении не делать breaking migration.
Transaction Module
Read-модель для истории операций:
- для покупателей - лента покупок с фильтрацией по периоду (
today/7d/30d/1y/all), агрегаты по сумме и количеству; - для мерчантов - реестр транзакций с фильтрацией, пагинацией, поиском;
- детализация чека, ссылка на ОФД;
- инициирование возврата с передачей управления в Payment Module.
Bank Adapter
Абстракция над API конкретного банка-эквайера. Три метода: charge(token, amount), refund(tx_id), tokenize(redirect_url). Такая абстракция изолирует бизнес-логику от деталей конкретного эквайера - смена банка или подключение второго не требует изменений в Payment Module.
Входящие webhooks от банка проходят через inbox-таблицу BANK_WEBHOOK_EVENT с дедупликацией по provider_event_id. Подпись каждого webhook проверяется HMAC-SHA256 с использованием BANK_API_WEBHOOK_SECRET. Если подпись невалидна - событие сохраняется в inbox с signature_valid=false и не обрабатывается.
MAX Adapter
Интеграция с мессенджером MAX:
- MVP: mock-SSO для ускорения разработки и ранней интеграции с командой Mini App;
- Production: валидация
initData(WebAppData) через HMAC-SHA256 сBOT_TOKEN, извлечениеuser_id, имени и языка пользователя; - Bot API: отправка чека от имени бота мерчанта в чат покупателя после успешной оплаты;
- Channel API: подписка пользователя на канал мерчанта.
POS Adapter
Интерфейс для кассовых систем:
POST /v1/pos/payments/init- инициация платежа с суммой и idempotency_key;GET /v1/pos/payments/{id}/status- long polling для получения результата;- Webhook: push-уведомление на зарегистрированный URL кассы при изменении статуса.
Кассовая система сама выбирает режим получения результата: long polling подходит для простых интеграций, webhook - для production-систем с обработкой событий.
Admin Module
Инструменты для команды платформы:
- Bull-board UI (
/admin/queues) - визуальная панель управления очередями: просмотр pending/active/failed задач, ручной retry зависших jobs; - Health-check - статус PostgreSQL, Redis и Bull-очередей;
- Мониторинг платежей - активные платежи, зависшие (>5 мин в PROCESSING), агрегаты по суммам и конверсии;
- Управление мерчантами - активация (
PENDING → ACTIVE), приостановка (ACTIVE → SUSPENDED), отклонение заявок (PENDING → REJECTED); - Метрики - Redis keys по паттернам, количество WebSocket-соединений, rate limit counters.
Admin-контур полностью изолирован от client/merchant auth: отдельный AdminAuthGuard с Bearer-токеном из env.
Конечный автомат платежа
Одним из центральных архитектурных артефактов стала формальная машина состояний платежа. PAYMENT.status в PostgreSQL - единственный источник истины.
Состояния
| Состояние | Смысл |
|---|---|
CREATED | Платёж создан кассой, checkout ещё не открыт клиенту |
AWAITING_CONFIRM | Покупатель видит экран подтверждения |
PROCESSING | Charge отправлен в банк, ожидаем webhook |
SUCCESS | Списание успешно, возможен refund |
FAILED | Попытка отклонена, оценивается fallback |
FAILED_FINAL | Платёж окончательно отклонён |
CANCELLED | Покупатель отменил до отправки в банк |
EXPIRED | Истёк TTL без подтверждения |
REFUND_PENDING | Возврат отправлен в банк |
REFUNDED | Возврат успешно завершён |
REFUND_FAILED | Банк отклонил возврат |
FAILED - не финальный статус, а развилка: если разрешён fallback и есть активный резервный метод, создаётся вторая PAYMENT_ATTEMPT(kind=FALLBACK) и платёж уходит обратно в PROCESSING. В MVP максимум два PAYMENT_ATTEMPT на один платёж.
Финальный исход charge и refund определяется bank webhook, а не синхронным HTTP-ответом банка. Это принципиально: синхронный ответ может быть timeout или недостоверным в случае сетевых проблем - webhook приходит, когда банк реально завершил операцию.
Все переходы состояний записываются в append-only таблицу PAYMENT_EVENT с информацией о триггере, акторе и контексте. Это полный audit trail для каждого платежа.
Recovery flow
Ключевой UX-проблемой в платёжных системах является разрыв соединения в момент оплаты. Если покупатель свернул Mini App или потерял интернет во время AWAITING_CONFIRM, платёж не должен зависнуть.
Решение: при создании checkout бэкенд записывает payment:pending:{user_id} в Redis (TTL 300 сек). При повторном открытии Mini App клиент всегда проверяет GET /v1/client/payments/pending и восстанавливает активный экран. Redis хранит только read-модель для UX - источником истины остаётся PostgreSQL.
WebSocket - основной канал доставки checkout-события. GET /v1/client/payments/pending - канонический fallback при потере соединения или переоткрытии Mini App.
Модель данных
Ключевые сущности
USER - покупатель Mini App. Хранит настройки: show_qr_on_open (показывать QR при открытии), cascade_payment_enabled (разрешить fallback на резервный метод), auto_confirm_enabled и auto_confirm_limit (автоподтверждение платежей до лимита).
PAYMENT_METHOD - привязанный способ оплаты: СБП или карта. PAN/CVC не хранятся никогда - только token_ref как ссылка на токен у банка/провайдера. Soft delete для отвязки методов.
MERCHANT - мерчант с юридическими данными (inn, settlement_account, bik), настройками лояльности (cashback_percent, welcome_bonus) и статусом (PENDING/ACTIVE/SUSPENDED/REJECTED). Пароль хранится как argon2id hash.
POS / POS_API_KEY - точка продаж и её API-ключи. В базе хранится только bcrypt-хэш ключа; история отозванных ключей сохраняется для audit.
PAYMENT - главный агрегат с status, суммой, ссылками на payment_method_id и fallback_payment_method_id, idempotency_key, временными метками и bank_tx_id.
PAYMENT_ATTEMPT - отдельная попытка списания (kind: PRIMARY/FALLBACK) с provider_tx_id, decline_code и временными метками.
PAYMENT_EVENT - append-only журнал всех переходов состояний. Каждая запись фиксирует from_status, to_status, тип события, актора и payload.
LOYALTY_ACCOUNT / LOYALTY_TRANSACTION - ledger-модель для бонусных балансов в разрезе по мерчантам.
BANK_WEBHOOK_EVENT - inbox для входящих событий банка с provider_event_id для дедупликации.
Принципы проектирования схемы
- Все денежные суммы -
DECIMAL(12,2), а неFLOAT. Floating point неприемлем для финансов. - Cascade payment и прочие настройки пользователя - поля
USER, а неMERCHANT: это выбор пользователя, не бизнес-конфигурация мерчанта. - Mutable fields с
updated_at, immutable events вPAYMENT_EVENT- чёткое разделение мутабельного состояния и неизменяемого журнала. - Soft delete везде, где нужна история: методы оплаты, сотрудники, API-ключи.
Безопасность: модель токенов и ключей
Система работает с несколькими классами криптографических материалов:
QR Session Token - случайная строка в Redis, TTL 40 сек. Не содержит ничего секретного, но однозначно идентифицирует пользователя и его метод оплаты в момент сканирования.
Offline HMAC Token - самодостаточный подписанный токен для бесинтернетной оплаты. Содержит user_id, payment_method_id, device_id, exp, нунс. Подписывается HMAC-SHA256 на серверном секрете. Бэкенд не хранит токен - только проверяет подпись и наличие в replay protection set Redis.
JWT Access/Refresh - стандартная JWT-пара, access TTL 15 мин / refresh TTL 30 дней. Refresh token rotation с reuse detection: повторное использование одного refresh token приводит к инвалидации всей семьи.
POS API Key - секрет для кассовых систем. plaintext выдаётся один раз при создании, в базе хранится bcrypt(cost=10). При выпуске нового ключа предыдущий отзывается атомарно.
Bank Webhook Signature - HMAC-SHA256 подпись от тела webhook с использованием BANK_API_WEBHOOK_SECRET. Позволяет убедиться, что webhook пришёл от реального банка.
MAX WebAppData HMAC - production-валидация initData от MAX через HMAC-SHA256 с BOT_TOKEN. Подтверждает, что запрос действительно инициирован из мессенджера пользователем с конкретным user_id.
API-дизайн: пять контекстов в одном монолите
Все публичные маршруты работают под префиксом /v1 с разбивкой по аудитории:
Client API (/v1/client/*)
Покрывает полный клиентский сценарий: SSO с MAX, управление профилем и методами оплаты, QR-сессии (online и offline), подтверждение платежа, recovery pending checkout, история транзакций с агрегатами.
Реализован механизм смены метода оплаты без перегенерации QR: PATCH /v1/client/qr/source обновляет payment_method_id в Redis-сессии. Пользователь может переключиться с карты на СБП прямо на экране QR.
Merchant API (/v1/merchant/*)
Регистрация и авторизация мерчанта, управление профилем и настройками лояльности, CRUD точек продаж, управление API-ключами касс, история транзакций с реестром и фильтрацией, инициирование возвратов.
Все merchant-эндпоинты защищены MerchantAuthGuard, который проверяет merchant-JWT и извлекает employee_id + role из payload.
POS API (/v1/pos/*)
Инициация платежа с idempotency_key, получение статуса через long polling или webhook. API-key аутентификация. Минималистичный контракт, ориентированный на интеграцию с кассовым ПО.
Webhook API (/v1/webhooks/*)
Получение событий от банка-эквайера. Каждый входящий запрос: проверка HMAC-подписи, запись в inbox (BANK_WEBHOOK_EVENT), постановка в Bull-очередь bank-webhooks для асинхронной обработки.
Admin API (/v1/admin/*)
Мониторинг системы, управление мерчантами (активация/блокировка), просмотр активных и зависших платежей, агрегаты. Отдельный AdminAuthGuard, изолированный от client/merchant auth.
Стандарт ошибок
Все ошибки возвращаются в единой структуре:
{
"error": {
"code": "PAYMENT_METHOD_DECLINED",
"message": "Недостаточно средств на счёте",
"details": {}
}
}
Машинно-читаемые code позволяют клиентским приложениям обрабатывать ошибки точечно, не парсируя текстовые сообщения.
Real-time: WebSocket Gateway
Для real-time уведомлений реализован Socket.IO Gateway на NestJS. Клиент (Mini App) и касса (POS) подключаются по WebSocket и получают события без polling.
Ключевые события:
payment:confirm- кассе нужно подтвердить оплату; клиент видит экран checkout;payment:success/payment:failed- финальный результат платежа обеим сторонам;payment:cancelled/payment:expired- статусные уведомления.
WebSocket-сессии аутентифицируются JWT на handshake. Redis используется как adapter для Socket.IO: при горизонтальном масштабировании несколько инстансов API синхронизируют события через Redis pub/sub. Это позволяет масштабировать WebSocket-контур без sticky sessions.
Очереди: асинхронная обработка событий
Bull на Redis обеспечивает три ключевые очереди:
bank-webhooks - обработка входящих событий банка. Очередь гарантирует, что webhook не потеряется, даже если в момент поступления система перегружена. Retry с backoff при транзиентных ошибках.
loyalty-accrual - начисление бонусов после успешного платежа. Асинхронное выполнение не задерживает основной платёжный flow.
max-notifications - отправка чека в MAX через Bot API. Failure в этой очереди не влияет на статус платежа.
Bull-board UI (/admin/queues) даёт команде платформы визуальный интерфейс для мониторинга состояния очередей, просмотра зависших задач и ручного retry.
Инфраструктура и деплой
Монорепо
Проект организован как pnpm-монорепо с Turborepo:
apps/api- NestJS-монолит;apps/mini-app- клиентский SPA в MAX;packages/shared-@pandapay/sharedс TypeScript-типами и Zod-схемами.
Shared-пакет - связующее звено между нашей командой и командой Merchant Dashboard. Внешняя команда подключает пакет и получает актуальные типы и схемы без ручной синхронизации контрактов.
Turborepo управляет кэшированием артефактов сборки и параллельным запуском задач. pnpm workspaces обеспечивает изоляцию зависимостей между пакетами.
Docker Compose
Для MVP выбран Docker Compose - достаточный инструмент для одного VPS без оверхеда Kubernetes. Стек:
api- NestJS-приложение (expose: 3000), не публикуется напрямую в интернет;postgres- PostgreSQL 16 Alpine с persistent volume;redis- Redis 7 Alpine сappendonly yesи persistent volume;nginx- reverse proxy, единственный контейнер с публичными портами 80/443.
Nginx маршрутизирует по доменам: api.pandapay.ru/v1/* → NestJS API, /admin/queues → Bull-board. Mini App и Merchant Dashboard живут на внешних серверах других команд и обращаются к API через CORS.
CI/CD
Релизный процесс: push в Git → CI lint/test/build → Docker image → push в registry → docker compose pull && docker compose up -d на VPS. Сервер не является рабочей git-копией - доставка только через CI. Rollback по тегу image.
Домены и маршрутизация
api.pandapay.ru/v1/* → Backend API (NestJS :3000)
api.pandapay.ru/admin/queues → Bull-board UI
api.pandapay.ru/health → Health check
max.pandapay.ru → Mini App (внешний сервер)
merc.pandapay.ru → Merchant Dashboard (внешний сервер)
Планируемые этапы развития
Self-Ordering (Stage 6)
После релиза MVP запланирован сценарий самостоятельного заказа. Покупатель сканирует статический QR на столике или витрине заведения, Mini App открывается в контексте конкретного мерчанта и конкретной точки (или стола). Покупатель видит меню, формирует заказ и оплачивает через уже существующий C2B-механизм.
Это потребует новых модулей на бэкенде: catalog (товары/услуги мерчанта по категориям с ценами) и order (управление заказом, связь с Payment после оформления). Существующий платёжный flow меняться не будет.
Масштабирование
Архитектура готова к горизонтальному масштабированию без рефакторинга бизнес-логики:
- несколько инстансов
apiза Nginx load balancer; - Redis adapter для Socket.IO синхронизирует WebSocket события между инстансами;
- Bull-воркеры выносятся в отдельный процесс (тот же Docker-образ, другая команда запуска);
- при необходимости - выделение отдельного
worker-сервиса в compose.
Технологический стек
| Слой | Технологии |
|---|---|
| Backend | NestJS 10, TypeScript, Prisma ORM |
| База данных | PostgreSQL 16 |
| Кэш и очереди | Redis 7, Bull |
| Real-time | Socket.IO (NestJS Gateway) |
| Аутентификация | JWT (access 15 мин / refresh 30 дней), HMAC-SHA256, argon2id, bcrypt |
| API | REST, WebSocket, Long Polling, Webhook |
| Монорепо | pnpm workspaces, Turborepo |
| Инфраструктура | Docker Compose, Nginx, Ubuntu 24 LTS |
| Shared | @pandapay/shared (TypeScript types + Zod schemas) |
Итог
Мы спроектировали и разработали бэкенд платёжной платформы с нуля: от архитектурных артефактов C4 до работающего NestJS-монолита с PostgreSQL, Redis, Bull-очередями и WebSocket.
Система покрывает полный платёжный цикл: инициация с POS → QR на экране покупателя → подтверждение → списание через банк → real-time уведомления → начисление бонусов. Реализована offline-оплата без интернета на HMAC-подписанных токенах, каскадный fallback при отказе банка, recovery flow при разрыве соединения, полный refund-процесс.
Архитектурная документация позволила параллельно запустить разработку Merchant Dashboard внешней командой с первого дня - без блокеров и ручной синхронизации контрактов. Все ключевые решения согласованы с клиентом до написания кода и зафиксированы в явном виде.
Стек: NestJS · TypeScript · Prisma · PostgreSQL · Redis · Bull · Socket.IO · Docker · Nginx · pnpm · Turborepo
Роль: Архитектурное проектирование (C4) · Разработка бэкенда · API-дизайн · Безопасность · Инфраструктура · Shared-пакет
Технологии