A backend-focused project that simulates real payment workflows: creation, async processing, failure handling, retries, refunds, idempotency, and audit logging.
Stack: Java 17 · Spring Boot 3.2 · PostgreSQL · RabbitMQ · Redis · React (Vite) · Docker
Modular monolith — one Spring Boot application split into clear packages:
com.payments/
├── auth/ → JWT login, role-based access
├── payment/ → Core payment APIs + state machine
├── transaction/ → Transaction records per processing attempt
├── refund/ → Refund request and approval flow
├── audit/ → Append-only audit trail
├── messaging/ → RabbitMQ producers + consumers
├── notification/ → Notification stub (logs)
└── common/ → Exceptions, response wrapper, configs
| Concept | How |
|---|---|
| Idempotency | Unique idempotency_key on payments — duplicate requests return the existing payment |
| Optimistic Locking | @Version on Payment entity prevents concurrent state overwrites |
| State Machine | validateTransition() in PaymentService enforces legal status transitions |
| Async Processing | RabbitMQ consumer handles payment processing after API returns |
| Dead-Letter Queue | RabbitMQ DLQ via x-dead-letter-exchange catches unprocessable messages |
| Retry Mechanism | Scheduled job picks up FAILED payments and re-queues them (max 3 attempts) |
| Redis Caching | Payment lookups cached for 5 minutes, evicted on status change |
| Audit Log | Async append-only writes on every state transition |
PENDING → PROCESSING → COMPLETED
↘ FAILED → (retry) → PROCESSING
↘ FAILED (max retries exhausted)
COMPLETED → REFUNDED
PENDING → CANCELLED
- Java 17+
- Maven 3.9+
- Docker + Docker Compose
- Node.js 18+
docker-compose up -dThis starts: PostgreSQL on 5432, RabbitMQ on 5672 (management UI on 15672), Redis on 6379.
cd backend
mvn spring-boot:runFlyway will run migrations automatically. Server starts on http://localhost:8080
cd frontend
npm install
npm run devDashboard available at http://localhost:3000
POST /api/auth/register
POST /api/auth/login
POST /api/payments # Create payment (requires idempotencyKey)
GET /api/payments # List my payments (paginated, filterable by status)
GET /api/payments/{id} # Get payment detail
POST /api/payments/{id}/cancel # Cancel a PENDING payment
GET /api/payments/{id}/transactions # Transaction history for a payment
GET /api/transactions # All transactions (ADMIN)
POST /api/refunds # Request refund (payment must be COMPLETED)
GET /api/refunds # My refunds
GET /api/refunds/{id}
POST /api/refunds/{id}/approve # Approve refund (ADMIN)
POST /api/refunds/{id}/reject # Reject refund (ADMIN)
GET /api/audit/{entityType}/{id} # Audit trail for any entity
- Email:
admin@payments.local - Password:
Admin@1234
curl -X POST http://localhost:8080/api/payments \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"amount": 150.00,
"currency": "USD",
"description": "Order #1023 payment",
"idempotencyKey": "order-1023-attempt-1"
}'| Queue | Purpose |
|---|---|
payment.created.queue |
Triggers async processing |
payment.processed.queue |
Triggers notifications |
payment.dead-letter.queue |
Catches unprocessable messages |
Management UI: http://localhost:15672 (guest/guest)
Key settings in application.yml:
payment:
max-retry-attempts: 3 # How many times to retry a failed payment
retry-interval-ms: 300000 # Retry scheduler interval (5 min)
simulated-failure-rate: 0.3 # 30% of payments fail on first attempt (for testing)Set simulated-failure-rate: 0.0 to make all payments succeed immediately.