Production-grade Digital Wallet & Payment System built with Microservices Architecture. Java 17 | Spring Boot 3 | Spring Security + JWT | Hibernate | MySQL | RabbitMQ | Docker | JUnit 5. Features: pessimistic locking, idempotent transactions, deadlock prevention, event-driven notifications, daily transfer limits.

๐ณ PayFlow
Digital Wallet & Payment System
A production-grade payment backend built with Microservices Architecture
Getting Started ยท API Docs ยท Architecture ยท Design Decisions
๐ฏ What is PayFlow?
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
| 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 |
๐ Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 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).
๐ Tech Stack
|
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) |
โจ Features
๐ Auth Service โ User Identity & Security
| 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 |๐ฐ Wallet Service โ Core Payment Engine
| 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) |๐ Notification Service โ Smart Alerts
| 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 |๐ก API Endpoints
๐ Authentication (auth-service โ port 8081)
| 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) |
๐ฐ Wallets (wallet-service โ port 8082)
| 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 |
๐ธ Transactions (wallet-service โ port 8082)
| 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 |
โญ Beneficiaries (wallet-service โ port 8082)
| 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 |
๐ Notifications (notification-service โ port 8083)
| 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 |
๐ Getting Started
Prerequisites
- โ Java 17+
- ๐ฆ Maven 3.8+
- ๐ณ Docker & Docker Compose
Quick Start (Docker) โ One Command Setup
git clone https://github.com/Shubh2-0/PayFlow.git
cd PayFlow
docker-compose up --build
That'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 | โ |
Manual Setup (without Docker)
- 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
๐ Usage Examples
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" }'
๐ Project Structure
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
๐ง Design Decisions
These are the engineering choices that make PayFlow production-grade, not just another CRUD project.
1. Why Pessimistic Locking for Balance Updates?
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.
2. Why Idempotency Keys?
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.
3. Why Ordered Locking for Deadlock Prevention?
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.
4. Why Event-Driven Notifications?
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.
5. Why Database Per Service?
Problem: Shared database = one service changes a table, other service breaks. Tight coupling defeats the purpose of microservices.
Solution: payflowauth, payflowwallet, payflow_notification โ three separate databases. Each service owns its data. No cross-database joins.
๐งช Testing
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 test
๐ CI/CD
GitHub 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
๐ License
This project is licensed under the MIT License.
Built with โค๏ธ by Shubham Bhati
โญ Star this repo if you found it useful!