A production-grade payment backend built with Microservices Architecture
Getting Started Β· API Docs Β· Architecture Β· Design Decisions
PayFlow is a digital wallet system β think of it like a simplified version of Paytm, PhonePe, or Google Pay's backend.
What can users do?
- π Sign up & log in securely (passwords are encrypted, sessions use JWT tokens)
- π° Create a wallet and add money to it
- πΈ Send money to other users instantly
- π View transaction history and download wallet statements
- β Save frequent contacts as beneficiaries for quick transfers
- π Get notified automatically when a transaction happens
What makes this a real engineering project (not a tutorial)?
| Challenge | How PayFlow Solves It |
|---|---|
| Two people send money from same wallet at once | Pessimistic Locking β database locks the wallet row, processes one at a time |
| Network glitch causes same payment request twice | Idempotency Keys β duplicate detected, money deducted only once |
| Two transfers between same wallets cause system freeze | Deadlock Prevention β wallets always locked in fixed order |
| Sending email slows down the payment | Event-Driven Architecture β payment completes instantly, email sent in background via RabbitMQ |
| One service goes down, others break | Microservices β each service runs independently with its own database |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Clients β
β (Mobile App / Web App) β
βββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β π Auth β β π° Wallet β β π Notification β
β Service β β Service β β Service β
β (:8081) β β (:8082) β β (:8083) β
β β β β β β
β β’ Register β β β’ Create Wallet β β β’ Email Alerts β
β β’ Login (JWT) β β β’ Add Money β β β’ Templates β
β β’ User Profile β β β’ Send Money β β β’ Retry Failed β
β β’ Role Access β β β’ Statement β β β’ Statistics β
β β β β’ Beneficiaries β β β
ββββββββββ¬βββββββββ βββββββββ¬βββ¬ββββββββ ββββββββββ²ββββββββββ
β β β β
β β β βββββββββββββ β
β β ββββΊβ π° βββββ
β β β RabbitMQ β
β β β (Messages) β
β β βββββββββββββ
ββββββββββΌβββββββββ ββββββββΌββββββββββ ββββββββββββββββββββ
β ποΈ payflow_ β β ποΈ payflow_ β β ποΈ payflow_ β
β auth (MySQL) β β wallet (MySQL) β β notification β
β β β β β (MySQL) β
βββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ
Each service has its own database β no shared tables, no tight coupling. Services communicate through REST APIs (synchronous) and RabbitMQ (asynchronous events).
|
Java 17 |
Spring Boot 3 |
MySQL 8 |
Docker |
RabbitMQ |
| Category | Technologies |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3.2, Spring Security, Spring Data JPA |
| Authentication | JWT (JSON Web Tokens), BCrypt password hashing |
| ORM & Database | Hibernate, MySQL 8 (one database per service) |
| Messaging | RabbitMQ (AMQP) for async event-driven communication |
| API Docs | Swagger / OpenAPI 3 (interactive API playground) |
| Testing | JUnit 5, Mockito, H2 in-memory DB (60+ test cases) |
| Containerization | Docker, Docker Compose (one-command startup) |
| CI/CD | GitHub Actions (automated build + test on every push) |
| Feature | Description |
|---|---|
| User Registration | Sign up with email, username, password (validated) |
| JWT Login | Login returns a signed token β no session storage needed |
| Password Security | BCrypt hashing β passwords never stored in plain text |
| Role-Based Access | ADMIN and USER roles with different permissions |
| Protected Routes | Profile endpoint only accessible with valid JWT |
| Feature | Description |
|---|---|
| Wallet Management | Create, view, freeze, and unfreeze wallets |
| Add Money | Top-up wallet balance (like adding money to Paytm) |
| Send Money | Transfer to another wallet with real-time balance update |
| Pessimistic Locking | Database-level locks prevent double-spending |
| Idempotent Transactions | Duplicate requests safely return same result |
| Deadlock Prevention | Wallets locked in ascending ID order |
| Daily Transfer Limits | Configurable limit (default: βΉ1,00,000/day) |
| Transaction Reversal | Reverse completed transactions (compensating transaction) |
| Beneficiary Management | Save, list, and remove frequent transfer recipients |
| Wallet Statements | Date-filtered history with total credits/debits summary |
| BigDecimal Precision | All money calculations use BigDecimal (no rounding errors) |
| Feature | Description |
|---|---|
| Event-Driven | Listens to RabbitMQ β gets triggered automatically on transactions |
| Email Notifications | Sends transaction receipts and alerts |
| Templates | Create reusable notification templates with {{variables}} |
| Retry Mechanism | Failed notifications auto-retry (configurable max retries) |
| Statistics Dashboard | Track sent, pending, and failed notification counts |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/register |
Register a new user |
POST |
/api/auth/login |
Login and receive JWT token |
GET |
/api/auth/profile |
Get current user profile (requires JWT) |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/wallets |
Create a new wallet |
GET |
/api/wallets/{walletId} |
Get wallet details |
GET |
/api/wallets/user/{userId} |
Get wallet by user ID |
PUT |
/api/wallets/{walletId}/freeze |
Freeze a wallet |
PUT |
/api/wallets/{walletId}/unfreeze |
Unfreeze a wallet |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/transactions/wallets/{walletId}/add-money |
Add money to wallet |
POST |
/api/transactions/wallets/{walletId}/send-money |
Send money to another wallet |
GET |
/api/transactions/{transactionId} |
Get transaction details |
GET |
/api/transactions/wallets/{walletId}/history |
Transaction history (paginated) |
GET |
/api/transactions/wallets/{walletId}/statement |
Wallet statement (date range) |
POST |
/api/transactions/{transactionId}/reverse |
Reverse a transaction |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/wallets/{walletId}/beneficiaries |
Add a beneficiary |
GET |
/api/wallets/{walletId}/beneficiaries |
List all beneficiaries |
DELETE |
/api/wallets/{walletId}/beneficiaries/{id} |
Remove a beneficiary |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/notifications |
List all notifications |
GET |
/api/notifications/{id} |
Get notification details |
POST |
/api/notifications/{id}/retry |
Retry a failed notification |
GET |
/api/notifications/stats |
Notification statistics |
POST |
/api/notification-templates |
Create notification template |
GET |
/api/notification-templates |
List all templates |
PUT |
/api/notification-templates/{id} |
Update a template |
DELETE |
/api/notification-templates/{id} |
Delete a template |
- β Java 17+
- π¦ Maven 3.8+
- π³ Docker & Docker Compose
git clone https://github.com/Shubh2-0/PayFlow.git
cd PayFlow
docker-compose up --buildThat's it! All services will be running:
| Service | URL | Swagger Docs |
|---|---|---|
| π Auth Service | http://localhost:8081 | Open |
| π° Wallet Service | http://localhost:8082 | Open |
| π Notification Service | http://localhost:8083 | Open |
| π° RabbitMQ Dashboard | http://localhost:15672 | β |
-
Start MySQL and create databases:
CREATE DATABASE payflow_auth; CREATE DATABASE payflow_wallet; CREATE DATABASE payflow_notification;
-
Start RabbitMQ on port 5672
-
Run each service:
cd auth-service && mvn spring-boot:run cd wallet-service && mvn spring-boot:run cd notification-service && mvn spring-boot:run
1. Register a user:
curl -X POST http://localhost:8081/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"email": "john@example.com",
"password": "secret123",
"fullName": "John Doe"
}'2. Login and get JWT token:
curl -X POST http://localhost:8081/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "john", "password": "secret123"}'3. Add βΉ5,000 to wallet:
curl -X POST http://localhost:8082/api/transactions/wallets/1/add-money \
-H "Content-Type: application/json" \
-d '{
"amount": 5000.00,
"description": "Initial top-up",
"idempotencyKey": "topup-001"
}'4. Send βΉ1,500 to another wallet:
curl -X POST http://localhost:8082/api/transactions/wallets/1/send-money \
-H "Content-Type: application/json" \
-d '{
"receiverWalletId": 2,
"amount": 1500.00,
"description": "Payment for services",
"idempotencyKey": "txn-001"
}'PayFlow/
β
βββ π³ docker-compose.yml # One-command setup for all services
βββ ποΈ init-db.sql # Auto-creates all 3 databases
βββ π¦ pom.xml # Parent Maven POM
β
βββ π auth-service/ # Authentication & User Management
β βββ src/main/java/
β βββ controller/ # REST API endpoints
β βββ dto/ # Request/Response objects
β βββ entity/ # User, Role (JPA entities)
β βββ exception/ # Error handling
β βββ repository/ # Database queries
β βββ security/ # JWT filter, config, token service
β βββ service/ # Business logic
β
βββ π° wallet-service/ # Core Payment Engine
β βββ src/main/java/
β βββ controller/ # Wallet, Transaction, Beneficiary APIs
β βββ dto/ # 10 DTOs for all operations
β βββ entity/ # Wallet, Transaction, Beneficiary
β βββ enums/ # WalletStatus, TransactionType/Status
β βββ event/ # RabbitMQ event publisher
β βββ exception/ # 6 domain-specific exceptions
β βββ repository/ # Queries with pessimistic locking
β βββ service/ # Transaction logic, daily limits
β
βββ π notification-service/ # Event-Driven Notifications
β βββ src/main/java/
β βββ config/ # RabbitMQ queue/exchange setup
β βββ controller/ # Notification & Template APIs
β βββ dto/ # TransactionEvent, responses
β βββ entity/ # Notification, Template
β βββ listener/ # RabbitMQ event consumer
β βββ repository/ # Database queries
β βββ service/ # Email & notification logic
β
βββ π .github/workflows/
βββ ci.yml # GitHub Actions CI pipeline
These are the engineering choices that make PayFlow production-grade, not just another CRUD project.
Problem: If two requests read wallet balance (βΉ1000) at the same time, both deduct βΉ500, and both write βΉ500 β the user loses βΉ500.
Solution: SELECT ... FOR UPDATE locks the wallet row. Second request waits until first completes. No money is lost.
Why not Optimistic Locking? In payments, retrying a failed transaction is risky and expensive. Better to wait 10ms for a lock than risk incorrect balances.
Problem: User clicks "Pay" β network timeout β user clicks again β money deducted twice.
Solution: Every transaction has a unique idempotencyKey. If the same key comes again, we return the existing result instead of creating a new transaction. This is how Stripe, Razorpay, and every production payment API works.
Problem: Transfer AβB locks wallet A, then tries to lock B. Simultaneously, transfer BβA locks wallet B, then tries to lock A. Both wait forever = deadlock.
Solution: Always lock the wallet with the smaller ID first. Both transfers lock A first, then B. No circular wait = no deadlock. Ever.
Problem: Sending email inside the payment transaction β if email server is slow (3 seconds), payment is slow. If email fails, does the payment rollback?
Solution: Payment completes β event published to RabbitMQ β notification service picks it up independently. Payment is fast, email failures don't affect payments, and we can add new consumers (SMS, push notifications) without changing wallet-service.
Problem: Shared database = one service changes a table, other service breaks. Tight coupling defeats the purpose of microservices.
Solution: payflow_auth, payflow_wallet, payflow_notification β three separate databases. Each service owns its data. No cross-database joins.
60+ test cases across 12 test classes in all three services:
| Type | What it Tests | Tools |
|---|---|---|
| Unit Tests | Service layer logic in isolation | JUnit 5, Mockito |
| Controller Tests | API endpoints, request validation, error responses | @WebMvcTest, MockMvc |
| Repository Tests | Database queries and JPA mappings | @DataJpaTest, H2 |
# Run all tests
cd auth-service && mvn test
cd wallet-service && mvn test
cd notification-service && mvn testGitHub Actions automatically runs on every push:
- β Sets up JDK 17 (Temurin) with Maven caching
- π¨ Builds all three services (
mvn clean verify) - π§ͺ Runs complete test suite
This project is licensed under the MIT License.
Built with β€οΈ by Shubham Bhati
β Star this repo if you found it useful!
