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 поднялся:
PM ожидает схему pm.* в БД onewallet. Схема создаётся миграциями Drizzle (шаг 6) — вручную SQL писать не нужно.
Важно: PM никогда не пишет в
public.*(это owned by Serverpod Auth Center). Локально схемаpublicостаётся пустой — это нормально.
2. Redis¶
Проверь:
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¶
Скопируй пример и заполни секреты:
Открой .env и обязательно поправь:
DATABASE_URL— на путь без PgBouncer для прямого подключения:TB_ADDRESS=127.0.0.1:3001(добавь, если в.env.exampleнет — переменная обязательная).INVOICE_QR_SECRET— не менее 32 символов. Сгенерируй сильный секрет: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¶
Установятся fastify, drizzle-orm, tigerbeetle-node, ioredis, zod, vitest и прочее (см. package.json).
6. Миграции¶
PM использует только Drizzle для управления схемой pm.*. Никакого ручного SQL.
Скрипт прогонит все миграции из 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¶
Команда поднимет tsx watch с подгрузкой .env и hot-reload (см. scripts в package.json:
"dev": "tsx watch --env-file=.env src/server.ts").
В логах увидишь примерно:
Дефолтный 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-тест¶
Ожидаемый ответ:
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",…}.