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!
