Перейти к содержанию

Cookbook: запуск Payment Manager локально

Цель: поднять PM на машине разработчика за 10–15 минут, дойти до зелёного GET /health. Аудитория: новый разработчик (или AI-агент), который только что клонировал репозиторий.

PM зависит от трёх внешних сервисов: PostgreSQL, Redis, TigerBeetle. Дальше — npm install, миграции, .env, и npm run dev.


0. Предусловия

  • Node.js 20+ (node -v).
  • Docker (для PG / Redis / TigerBeetle).
  • Свободные порты: 5432 (Postgres), 6379 (Redis), 3001 (TB), 3000 (PM API).
  • Клон репозитория: cd projects/payment-manager.

Альтернатива: в репозитории есть docker-compose.yml, который поднимает PG + PgBouncer + TigerBeetle + сам PM. Этот рецепт описывает «ручной» путь, удобный для отладки и hot-reload разработки самого PM.


1. PostgreSQL

docker run -d --name pm-postgres \
  -e POSTGRES_DB=onewallet \
  -e POSTGRES_USER=sa \
  -e POSTGRES_PASSWORD=Asd123321 \
  -p 127.0.0.1:5432:5432 \
  postgres:16-alpine

Проверь, что Postgres поднялся:

docker exec pm-postgres pg_isready -U sa
# /var/run/postgresql:5432 - accepting connections

PM ожидает схему pm.* в БД onewallet. Схема создаётся миграциями Drizzle (шаг 6) — вручную SQL писать не нужно.

Важно: PM никогда не пишет в public.* (это owned by Serverpod Auth Center). Локально схема public остаётся пустой — это нормально.


2. Redis

docker run -d --name pm-redis \
  -p 127.0.0.1:6379:6379 \
  redis:7-alpine

Проверь:

docker exec pm-redis redis-cli ping
# PONG

Redis используется для pub/sub (intent.{id} стримы для Flutter-клиента) и job-очередей Notifications Service. Пароль локально не нужен.


3. TigerBeetle

TigerBeetle требует однократного форматирования data-файла, после чего запускается как обычный сервер. В репозитории лежит готовый init-скрипт scripts/tb-init.sh — используем его:

docker run -d --name pm-tigerbeetle \
  --security-opt seccomp=unconfined \
  -v "$(pwd)/scripts/tb-init.sh:/tb-init.sh:ro" \
  -v pm-tb-data:/data \
  -p 127.0.0.1:3001:3000 \
  --entrypoint /bin/sh \
  ghcr.io/tigerbeetle/tigerbeetle:latest /tb-init.sh

Зачем seccomp=unconfined: TigerBeetle использует io_uring, который блокируется дефолтным seccomp-профилем Docker. Без этого флага сервер не стартует.

Проверь логи:

docker logs pm-tigerbeetle
# [tb-init] Formatting new TigerBeetle data file...
# [tb-init] Starting TigerBeetle...
# info(main): 0_0: cluster=0: listening on 0.0.0.0:3000

TB слушает порт 3000 внутри контейнера и мапится на 3001 снаружи — поэтому в .env будет TB_ADDRESS=127.0.0.1:3001.


4. .env

Скопируй пример и заполни секреты:

cp .env.example .env

Открой .env и обязательно поправь:

  • DATABASE_URL — на путь без PgBouncer для прямого подключения:
    DATABASE_URL=postgres://sa:Asd123321@localhost:5432/onewallet
    
  • TB_ADDRESS=127.0.0.1:3001 (добавь, если в .env.example нет — переменная обязательная).
  • INVOICE_QR_SECRETне менее 32 символов. Сгенерируй сильный секрет:
    node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
    
  • ADMIN_SECRET — тоже не менее 32 символов (валидируется через z.string().min(32)).
  • SERVICE_SECRETS — JSON-объект для HMAC-проверки входящих запросов. Дефолт из .env.example подходит для локалки.

Минимально обязательные переменные

Эти env проверяются Zod-схемой при старте — без них PM упадёт с понятной ошибкой:

Переменная Формат Зачем
DATABASE_URL postgres://… Подключение к Postgres onewallet
REDIS_URL redis://… Pub/sub и job-очереди
ADMIN_SECRET строка ≥32 символов Bearer-токен для admin endpoints
TB_ADDRESS host:port Адрес TigerBeetle
INVOICE_QR_SECRET строка ≥32 символов HMAC-подпись Merchant Invoice QR
SERVICE_SECRETS JSON {serviceId: secret} HMAC-секреты входящих сервисов

Все остальные переменные (PORT, OUTBOX_INTERVAL_MS, IPPS_*, INVOICE_DEFAULT_TTL_SECONDS и т.д.) имеют дефолты — можно не указывать.

Опасно: не коммить .env в git. Файл уже в .gitignore.


5. npm install

npm install

Установятся fastify, drizzle-orm, tigerbeetle-node, ioredis, zod, vitest и прочее (см. package.json).


6. Миграции

PM использует только Drizzle для управления схемой pm.*. Никакого ручного SQL.

npm run db:migrate

Скрипт прогонит все миграции из drizzle/ против DATABASE_URL и создаст:

  • схему pm (таблицы intents, outbox, psp_tx_map, merchant_invoices, service_keys, …),
  • системные fee-rules, transit-аккаунты, дефолтных PSP.

Никогда не пиши SQL руками — используй npm run db:generate для генерации новой миграции из изменений в drizzle/schema.ts, потом коммить полученные SQL-файлы вместе с PR.


7. Seed (опционально)

Часть seed-данных уже идёт миграциями. Дополнительные seed-скрипты для разработки:

npm run db:seed         # базовый сид (drizzle/seed.ts)
npm run seed-test-psp   # тестовые PSP-аккаунты и fee-rules для smoke-тестов

Если запускаешь PM впервые на чистой БД — прогоняй оба.


8. npm run dev

npm run dev

Команда поднимет tsx watch с подгрузкой .env и hot-reload (см. scripts в package.json: "dev": "tsx watch --env-file=.env src/server.ts").

В логах увидишь примерно:

INFO server: TigerBeetle connected
INFO PM started — roles=[api,outbox-worker] port=3000

Дефолтный PORT=3000 (см. src/server.ts).

Если падает на старте: - Invalid environment variables: … — проверь, что заполнены все 6 обязательных env из таблицы выше. - TigerBeetle unreachable after all retries — проверь TB_ADDRESS и что контейнер pm-tigerbeetle жив. - ECONNREFUSED 127.0.0.1:5432 — Postgres не запущен или порт занят.


9. Smoke-тест

curl http://localhost:3000/health

Ожидаемый ответ:

{
  "status": "ok",
  "version": "0.3.0",
  "timestamp": "2026-05-29T10:00:00.000Z"
}
  • 200 ok — PM стартанул, TigerBeetle отвечает на lookupAccounts.
  • 503 ko с "TigerBeetle unavailable" — TB упал/не стартовал; смотри docker logs pm-tigerbeetle.
  • 503 ko с "Service under pressure" — event loop перегружен (вряд ли локально).

Endpoint /health — единственный, который не требует HMAC. Все остальные роуты (включая /intents, /intents/quote, /admin/*) защищены HMAC-плагином.


10. Что дальше

  • OpenAPI UI: http://localhost:3000/docs — Swagger со всеми endpoint'ами.
  • Подписать HMAC-запрос вручную: scripts/hmac-curl.js — пример формирования заголовков X-Service-Id, X-Timestamp, X-Signature.
  • Smoke IPPS-флоу: npm run ipps-smoke — прогоняет создание intent + IPPS withdrawal end-to-end.
  • Тесты: npm test — Vitest, не требует поднятых PG/Redis/TB (вся инфраструктура мокается).
  • Production-сборка: npm run build && npm start — TypeScript → dist/, запуск через node dist/server.js.

См. также: - docs/dev/architecture/01-overview.md — архитектура и модули. - docs/dev/api/intents.md — REST endpoints. - docs/dev/operations/deployment.md — production-конфигурация.


Чек-лист готовности окружения

  • docker ps показывает pm-postgres, pm-redis, pm-tigerbeetle.
  • .env создан, все 6 обязательных переменных заполнены, секреты ≥32 символов.
  • npm run db:migrate отработал без ошибок (схема pm.* создана).
  • npm run dev стартует, в логах нет Invalid environment variables.
  • curl http://localhost:3000/health возвращает {"status":"ok",…}.