This project is a backend-only Payment Wallet System designed using real-world financial system principles.
It demonstrates how digital wallet systems work internally, focusing on correctness, consistency, concurrency handling, and transaction safety.
The project intentionally avoids frontend development to concentrate on backend engineering and system design.
Systems like Google Pay, PhonePe, Paytm, Amazon Pay, Uber Wallet, and banking platforms use similar backend architectures.
Whenever a user:
- Adds money
- Transfers money
- Receives cashback
- Gets a refund
A wallet backend system like this is involved.
Handling money is not simple CRUD.
Incorrect implementations can lead to:
- Money duplication
- Money loss
- Incorrect balances
- Inconsistent transaction history
- Race conditions under concurrent requests
This project solves these issues using proper system design techniques.
The main goals of this project are:
- Ensure no money is lost or created
- Maintain strong data consistency
- Handle concurrent transactions safely
- Provide a full audit trail
- Build a scalable backend architecture
The system is divided into the following layers:
-
API Layer
- Exposes REST APIs using Express.js
- Handles routing, validation, and authentication
-
Service Layer
- Contains core business logic
- Manages transactions and consistency
-
Database Layer (PostgreSQL)
- Provides ACID guarantees
- Uses row-level locking
- Stores immutable ledger records
-
Future Infrastructure (Optional)
- Redis for idempotency
- Queues for async processing
- Monitoring and metrics
Every financial transaction creates two ledger entries:
- One debit entry (negative amount)
- One credit entry (positive amount)
Example: User A sends ₹500 to User B
User A ledger entry: -500
User B ledger entry: +500
This ensures:
- Total money in the system remains constant
- Every transaction is auditable
- Errors can be traced easily
This approach is used by banks and fintech companies.
All operations related to a money transfer are executed inside a single database transaction.
If any step fails, the entire transaction is rolled back.
This guarantees all-or-nothing behavior.
To prevent race conditions:
- Wallet rows are locked using row-level locks
- Concurrent transfers from the same wallet are serialized safely
- Ledger entries are the source of truth
- Wallet balance is a cached value for faster reads
Balances can always be recomputed from ledger entries if required.
The money transfer process works as follows:
- Client sends a transfer request
- Backend validates sender and receiver
- Database transaction begins
- Sender wallet row is locked
- Balance is checked
- Sender wallet is debited
- Receiver wallet is credited
- Two ledger entries are inserted
- Transaction is committed
If any step fails, the transaction is rolled back.
The database consists of three core tables:
-
users
- Stores user identity information
-
wallets
- Stores wallet balance for fast access
-
ledger_entries
- Stores immutable transaction records
Ledger entries are never updated or deleted.
Backend: Node.js with Express
Database: PostgreSQL
Authentication: JWT
Database Access: Raw SQL
Testing: Postman
Version Control: Git
wallet-system/ ├── src/ │ ├── app.js │ ├── server.js │ ├── config/ │ │ └── db.js │ ├── routes/ │ │ ├── auth.routes.js │ │ └── wallet.routes.js │ ├── controllers/ │ │ ├── auth.controller.js │ │ └── wallet.controller.js │ ├── services/ │ │ └── wallet.service.js │ ├── models/ │ │ ├── user.model.js │ │ ├── wallet.model.js │ │ └── ledger.model.js │ ├── middlewares/ │ │ └── auth.middleware.js │ ├── utils/ │ │ └── dbTransaction.js │ └── sql/ │ └── schema.sql ├── README.md ├── package.json └── .gitignore
Authentication:
POST /auth/register
POST /auth/login
Wallet:
POST /wallet/create
POST /wallet/add-money
POST /wallet/transfer
GET /wallet/balance
GET /wallet/transactions
- Insufficient balance → request rejected
- Concurrent transfers → handled via row locks
- Partial failures → full rollback
- Duplicate requests → prevented via idempotency (future scope)
- Stateless backend services
- Horizontal scaling
- Database row-level locking
- Read replicas for transaction history
- Redis for idempotency and caching
- Async processing for notifications
- API testing using Postman
- Manual SQL verification
- Edge case testing:
- Concurrent transfers
- Insufficient balance
- Duplicate requests
- Idempotency keys using Redis
- Async notifications
- Rate limiting
- Monitoring and metrics
- Admin reconciliation tools
Note: This README is intentionally detailed so that any developer or AI assistant can understand the system design and help implement the project correctly without additional explanation.