2026-09-16 14:31:13 +05:00

Асинхронный сервис процессинга платежей

Микросервис принимает платежи по HTTP, атомарно сохраняет платёж и outbox-событие в PostgreSQL, публикует событие в RabbitMQ и асинхронно отправляет результат на webhook.

Запуск

Требования: Docker с Compose plugin.

docker compose up --build

После запуска доступны:

Миграции выполняются контейнером 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.

S
Description
No description provided
Readme 412 KiB
Languages
Python 98.7%
Mako 0.9%
Dockerfile 0.4%