Full-stack banking platform built with Spring Boot and React, focused on transaction correctness, double-entry accounting, concurrency safety, idempotent payments, security, and auditability.
- What this project is
- Why this system matters
- Core Engineering Problems
- Engineering Architecture & Design
- Tech Stack
- Quick Start
- Roles & Access Control
- API Reference
- Authentication
- Bank Management
- Customer Management
- Account Management
- Transaction Processing
- UPI Payment System
- QR Code Generation
- Audit Logging
- Security & Session Management
- Notifications
- Debit Cards
- Debit Card Requests
- Credit Cards & Plans
- Loans & EMI
- Account Statements
- Webhooks
- Composite APIs & Card Events
- Ledger Architecture
- Security Design
- Database Schema
- Frontend
- Engineering Highlights
- Troubleshooting
- Extra Documentation
- License
Double Ledger is a full-stack banking system built with Java 21, Spring Boot, Spring Security, PostgreSQL, React, and TypeScript.
The system models financial operations using an immutable double-entry ledger rather than treating an account balance as the primary source of truth.
It includes:
- Double-entry financial ledger
- Transaction processing
- Idempotent UPI payments
- JWT authentication
- Refresh-token rotation
- Role-based access control
- Sender ownership validation
- Pessimistic database locking
- Deterministic account lock ordering
- KYC and compliance checks
- Audit logging
- Session management
- Security access logs
- Notifications
- Debit cards
- Credit cards and credit plans
- Loans and EMI schedules
- QR code generation
- Webhook subscriptions
- Server-Sent Events for card events
- CSV/PDF account statements
- React role-based dashboards
The project is intentionally designed around backend concerns such as correctness, consistency, concurrency, security, and traceability.
Financial systems cannot rely on simple balance updates such as:
balance = balance - amount
because concurrent requests, retries, partial failures, and duplicate requests can produce inconsistent results.
This system instead separates the concepts of:
Transaction
β
Ledger Entries
β
Derived Account Balance
For every successful transfer:
Alice β Bob βΉ5,000
Alice account
DEBIT βΉ5,000
Bob account
CREDIT βΉ5,000
The implementation also protects money movement using:
- Database transactions
- Pessimistic locking
- Deterministic lock ordering
- Idempotency keys
- Sender ownership validation
- KYC validation
- Append-oriented ledger entries
- Unique database constraints
- Audit trails
The project focuses on solving several problems commonly found in transactional backend systems.
Every transfer produces balanced debit and credit ledger entries.
UPI payments use persisted idempotency state so retrying the same request does not create another payment.
Account rows are protected using pessimistic locking.
When two accounts need to be locked, they are locked in deterministic ascending ID order.
Knowing an account number or UPI ID is not enough.
The authenticated user must actually own the sender account.
Financial and security-related operations are recorded through audit and access logs.
Access tokens are short-lived and refresh tokens are rotated and stored in hashed form.
The following diagrams explain the main architectural and backend design decisions of the system.
The backend separates API handling, business logic, persistence, security, and financial transaction processing.
Authentication is implemented using Spring Security and JWT. Authorization is enforced at the endpoint/service boundary using method-level security.
The financial operation is executed inside a transactional boundary so the transaction state and ledger entries remain consistent.
Concurrent transfers can attempt to update the same accounts at the same time. Both transactions therefore acquire locks in the same order.
If the same idempotency key is submitted again, the existing payment state is checked instead of blindly creating another financial operation.
This protects against duplicate submissions and retry scenarios.
Every successful money movement creates exactly two ledger entries.
Both entries share the same transaction reference.
This allows the system to maintain a complete financial trail and derive account balances from ledger activity.
The PostgreSQL schema models the banking domain across users, customers, accounts, transactions, payments, cards, loans, security, and auditing.
The application is structured so the major components can be deployed independently:
The frontend communicates with the backend through REST APIs, while the backend manages persistence and financial business logic.
| Technology | Version | Purpose |
|---|---|---|
| Java | 21 | Backend language |
| Spring Boot | 3.5.10 | Application framework |
| Spring Security | 6.x | Authentication & authorization |
| Spring Data JPA | Bundled | Persistence layer |
| Hibernate | 6.6.x | JPA provider |
| PostgreSQL | 15+ | Primary database |
| JJWT | 0.12.6 | JWT generation & validation |
| MapStruct | 1.6.3 | DTO mapping |
| SpringDoc OpenAPI | 2.7.0 | API documentation |
| Spring Actuator | Bundled | Health & metrics |
| Spring Mail | Bundled | Password reset emails |
| Spring Cache / Redis | Optional | Caching |
| OpenPDF | 1.3.30 | PDF statement generation |
| ZXing | 3.5.3 | QR code generation |
| Lombok | Bundled | Boilerplate reduction |
| Jakarta Validation | Bundled | Request validation |
| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| TypeScript | Type-safe frontend development |
| Vite | Build tool and development server |
| Tailwind CSS | Styling |
| shadcn/ui | UI components |
| Radix UI | Accessible primitives |
| Recharts | Charts and analytics |
| React Router v6 | Client-side routing |
| Lucide React | Icons |
Install:
- Java 21
- PostgreSQL 15+
- Node.js 18+
- npm
- Git
git clone <your-repository-url>
cd <your-repository>Create a PostgreSQL database and configure the credentials in:
src/main/resources/application.yml
Example:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/double_ledger
username: postgres
password: your_passwordUse your actual database configuration.
.\mvnw.cmd spring-boot:run./mvnw spring-boot:runBackend:
http://localhost:8080
cd bank-frontend
npm installCreate:
bank-frontend/.env.local
Add:
VITE_API_BASE_URL=http://localhost:8080
VITE_ENABLE_AUDIT=true
VITE_ENABLE_SECURITY=trueThen:
npm run devFrontend:
http://localhost:5173
Depending on the Vite configuration, the application may also use port 8081.
Swagger UI:
http://localhost:8080/swagger-ui.html
OpenAPI JSON:
http://localhost:8080/v3/api-docs
OpenAPI YAML:
swagger-documentation/openapi.yaml
The system supports five roles.
| Role | Description |
|---|---|
ROLE_ADMIN |
Full system access |
ROLE_MANAGER |
Banking, customer, account, transaction and UPI management |
ROLE_CUSTOMER_MANAGER |
Customer/account management and compliance operations |
ROLE_AUDITOR |
Read-only financial and audit access |
ROLE_USER |
Own accounts, transactions and UPI payments |
Authorization is enforced using Spring Security method-level security.
Example:
@PreAuthorize("hasRole('ADMIN')")or:
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")The application exposes REST APIs for authentication, banking, accounts, transactions, payments, cards, loans, auditing, security, and dashboard operations.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
Public | Authenticate and receive access + refresh tokens |
| POST | /api/auth/forgot-password |
Public | Request password reset |
| POST | /api/auth/reset-password |
Public | Reset password using reset token |
| GET | /api/auth/me |
Authenticated | Return current user profile and banking metrics |
| POST | /api/auth/change-password |
Authenticated | Change current password |
| POST | /api/auth/refresh |
Public | Rotate refresh token |
| POST | /api/auth/logout |
Authenticated | Logout and terminate active session |
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"tokenType": "Bearer",
"userId": 1,
"username": "admin",
"email": "admin@bank.com",
"fullName": "Admin User",
"roles": ["ROLE_ADMIN"],
"expiresAt": "2026-05-11T19:38:00",
"passwordChangeRequired": false
}{
"id": 1,
"username": "alice",
"email": "alice@bank.com",
"primaryRole": "ROLE_USER",
"customerId": "SBI_abc123",
"kycStatus": "ACTIVE",
"customerStatus": "ACTIVE",
"accountCount": 2,
"totalBalance": 45000.00,
"upiProfileCount": 1,
"transactionCount": 12
}Additional fields are returned based on the authenticated user's role.
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /bank |
ADMIN, MANAGER, AUDITOR | List banks |
| GET | /bank/{id} |
ADMIN, MANAGER, AUDITOR | Get bank |
| GET | /bank/upi/{upiId} |
ADMIN, MANAGER, AUDITOR, USER | Find bank by UPI ID |
| POST | /bank/create |
ADMIN | Create bank |
| PATCH | /bank/{id} |
ADMIN | Update bank |
| DELETE | /bank/{id} |
ADMIN | Delete bank |
Bank fields include:
bankName
branch
ifscCode
city
state
branchAddress
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /customer |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | List customers |
| GET | /customer/paginated |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Paginated customers |
| GET | /customer/me |
Authenticated | Current customer |
| GET | /customer/email/{email} |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Find customer |
| GET | /customer/search |
ADMIN, MANAGER, CUSTOMER_MANAGER | Search customers |
| GET | /customer/bank |
ADMIN, MANAGER, CUSTOMER_MANAGER | Customers by bank |
| PATCH | /customer/update |
ADMIN, CUSTOMER_MANAGER | Update customer |
| DELETE | /customer/delete |
ADMIN | Delete customer |
Customer data includes:
fullName
email
phoneNumber
address
age
kycStatus
customerStatus
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /account |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | List accounts |
| GET | /account/paginated |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Paginated accounts |
| GET | /account/my |
Authenticated | Own accounts |
| GET | /account/{id} |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Account details |
| GET | /account/name/{bankName} |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Accounts by bank |
| GET | /account/email/{email} |
ADMIN, MANAGER, CUSTOMER_MANAGER | Accounts by email |
| GET | /account/validate-receiver |
ADMIN, MANAGER, USER | Validate receiver |
| GET | /account/lookup-by-number |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR | Lookup account |
| GET | /account/{accountNumber}/balance |
ADMIN, MANAGER, AUDITOR, USER | Ledger-derived balance |
| POST | /account/{bankName} |
ADMIN, MANAGER | Create account |
| PATCH | /account/{accNumber} |
ADMIN, MANAGER | Update account |
| PATCH | /account/{accNumber}/compliance |
ADMIN, MANAGER, CUSTOMER_MANAGER | Update compliance |
| DELETE | /account/{accNumber} |
ADMIN | Delete account |
Account numbers are generated automatically.
Example:
ACC_SBI_xxxxx
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /transaction/all |
ADMIN, MANAGER, AUDITOR | All transactions |
| GET | /transaction/all/paginated |
ADMIN, MANAGER, AUDITOR | Paginated transactions |
| GET | /transaction |
ADMIN, MANAGER, AUDITOR, USER | Filter transactions |
| GET | /transaction/my |
Authenticated | Own transactions |
| GET | /transaction/customer/{customerId} |
ADMIN, MANAGER, AUDITOR, CUSTOMER_MANAGER | Customer transactions |
| GET | /transaction/accounts/{id}/balance |
ADMIN, MANAGER, AUDITOR, USER | Ledger-derived balance |
| POST | /transaction |
USER | Transfer money |
| POST | /transaction/{transactionId}/reverse |
ADMIN, MANAGER | Reverse transaction |
| GET | /transaction/{transactionId}/receipt |
ADMIN, MANAGER, AUDITOR, USER | Transaction receipt |
INITIATED
β
PROCESSING
β
COMPLETED
or:
INITIATED
β
PROCESSING
β
FAILED
{
"senderAccount": "ACC_SBI_xxxxx",
"receiverAccount": "ACC_HDFC_xxxxx",
"amount": 5000.00
}The sender must own the source account.
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| POST | /upi/register |
ADMIN, MANAGER, USER | Register UPI profile |
| GET | /upi |
ADMIN, MANAGER, AUDITOR, USER | List UPI profiles |
| GET | /upi/paginated |
ADMIN, MANAGER, AUDITOR, USER | Paginated profiles |
| GET | /upi/my |
Authenticated | Own UPI profiles |
| GET | /upi/{upiId} |
ADMIN, MANAGER, AUDITOR, USER | Get UPI profile |
| GET | /upi/account/{accountNumber} |
ADMIN, MANAGER, USER | Account UPI profiles |
| PATCH | /upi/{upiId}/status |
ADMIN, MANAGER, USER | Update status |
| PUT | /upi/{upiId}/toggle |
ADMIN, MANAGER, USER | Enable/disable |
| DELETE | /upi/{upiId} |
ADMIN | Soft delete |
| POST | /upi/pay |
USER | Execute UPI payment |
{
"fromUpi": "alice@sbi",
"toUpi": "bob@hdfc",
"amount": 1000.00,
"idempotencyKey": "unique-client-key-123"
}| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /qr/generate |
ADMIN, MANAGER, USER | Generate UPI payment QR |
| GET | /qr/account |
ADMIN, MANAGER, USER | Generate account QR |
QR codes are generated using ZXing.
UPI QR format:
upi://pay?pa=...&pn=...&am=...&cu=INR
The API returns:
image/png
Access is restricted to:
ROLE_ADMIN
ROLE_AUDITOR
| Method | Endpoint | Description |
|---|---|---|
| GET | /audit/logs |
Paginated audit log query |
| GET | /audit/logs/{id} |
Get individual audit entry |
Supported filters include:
startDate
endDate
action
userId
resource
page
size
Audit actions:
VIEW
CREATE
UPDATE
DELETE
Additional explicit security events include:
LOGIN
LOGOUT
PASSWORD_RESET_REQUEST
PASSWORD_RESET
PASSWORD_CHANGE
The AuditLoggingInterceptor automatically captures eligible HTTP activity.
Excluded paths include:
/audit
/security
/api/auth
/swagger-ui
/v3/api-docs
/actuator
All endpoints are restricted to:
ROLE_ADMIN
| Method | Endpoint | Description |
|---|---|---|
| GET | /security/sessions |
List active sessions |
| DELETE | /security/sessions/{sessionId} |
Terminate session |
| POST | /security/sessions/terminate-all |
Terminate sessions |
| GET | /security/access-logs |
Query access logs |
Session tracking includes:
sessionId
tokenId
userId
userName
ipAddress
userAgent
createdAt
lastActivity
expiresAt
active
Access events include:
LOGIN_SUCCESS
FAILED_LOGIN
LOGOUT
PASSWORD_CHANGE
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/notifications |
Paginated notifications |
| GET | /api/notifications/unread |
Unread notifications |
| GET | /api/notifications/unread/count |
Unread count |
| PUT | /api/notifications/{notificationId}/read |
Mark as read |
| PUT | /api/notifications/read-all |
Mark all as read |
Users can only access their own notifications.
The user ID is derived from the authenticated JWT.
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /api/debit-cards/account/{accountId} |
ADMIN, MANAGER, USER | Cards by account |
| GET | /api/debit-cards/account-number/{accountNumber} |
ADMIN, MANAGER, USER | Cards by account number |
| GET | /api/debit-cards/{cardId} |
ADMIN, MANAGER, USER | Card details |
| PUT | /api/debit-cards/{cardId}/toggle-contactless |
ADMIN, USER | Toggle contactless |
| PUT | /api/debit-cards/{cardId}/toggle-international |
ADMIN, USER | Toggle international |
| PUT | /api/debit-cards/{cardId}/toggle-otp |
ADMIN, USER | Toggle OTP |
| PUT | /api/debit-cards/{cardId}/limits |
ADMIN, USER | Update limits |
| PUT | /api/debit-cards/{cardId}/merchant-blocks |
ADMIN, USER | Merchant category controls |
| POST | /api/debit-cards/{cardId}/freeze |
ADMIN, USER | Freeze card |
| POST | /api/debit-cards/{cardId}/unfreeze |
ADMIN, USER | Unfreeze card |
| POST | /api/debit-cards/{cardId}/replace |
ADMIN, USER | Request replacement |
| PUT | /api/debit-cards/{cardId}/block |
ADMIN, USER | Permanently block |
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| POST | /api/debit-card-requests |
USER | Create card request |
| GET | /api/debit-card-requests/my |
USER | Own requests |
| GET | /api/debit-card-requests/pending |
ADMIN, MANAGER | Pending requests |
| GET | /api/debit-card-requests/approved |
ADMIN, MANAGER | Approved requests |
| GET | /api/debit-card-requests/issued |
ADMIN, MANAGER | Issued requests |
| POST | /api/debit-card-requests/{requestId}/approve |
ADMIN, MANAGER | Approve |
| POST | /api/debit-card-requests/{requestId}/reject |
ADMIN, MANAGER | Reject |
| POST | /api/debit-card-requests/{requestId}/issue |
ADMIN, MANAGER | Issue |
| POST | /api/debit-card-requests/{requestId}/dispatch |
ADMIN, MANAGER | Dispatch |
| POST | /api/debit-card-requests/{requestId}/deliver |
ADMIN, MANAGER | Deliver |
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /api/credit-cards/account/{accountId} |
ADMIN, MANAGER, USER | Cards by account |
| GET | /api/credit-cards/account-number/{accountNumber} |
ADMIN, MANAGER, USER | Cards by account number |
| GET | /api/credit-cards/{cardId} |
ADMIN, MANAGER, USER | Card details |
| PUT | /api/credit-cards/{cardId}/toggle-contactless |
ADMIN, USER | Toggle contactless |
| PUT | /api/credit-cards/{cardId}/toggle-international |
ADMIN, USER | Toggle international |
| PUT | /api/credit-cards/{cardId}/toggle-otp |
ADMIN, USER | Toggle OTP |
| PUT | /api/credit-cards/{cardId}/limits |
ADMIN, USER | Update limits |
| PUT | /api/credit-cards/{cardId}/merchant-blocks |
ADMIN, USER | Merchant controls |
| POST | /api/credit-cards/{cardId}/freeze |
ADMIN, USER | Freeze |
| POST | /api/credit-cards/{cardId}/unfreeze |
ADMIN, USER | Unfreeze |
| POST | /api/credit-cards/{cardId}/replace |
ADMIN, USER | Replacement |
| PUT | /api/credit-cards/{cardId}/block |
ADMIN, USER | Permanent block |
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /api/credit-plans |
ADMIN, MANAGER, USER | List plans |
| GET | /api/credit-plans/all |
ADMIN, MANAGER | Admin/manager plan view |
| POST | /api/credit-plans |
ADMIN, MANAGER | Create plan |
| PATCH | /api/credit-plans/{planId} |
ADMIN, MANAGER | Update plan |
| POST | /api/credit-plans/{planId}/assign/{cardId} |
ADMIN, MANAGER | Assign plan |
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /api/loans/customer/{customerId} |
ADMIN, MANAGER, USER | Customer loans |
| GET | /api/loans/account/{accountId} |
ADMIN, MANAGER, USER | Account loans |
| GET | /api/loans/{loanId} |
ADMIN, MANAGER, USER | Loan details |
| POST | /api/loans |
ADMIN, MANAGER | Create loan |
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| GET | /api/emis/loan/{loanId} |
ADMIN, MANAGER, USER | EMI schedule |
| GET | /api/emis/{emiId} |
ADMIN, MANAGER, USER | EMI details |
Endpoint:
GET /api/accounts/{accountNumber}/statement
Supports:
format=csv
format=pdf
from=
to=
Example:
GET /api/accounts/ACC_SBI_xxxxx/statement?format=pdf
Statements are generated from account transaction / ledger information.
| Method | Endpoint | Roles | Description |
|---|---|---|---|
| POST | /api/webhooks |
ADMIN, MANAGER | Register webhook |
| GET | /api/webhooks |
ADMIN, MANAGER, AUDITOR | List subscriptions |
| DELETE | /api/webhooks/{id} |
ADMIN, MANAGER | Delete subscription |
| PATCH | /api/webhooks/{id}/active |
ADMIN, MANAGER | Enable/disable |
Webhook subscriptions contain configuration such as:
url
secret
eventTypes
active
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/composite/my/overview |
Aggregated dashboard overview |
| POST | /api/composite/transfer |
Composite transfer workflow |
| GET | /api/composite/customers/{customerId}/banking-profile |
Banking profile |
| GET | /api/composite/banks/{bankId}/operations-summary |
Bank operations summary |
| POST | /api/composite/accounts/balance-check |
Multi-account balance check |
| GET | /api/composite/transactions/advanced |
Advanced transaction search |
| GET | /api/composite/search/global |
Global entity search |
| POST | /api/composite/accounts/{accountNumber}/freeze |
Freeze account |
| POST | /api/composite/customers/{customerId}/kyc-verify |
KYC verification |
GET /stream/events
The endpoint provides real-time card-related events using Server-Sent Events (SSE).
The ledger is the core financial component of the system.
Every successful transaction produces two entries:
Transaction
β
βββββββββββββββββ
βΌ βΌ
DEBIT CREDIT
β β
Sender Receiver
Example:
Transaction: TXN_001
Alice Account
DEBIT βΉ5,000
Bob Account
CREDIT βΉ5,000
Both entries reference the same transaction.
Balances are derived from ledger entries:
SELECT COALESCE(
SUM(
CASE
WHEN entry_type = 'CREDIT' THEN amount
WHEN entry_type = 'DEBIT' THEN -amount
END
),
0
)
FROM ledger
WHERE account_id = :accountId;Conceptually:
Balance = Total Credits - Total Debits
A unique constraint is used to prevent duplicate entries:
uk_ledger_entry
(reference_id, account_id, entry_type)
This provides an additional database-level consistency guarantee.
The application uses:
Spring Security
+
JWT
+
Refresh Token Rotation
Protected requests use:
Authorization: Bearer <access-token>Refresh tokens are:
- Stored as hashes
- Rotated after use
- Associated with token metadata
- Revoked when reused
- Managed as a token family
This prevents a previously used refresh token from being continuously reused.
Authentication alone is not considered sufficient for financial operations.
For:
POST /transaction
the service verifies:
JWT User
β
Customer
β
Owned Account
β
Sender Account
If the sender account does not belong to the authenticated user:
HTTP 403 Forbidden
The UPI payment flow applies the same ownership concept through UPI-to-account resolution.
Passwords are protected using BCrypt.
Password reset flow:
Forgot Password
β
Generate random reset token
β
Store token + expiry
β
Send reset link
β
Validate token
β
Update password
β
Clear token
Reset tokens have a limited lifetime.
Configurable development origins include:
http://localhost:8081
http://localhost:5173
http://localhost:3000
The transaction layer uses database-level locking.
Accounts involved in a transfer are locked before balance-sensitive operations.
Conceptually:
Transfer A β B
Lock A
Lock B
β
Validate balance
β
Create transaction
β
Create ledger entries
β
Commit
When two accounts are involved:
accountId 10
accountId 25
the system locks:
10 β 25
regardless of transfer direction.
Therefore:
A β B
B β A
both follow the same lock acquisition order.
The database contains the core banking and supporting infrastructure tables.
| Table | Description |
|---|---|
users |
Authentication users |
roles |
Application roles |
user_roles |
User-role relationship |
banks |
Bank master data |
customers |
Customer profiles |
accounts |
Customer bank accounts |
transactions |
Financial transactions |
ledger |
Double-entry financial ledger |
upi_profiles |
UPI identifiers |
upi_payment_obj |
UPI payment intents and idempotency |
audit_logs |
Application audit events |
access_logs |
Security access events |
user_sessions |
Active session tracking |
refresh_tokens |
Refresh token metadata |
notifications |
User notifications |
debit_cards |
Debit cards |
debit_card_requests |
Card issuance requests |
credit_cards |
Credit cards |
credit_plans |
Credit plan definitions |
loans |
Loan records |
emis |
EMI schedule |
webhook_subscriptions |
Outbound webhook registrations |
users
β
βββ user_roles ββ roles
β
βββ customers
β
βββ accounts
β
βββ transactions
β
βββ ledger
β
βββ upi_profiles
β
βββ debit_cards
β
βββ credit_cards
β β
β βββ credit_plans
β
βββ loans
β
βββ emis
Security-related data is maintained separately:
users
β
βββ refresh_tokens
βββ user_sessions
βββ access_logs
βββ audit_logs
βββ notifications
idx_ledger_account_id
idx_ledger_reference_id
idx_upi_payment_key
idx_upi_payment_status
idx_transactions_date
idx_audit_logs_timestamp
idx_audit_logs_action
idx_audit_logs_user_id
idx_audit_logs_resource
idx_access_logs_timestamp
idx_access_logs_event_type
idx_access_logs_user_id
idx_user_sessions_token_id
idx_user_sessions_active
idx_user_sessions_user_id
These indexes support common lookup, transaction, auditing, and session-management operations.
The frontend is located in:
bank-frontend/
Technology:
React 18
TypeScript
Vite
Tailwind CSS
shadcn/ui
Radix UI
Recharts
React Router
Lucide React
| Route | Component | Access |
|---|---|---|
/ |
Index.tsx |
Public / redirect |
/login |
LoginPage.tsx |
Public |
/register |
RegisterPage.tsx |
Public |
/forgot-password |
ForgotPasswordPage.tsx |
Public |
/set-password |
SetPasswordPage.tsx |
Public |
/dashboard |
Dashboard.tsx |
Authenticated |
/banks |
BanksPage.tsx |
ADMIN, MANAGER, AUDITOR |
/customers |
CustomersPage.tsx |
ADMIN, MANAGER, CUSTOMER_MANAGER, AUDITOR |
/accounts |
AccountsPage.tsx |
Authenticated |
/transactions |
TransactionsPage.tsx |
Authenticated |
/payments |
PaymentsPage.tsx |
Authenticated |
/upi |
UpiPage.tsx |
Authenticated |
/audit-logs |
AuditLogsPage.tsx |
ADMIN, AUDITOR |
/security |
SecurityPage.tsx |
ADMIN |
/profile |
ProfilePage.tsx |
Authenticated |
ProtectedRoute
β
PermissionGate
β
Role-specific UI
Pages and UI sections are conditionally displayed based on the authenticated user's role.
Different roles receive different dashboard views:
ADMIN
MANAGER
CUSTOMER_MANAGER
AUDITOR
USER
The dashboard includes:
Ctrl + K
for quick navigation through the application.
The dashboard uses Recharts for:
- Transaction volume
- Status distribution
- Bank distribution
- Other operational metrics
UPI and account QR codes can be generated by the backend and displayed directly inside the React dashboard.
Administrators can view:
- Active sessions
- Session metadata
- Access logs
- Login failures
- Password-change events
and terminate sessions when required.
Audit and security modules can be toggled using:
VITE_ENABLE_AUDIT=true
VITE_ENABLE_SECURITY=trueThe dashboard supports:
Light Mode
Dark Mode
The most important engineering decisions in this project are:
One transaction
β
Debit + Credit
β
Balanced ledger
Same request
β
Same idempotency key
β
Existing payment state
β
No duplicate financial operation
Concurrent requests
β
Pessimistic locks
β
Deterministic lock ordering
β
Safer account updates
JWT identity
β
Customer
β
Account ownership
β
Financial operation
API operation
β
Audit interceptor
β
Audit log
β
Traceable activity
Access Token
+
Refresh Token
β
Refresh
β
New Access Token
+
New Refresh Token
β
Previous Refresh Token revoked
The backend includes unit tests covering important business and security rules, including:
- KYC validation
- Authorization
- Transaction rules
- Ownership validation
- Security-related behavior
Run tests using:
./mvnw testWindows:
.\mvnw.cmd testVerify:
VITE_API_BASE_URL=http://localhost:8080and make sure the Spring Boot backend is running.
Default ports:
Backend β 8080
Frontend β 5173
Depending on configuration, the frontend may use:
8081
Check:
PostgreSQL is running
Database exists
Username is correct
Password is correct
application.yml is configured correctly
The access token may have expired.
Log in again or use the refresh-token flow.
Use a new unique idempotency key for a new payment attempt.
Do not reuse an existing key for a completely different payment.
The transaction layer uses deterministic account locking.
If investigating a locking problem, verify that account locking continues to use the intended ordered locking path.
Additional project documentation is available in the repository:
The frontend is deployed at:
https://ledgerlypay.vercel.app
Note: The live deployment demonstrates the frontend experience. Local development is recommended when exploring the complete backend, database, Swagger API, and transaction-processing workflow.
A simplified project structure:
.
βββ src/
β βββ main/
β β βββ java/
β β βββ resources/
β β
β βββ test/
β
βββ bank-frontend/
β
βββ public/
β βββ diagrams/
β βββ double-ledger-architecture.png
β βββ double-ledger-authentication-jwt-rbac.png
β βββ double-ledger-concurrency-locking.png
β βββ double-ledger-database-erd.png
β βββ double-ledger-deployment.png
β βββ double-ledger-idempotency-flow.png
β βββ double-ledger-ledger.png
β βββ double-ledger-transaction-flow.png
β
βββ swagger-documentation/
β βββ openapi.yaml
β βββ openapi.json
β
βββ PROJECT_REPORT.md
βββ pom.xml
βββ README.md
This project is licensed under the MIT License.
See LICENSE for details.