A fintech backend service that intelligently routes payments through multiple payment gateways based on configurable strategies.
Built with Spring Boot 3.4, this project demonstrates production-grade patterns including fault tolerance, caching, event-driven architecture, and observability.
| Category | Technology |
|---|---|
| Framework | Spring Boot 3.4, Java 21 |
| Database | PostgreSQL 16, Spring Data JPA |
| Cache | Redis 7 |
| Messaging | Apache Kafka |
| Security | Spring Security, JWT |
| Resilience | Resilience4j (Circuit Breaker, Retry) |
| Rate Limiting | Bucket4j |
| Documentation | SpringDoc OpenAPI 3.0 (Swagger UI) |
| Testing | JUnit 5, Testcontainers |
| Containerization | Docker, Docker Compose |
- JWT Authentication — Stateless token-based auth with BCrypt password encoding
- Multi-currency Wallets — Create and manage wallets in USD, EUR, GBP, KRW and more
- Smart Payment Routing — Pluggable routing strategies using the Strategy pattern
LOWEST_FEE— routes to the gateway with the lowest transaction feeHIGHEST_SUCCESS_RATE— routes to the gateway with the best success rate
- Idempotency — Safe payment retries using
X-Idempotency-Keyheader backed by Redis - Exchange Rate Integration — Real-time rates via ExchangeRate-API with Redis caching
- Circuit Breaker — Resilience4j protects against cascading failures from external APIs
- Rate Limiting — Bucket4j token bucket algorithm per user (10 req/min for payments)
- Event-driven — Kafka publishes payment events for downstream consumers
- Integration Tests — Full test suite using Testcontainers (real PostgreSQL, Redis, Kafka)
- API Documentation — Interactive Swagger UI with JWT auth support
- Docker Desktop
- Java 21
# Clone the repository
git clone https://github.com/dahyvuun/payment-router.git
cd payment-router
# Start infrastructure (PostgreSQL, Redis, Kafka)
docker compose up -d
# Run the application
./gradlew bootRunThe API will be available at http://localhost:8080
Open Swagger UI in your browser:
http://localhost:8080/swagger-ui.html
POST /api/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}POST /api/payments
Authorization: Bearer <token>
X-Idempotency-Key: payment-2026-001
Content-Type: application/json
{
"amount": 100.00,
"currency": "USD",
"strategy": "LOWEST_FEE"
}Response:
{
"transactionId": 1,
"amount": 100.00,
"currency": "USD",
"status": "COMPLETED",
"selectedPaymentMethod": "BANK_TRANSFER",
"feeRate": 0.5,
"successRate": 98.0,
"createdAt": "2026-03-01T12:00:00"
}POST /api/wallets # Create wallet
GET /api/wallets # Get all wallets
GET /api/wallets/{id} # Get wallet by ID
DELETE /api/wallets/{id} # Delete walletGET /api/exchange-rates/{baseCurrency} # Get rates (cached in Redis)
GET /api/exchange-rates/{from}/{to} # Get specific rateGET /api/v1/admin/circuit-breakers # All circuit breakers
GET /api/v1/admin/circuit-breakers/{name} # Specific circuit breaker┌─────────────────────────────────────────────────────┐
│ REST API Layer │
│ (Spring MVC + JWT + Rate Limiting) │
└────────────────────┬────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────┐
│ Service Layer │
│ PaymentService │ ExchangeRateService │
│ AuthService │ WalletService │
└──────┬────────────┴──────────┬──────────────────────┘
│ │
┌──────▼──────┐ ┌───────────▼──────────────────────┐
│ Routing │ │ Infrastructure │
│ Engine │ │ PostgreSQL │ Redis │ Kafka │
│ (Strategy) │ └──────────────────────────────────┘
└─────────────┘
Client → RateLimitInterceptor → PaymentController
→ PaymentService (check idempotency key in Redis)
→ RoutingEngine (select strategy)
→ CircuitBreaker (call payment gateway)
→ Save Transaction to PostgreSQL
→ Publish PaymentEvent to Kafka
→ Return PaymentResponse
The circuit breaker protects against cascading failures from external services.
| State | Behavior |
|---|---|
CLOSED |
Requests pass through normally |
OPEN |
Requests fail immediately, fallback is returned |
HALF_OPEN |
A few test requests are allowed through |
Configuration:
- Failure rate threshold: 50%
- Minimum calls before opening: 5
- Wait duration in OPEN state: 10s
Token bucket algorithm per authenticated user:
| Endpoint | Limit |
|---|---|
POST /api/payments |
10 requests/minute |
GET /api/exchange-rates/** |
30 requests/minute |
All other /api/** |
60 requests/minute |
# Run all tests (starts Testcontainers automatically)
./gradlew testIntegration tests use Testcontainers to spin up real PostgreSQL, Redis, and Kafka instances — no mocking, no H2 in-memory database.
src/
├── main/java/com/dahyvuun/payment_router/
│ ├── config/ # Redis, Kafka, OpenAPI, Rate Limit config
│ ├── controller/ # REST controllers
│ ├── domain/ # JPA entities (User, Wallet, Transaction, PaymentRoute)
│ ├── dto/ # Request/Response DTOs
│ ├── exception/ # Global exception handler
│ ├── repository/ # Spring Data JPA repositories
│ ├── routing/ # Strategy pattern (LowestFee, HighestSuccessRate)
│ ├── security/ # JWT filter, SecurityConfig
│ └── service/ # Business logic
└── test/
└── java/ # Testcontainers integration tests
MIT License — see LICENSE for details.