Асинхронный сервис приёма платежей. Принимает запрос, кладёт событие через outbox, обрабатывает его в отдельном consumer'е (эмуляция платёжного шлюза), обновляет статус и шлёт webhook. Сообщения, не обработанные за 3 попытки, уходят в DLQ.
FastAPI · SQLAlchemy 2.0 async + PostgreSQL · Alembic · RabbitMQ (FastStream) · dishka (DI) · Docker.
POST /payments пишет платёж и outbox-событие в одной транзакции. Фоновый relay
вычитывает outbox (FOR UPDATE SKIP LOCKED) и публикует событие в payments.new.
Consumer обрабатывает платёж и шлёт webhook.
- Создание идемпотентно по заголовку
Idempotency-Key. - Вызов шлюза идемпотентен по
idплатежа - повтор сообщения не спишет дважды. - Шлюз различает
declined(финальный отказ ->failed) и временный сбой (исход неизвестен -> повтор, статус остаётсяpending, после 3 попыток DLQ). - Webhook шлётся с ретраями (1s, 2s, 4s).
Тело webhook:
{"payment_id": "...", "status": "succeeded", "amount": "199.90",
"currency": "RUB", "processed_at": "..."}make up # поднять стек, миграции применяются сами
make logs
make down # остановить и снести томаНаружу открыт только API на :8080, доки - http://localhost:8080/docs.
Все ручки требуют заголовок X-API-Key (по умолчанию local-dev-api-key).
# создать
curl -i -X POST http://localhost:8080/api/v1/payments \
-H "X-API-Key: local-dev-api-key" \
-H "Idempotency-Key: 11111111-1111-1111-1111-111111111111" \
-H "Content-Type: application/json" \
-d '{"amount": "199.90", "currency": "RUB", "webhook_url": "https://webhook.site/<id>"}'
# получить
curl http://localhost:8080/api/v1/payments/<payment_id> -H "X-API-Key: local-dev-api-key"Поведение шлюза и авторизации задаётся в deploy/dev.yml. После правки -
make up (образ пересоберётся). Те же значения переопределяются через env, напр.
GATEWAY__ERROR_RATE=1 (удобно при локальном запуске без Docker).
| Сценарий | Настройка |
|---|---|
| Успешная оплата | по умолчанию (gateway.success_rate: 0.9) |
| Отказ (declined) | gateway.success_rate: 0 |
| Временный сбой -> DLQ | gateway.error_rate: 1 |
| Сбой webhook -> DLQ | webhook_url: https://httpbin.org/status/500 |
| Идемпотентность | повторить запрос с тем же Idempotency-Key |
| Доставка webhook | webhook_url на https://webhook.site |
| Без авторизации | security.enabled: false |
Чтобы видеть очереди и DLQ, добавьте сервису rabbitmq в
deploy/docker-compose.yml ports: ["15672:15672"] и откройте
http://localhost:15672 (guest/guest).
make install
make migrate # нужен запущенный PostgreSQL
make api
make consumer
make test # pytest; PostgreSQL/RabbitMQ не нужны - инфраструктура in-memory
make lint # ruff + isort + blackСтроки подключения переопределяются через DATABASE__DSN, RABBIT__URL