A payment orchestrator service that routes payouts through multiple providers (Stripe, mock providers) with automatic fallback support.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT β
β (Merchant System) β
βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
β HTTP POST /v1/payouts
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API (:8080) β
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Rate Limiterβ β HTTP Router β β Prometheus Metrics β β
β β (Redis) β β (Chi) β β (/metrics) β β
β βββββββββββββββ ββββββββ¬βββββββ βββββββββββββββββββββββ β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β
β gRPC (CreatePayout, GetPayout, CancelPayout)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ORCHESTRATOR (:50051) β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Routing Layer β β
β β ββββββββββββ ββββββββββββ βββββββββββββββββββββββ β β
β β β Priority β β Weighted β β SuccessBased β β β
β β β β β β β (track success %) β β β
β β ββββββββββββ ββββββββββββ βββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββ β
β β Fallback Engine β β
β β Provider A βββΊ (fail) βββΊ Provider B βββΊ (fail) β β
β β β β
β β Terminal errors (decline, fraud) stop immediately β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PROVIDERS β β
β β β β
β β βββββββββββ βββββββββββ βββββββββββββββ β β
β β β Stripe β βMock Cardβ β Crypto Sim β β β
β β β (card) β β (card) β β (crypto) β β β
β β βββββββββββ βββββββββββ βββββββββββββββ β β
β β β β β β β
β β ββββββββββββββββ΄ββββββββββββββββ β β
β β β β β
β β βΌ β β
β β External Payment Rails β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INFRASTRUCTURE β
β β
β βββββββββββββββ βββββββββββββββ β
β β PostgreSQL β β Redis β β
β β (payouts) β β(rate limit) β β
β βββββββββββββββ βββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β OBSERVABILITY ββ
β β ββ
β β βββββββββββββββββ βββββββββββββββββ βββββββββββββββ ββ
β β βVictoriaMetricsβ β VictoriaLogs β β Grafana β ββ
β β β :8428 β β :9428 β β :3000 β ββ
β β β (metrics) β β (logs) β β (dashboards)β ββ
β β βββββββββ²ββββββββ βββββββββ²ββββββββ ββββββββ²βββββββ ββ
β β β β β ββ
β β βββββββββ΄ββββββββ βββββββββ΄ββββββββ β ββ
β β β vmagent β β Vector β β ββ
β β β(metrics scrape)β β(log collector)β β ββ
β β βββββββββββββββββ βββββββββββββββββ β ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Multi-provider routing - Route payouts through Stripe, mock providers, or crypto simulator
- Automatic fallback - On retryable errors, automatically try the next provider
- Routing algorithms - Priority, Weighted, and Success-based routing
- Idempotent API - Safe request retries with idempotency keys
- Rate limiting - Redis-backed token bucket rate limiter
- Observability - VictoriaMetrics metrics/logs + Grafana dashboards
# Start all services in Docker (API + Postgres + Redis + Observability)
make services-up
# Run migrations (first time only)
make migrate-up
# Services are now running:
# - API: http://localhost:8080
# - Grafana: http://localhost:3000 (admin/admin)
# - VictoriaMetrics: http://localhost:8428
# - VictoriaLogs: http://localhost:9428For local development without Docker:
# Start just infrastructure
make up
# Run migrations
make migrate-up
# Build services
make build
# Run orchestrator and API locally
./bin/orchestrator start --config deploy/configs/orchestrator-local.yaml &
./bin/api &| Method | Endpoint | Description |
|---|---|---|
POST |
/v1/payouts |
Create a new payout |
GET |
/v1/payouts/{id} |
Get payout status |
POST |
/v1/payouts/{id}/cancel |
Cancel a pending payout |
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
Full API documentation: docs/openapi.yaml
curl -X POST http://localhost:8080/v1/payouts \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"amount": 1000, "currency": "USD", "rail": "card"}'Response:
{"payout_id": "550e8400-e29b-41d4-a716-446655440000"}Copy .env.example to .env and configure:
API_ENV=development
API_HTTP_ADDR=:8080
API_RATE_LIMIT_ENABLED=trueThe project uses the VictoriaMetrics ecosystem for observability:
| Component | Port | Purpose |
|---|---|---|
| VictoriaMetrics | 8428 | Metrics storage (Prometheus-compatible) |
| VictoriaLogs | 9428 | Log storage |
| vmagent | - | Metrics scraper (scrapes /metrics from services) |
| Vector | - | Log collector (reads Docker container logs) |
| Grafana | 3000 | Visualization (admin/admin) |
# Start all services in Docker (API + dependencies + observability)
make services-up
# Or start just observability stack
make observability-up
# Open Grafana
make grafana-open
# Open VictoriaMetrics UI
make vm-open
# Open VictoriaLogs UI
make vlogs-open- Go to Explore β Select VictoriaLogs datasource
- Use LogsQL syntax:
service:fintech-api # API logs only
service:~"fintech-.*" # All project services
service:fintech-api _msg:~"error" # API logs containing "error"
VictoriaMetrics is compatible with PromQL. In Grafana Explore:
rate(api_http_request_duration_seconds_count[5m]) # Request rate
histogram_quantile(0.95, rate(api_http_request_duration_seconds_bucket[5m])) # P95 latency
Pre-built dashboards:
- API Overview - HTTP latency, request rate, rate limiter stats
- Orchestrator Overview - gRPC latency, provider distribution
# Run k6 load tests
make loadtest-create # Create payout load test
make loadtest-rate-limit # Rate limiter stress testmake build # Build all services
make test # Run tests
make lint # Run linter