A production-grade, PhonePe-style UPI payments system built with Java 21, Spring Boot 3, PostgreSQL, and Apache Kafka. Demonstrates microservices architecture, event-driven design, JWT security, and concurrency control patterns.
Client
|
v
[API Gateway :8080] -- JWT validation at edge
|
|-- /api/auth/** --> [User Service :8081] <--> [PostgreSQL userdb]
|-- /api/users/** --> [User Service :8081]
|-- /api/wallets/** --> [Wallet Service :8082] <--> [PostgreSQL walletdb]
|-- /api/payments/** --> [Payment Service :8083] <--> [PostgreSQL paymentdb]
|
| Kafka: payment-events topic
v
[Notification Service :8084] <--> [PostgreSQL notificationdb]
| Service | Port | Responsibility |
|---|---|---|
| API Gateway | 8080 | JWT auth filter, routing |
| User Service | 8081 | Registration, login, UPI ID management |
| Wallet Service | 8082 | Balance management, transfers, transaction history |
| Payment Service | 8083 | Payment orchestration, Kafka event publishing |
| Notification Service | 8084 | Kafka consumer, push/SMS notifications |
@Version on the Wallet entity prevents double-spend in concurrent payment requests.
If two requests debit the same wallet simultaneously, one gets OptimisticLockException
and is rejected — no dirty reads, no lost updates.
Payment Service publishes domain events (PAYMENT_INITIATED, PAYMENT_SUCCESS, PAYMENT_FAILED)
to the payment-events topic. Notification Service consumes these asynchronously — decoupled,
resilient, and easy to extend (analytics, fraud detection can tap the same topic).
Every transfer carries a paymentId (UUID). Before debiting, wallet-service checks if
referenceId already exists in wallet_transactions. Duplicate Kafka retries or network
replays are safely rejected with 409 CONFLICT.
Each service owns its schema in a dedicated PostgreSQL instance. No shared tables, no cross-service JOINs — true data isolation.
The API Gateway validates tokens once at the edge. Downstream services trust the
Authorization header forwarded by the gateway (internal network trust model).
- Docker & Docker Compose
- Java 21+ (for local dev)
- Maven 3.9+
git clone https://github.com/23Anish/PayFlow-DigitalPaymentsPlatform.git
cd PayFlow-DigitalPaymentsPlatform
docker-compose up --buildServices start in order: PostgreSQL instances → Zookeeper → Kafka → App services
Visit http://localhost:8090 to inspect topics, consumer groups, and messages.
POST /api/auth/register
Content-Type: application/json
{
"phone": "9876543210",
"email": "anishthumula@gmail.com",
"password": "password123",
"fullName": "Anish Thumula",
"upiHandle": "23Anish"
}POST /api/auth/login
Content-Type: application/json
{ "phone": "9876543210", "password": "password123" }Returns accessToken — use as Authorization: Bearer <token> for all other requests.
POST /api/wallets
Authorization: Bearer <token>POST /api/wallets/add-money
Authorization: Bearer <token>
Content-Type: application/json
{ "amount": 1000.00, "source": "BANK_ACCOUNT" }POST /api/payments
Authorization: Bearer <token>
Content-Type: application/json
{
"receiverUpiId": "friend@payflow",
"amount": 500.00,
"note": "For dinner",
"type": "UPI_TRANSFER"
}GET /api/payments/history?page=0&size=20
Authorization: Bearer <token>- Register two users (Alice + Bob)
- Create wallets for both, add money to Alice
- POST /api/payments to send from Alice to Bob
- Open Kafka UI (http://localhost:8090) → topic
payment-events - See
PAYMENT_INITIATED, thenPAYMENT_SUCCESSevents - Notification-service logs show both Alice and Bob notified
- Java 21 — Records, pattern matching, text blocks
- Spring Boot 3.2 — Web, JPA, Security, Validation
- Spring Cloud Gateway — Reactive edge proxy
- PostgreSQL 15 — Per-service databases
- Apache Kafka — Async event streaming (Confluent Platform 7.5)
- JWT (JJWT 0.12) — Stateless authentication
- Lombok — Boilerplate reduction
- Docker + Docker Compose — Containerized local dev
payflow/
├── api-gateway/ # Spring Cloud Gateway + JWT filter
├── user-service/ # Auth, registration, UPI ID
├── wallet-service/ # Balance, transfers, optimistic locking
├── payment-service/ # Orchestration + Kafka producer
├── notification-service/ # Kafka consumer + notification storage
├── docker-compose.yml # Full local stack
└── pom.xml # Parent POM (multi-module)