Skip to content

bashkirian/payment-orchestrator

Repository files navigation

Payment Orchestrator

A payment orchestrator service that routes payouts through multiple providers (Stripe, mock providers) with automatic fallback support.

Architecture

                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚                        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)β”‚          β”‚         β”‚β”‚
                                    β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚         β”‚β”‚
                                    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

  • 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

Quick Start

# 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:9428

For 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 &

API Endpoints

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

Example Request

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"}

Configuration

Copy .env.example to .env and configure:

API_ENV=development
API_HTTP_ADDR=:8080
API_RATE_LIMIT_ENABLED=true

Observability

The 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)

Starting Observability Stack

# 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

Querying Logs in Grafana

  1. Go to Explore β†’ Select VictoriaLogs datasource
  2. Use LogsQL syntax:
service:fintech-api                    # API logs only
service:~"fintech-.*"                  # All project services
service:fintech-api _msg:~"error"      # API logs containing "error"

Querying Metrics

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

Load Testing

# Run k6 load tests
make loadtest-create      # Create payout load test
make loadtest-rate-limit  # Rate limiter stress test

Development

make build    # Build all services
make test     # Run tests
make lint     # Run linter

About

demo fintech project (provider orchestrator)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors