# Асинхронный сервис процессинга платежей Микросервис принимает платежи по HTTP, атомарно сохраняет платёж и outbox-событие в PostgreSQL, публикует событие в RabbitMQ и асинхронно отправляет результат на webhook. ## Запуск Требования: Docker с Compose plugin. ```bash docker compose up --build ``` После запуска доступны: - API и Swagger UI: - RabbitMQ Management: (`guest` / `guest`) - API key по умолчанию: `local-secret`; для другого значения задайте `API_KEY` Миграции выполняются контейнером `migrate` до запуска API и consumer. ## API Создать платёж: ```bash curl -i -X POST http://localhost:8000/api/v1/payments \ -H 'Content-Type: application/json' \ -H 'X-API-Key: local-secret' \ -H 'Idempotency-Key: order-123-attempt-1' \ -d '{ "amount": "1250.50", "currency": "RUB", "description": "Заказ 123", "metadata": {"order_id": 123}, "webhook_url": "https://example.com/payment-webhook" }' ``` Ответ имеет статус `202 Accepted`: ```json { "payment_id": "8f814d58-8a52-493f-aafb-98b383aa0325", "status": "pending", "created_at": "2026-09-16T08:00:00Z" } ``` Повторный запрос с тем же `Idempotency-Key` вернёт тот же платёж и не создаст новое outbox-событие. Получить платёж: ```bash curl http://localhost:8000/api/v1/payments/8f814d58-8a52-493f-aafb-98b383aa0325 \ -H 'X-API-Key: local-secret' ``` Webhook получает JSON следующего вида: ```json { "payment_id": "8f814d58-8a52-493f-aafb-98b383aa0325", "amount": "1250.50", "currency": "RUB", "status": "succeeded", "processed_at": "2026-09-16T08:00:04Z", "metadata": {"order_id": 123} } ``` ## Надёжность - `payments` и `outbox` записываются в одной транзакции. - Publisher читает outbox через `FOR UPDATE SKIP LOCKED` и помечает событие после publisher confirm RabbitMQ. - Доставка события имеет семантику at-least-once. Consumer идемпотентен по состоянию платежа. - Обработка и webhook выполняются до трёх раз с задержками 1 и 2 секунды. - После исчерпания попыток сообщение отклоняется без requeue и RabbitMQ переносит его через `payments.dlx` в `payments.dead`. - Если процесс остановился после обновления статуса, но до webhook, повторная доставка продолжит только webhook. ## Локальная разработка ```bash python -m venv .venv . .venv/bin/activate pip install -e '.[dev]' cp .env.example .env ruff check . ty check pytest tests/unit pytest tests/integration ``` Unit-тесты не требуют внешних сервисов. Integration-тесты используют Testcontainers и требуют запущенный Docker; они поднимают изолированные PostgreSQL и RabbitMQ на случайных портах. Для локального запуска без Docker измените в `.env` адреса PostgreSQL и RabbitMQ на `localhost`, затем выполните: ```bash alembic upgrade head uvicorn app.main:app --reload faststream run app.consumer:app ``` Основные параметры эмуляции (`PROCESSING_*`), retry и polling outbox перечислены в `.env.example`.