Асинхронный сервис процессинга платежей
Микросервис принимает платежи по HTTP, атомарно сохраняет платёж и outbox-событие в PostgreSQL, публикует событие в RabbitMQ и асинхронно отправляет результат на webhook.
Запуск
Требования: Docker с Compose plugin.
docker compose up --build
После запуска доступны:
- API и Swagger UI: http://localhost:8000/docs
- RabbitMQ Management: http://localhost:15672 (
guest/guest) - API key по умолчанию:
local-secret; для другого значения задайтеAPI_KEY
Миграции выполняются контейнером migrate до запуска API и consumer.
API
Создать платёж:
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:
{
"payment_id": "8f814d58-8a52-493f-aafb-98b383aa0325",
"status": "pending",
"created_at": "2026-09-16T08:00:00Z"
}
Повторный запрос с тем же Idempotency-Key вернёт тот же платёж и не создаст новое outbox-событие.
Получить платёж:
curl http://localhost:8000/api/v1/payments/8f814d58-8a52-493f-aafb-98b383aa0325 \
-H 'X-API-Key: local-secret'
Webhook получает 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.
Локальная разработка
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, затем выполните:
alembic upgrade head
uvicorn app.main:app --reload
faststream run app.consumer:app
Основные параметры эмуляции (PROCESSING_*), retry и polling outbox перечислены в .env.example.