Skip to content

AryanDevCodes/Bank-Ledger-Payment-Engine

Repository files navigation

🏦 Double-Entry Payment Ledger System (Spring Boot)

Double-entry ledger + secure UPI payments — with JWT auth and strict sender ownership validation.

Java Spring Boot PostgreSQL Spring Security JWT OpenAPI Maven React Vite

Live Demo Swagger UI

templte (1)
  • What this project is
  • Why this system matters
  • Demo
  • Architecture Diagrams
  • Key Features
  • Failure Handling
  • Design Tradeoffs
  • Tech Stack
  • Quick Start (4–5 steps)
  • Security Highlights
  • Backend Updates (This Session)
  • API Documentation
  • Ledger Architecture
  • Frontend
  • Project Structure
  • Troubleshooting
  • Extra Docs
  • License
  • ✨ What this project is

    Double-Entry Payment Ledger System is a fintech-style Spring Boot backend that models money movement using an immutable double-entry ledger, plus UPI payment flows and a React dashboard.

    The security centerpiece is full sender ownership validation: even if someone knows an account number or UPI ID, they still cannot initiate payments unless the authenticated JWT user truly owns the sender account.


    Why this system matters

    In financial systems, storing balances directly can lead to inconsistencies due to concurrent updates, retries, and partial failures.

    This system avoids that by:

    • Deriving balances from transaction history (ledger entries)
    • Enforcing double-entry accounting (every movement is DEBIT + CREDIT)
    • Using immutable logs for auditability and easier reconciliation

    🎬 Demo

    Watch the demo


    🗺️ Architecture Diagrams

    Diagram assets live in demo/docs/.

    System architecture

    What this diagram is showing (end-to-end layers):

    1. Client (Postman / Frontend) calls either /transaction (transfer) or /upi/pay
    2. JWT Auth gates protected endpoints before business logic executes
    3. API Layer (Spring Boot Controllers) receives the request and delegates
    4. Service Layer orchestrates use-cases:
      • TransactionService for transfers (API: /transaction)
      • UpiService for UPI payments (API: /upi/pay)
      • LedgerService for posting ledger entries
    5. Core Engine contains the “correctness primitives”:
      • Idempotency Handler (safe retries)
      • Transaction Validator (amount/account invariants)
      • Ledger Processor (double-entry posting)
      • Debit = Credit enforcement (system invariant)
    6. Persistence Layer (PostgreSQL) stores:
      • Transactions table (status + snapshot)
      • Ledger table (append-only, the source of truth)
      • UPI tables (profiles + payment objects)
      • Account table (identity/metadata; balance correctness comes from ledger)
    7. The right-side bracket highlights the atomic transaction boundary: either the whole ledger+status update commits, or everything rolls back.

    System Architecture

    Database relations (ERD)

    What to notice in this ERD:

    • banks → account: a bank has many accounts
    • account → upi_profiles: an account can have UPI profiles via linked_account_id
    • upi_profiles → upi_payment_obj: UPI payment requests are tracked with a persisted object containing:
      • transaction_id
      • idempotency_key
    • upi_payment_obj → transactions: the payment object links to the canonical transaction record
    • transactions → ledger: ledger entries reference the transaction via reference_id (transaction_id)

    Why this matters:

    • Idempotency is stateful: the upi_payment_obj row is where duplicates/retries get deduplicated.
    • Audit is reconstructable: transactions give business context; ledger gives immutable financial truth.
    • Balance is not stored (as the diagram notes): balance is derived from ledger entries, preventing drift.

    Database Relation Diagram

    Ledger engine (double-entry)

    How the ledger processing works (the invariant):

    1. Input: from_account, to_account, amount
    2. Validation: amount > 0 and both accounts are valid
    3. Process:
      • Step 1: create DEBIT entry (account_id = sender)
      • Step 2: create CREDIT entry (account_id = receiver)
    4. Check (hard invariant): Sum(DEBIT) == Sum(CREDIT) — always
    5. Store: append to the ledger table (append-only)

    Result: balances can be computed from the ledger as credits minus debits, which is safer than trusting a mutable “balance” column.

    Ledger Engine Diagram

    Complete payment flow

    This is the detailed execution path the system follows:

    1. User initiates payment (UPI / transfer)
    2. API receives request and validates input
    3. Idempotency check (upi_payment_obj):
      • If the key already exists → return previous result
      • If it’s a failed/invalid previous attempt → surfaced via stored failure status
    4. Create transaction record (transactions table) with status = PROCESSING
    5. Validate accounts (from_account_id, to_account_id must exist)
    6. Ledger processing (CRITICAL - Double Entry):
      • Ledger Entry 1: sender DEBIT amount = X
      • Ledger Entry 2: receiver CREDIT amount = X
    7. Store in ledger table (append-only)
    8. Update transaction statusSUCCESS / FAILED
    9. Update upi_payment_obj with final status + reference transaction_id
    10. Return response

    Failure behavior shown in red:

    • On errors, the system rolls back the transaction, marks the business transaction as FAILED, and persists a failure_reason (in upi_payment_obj) so retries are safe and diagnosable.

    Complete Payment Flow

    Idempotency flow

    This is the decision tree used for safe retries:

    1. Request arrives with an idempotency_key
    2. Check if a matching record exists in upi_payment_obj
    3. If it exists → return the stored response (no double-debit)
    4. If it does not exist → execute the payment, then store key + result
    5. Return result to the client

    The diagram’s key point: idempotency prevents duplicate payments caused by retry issues or network failures.

    Idempotency Flow


    ✅ Key Features

    • 📒 Double-entry ledger (DEBIT + CREDIT) for every transaction
    • 🧮 Balances derived from ledger (reduces “stored balance drift”)
    • 🔐 JWT authentication + role-based access (Spring Security)
    • 🧾 Audit-friendly trail with immutable ledger references
    • 🪙 Secure UPI payments + UPI profiles (link UPI → account)
    • 🔁 Idempotent UPI payment execution (safe retries)
    • 🖥️ React dashboard (admin/manager/auditor/user views)

    Failure Handling

    • Duplicate requests handled via idempotency keys (safe retries)
    • Partial failures avoided using atomic database transactions
    • Invalid financial states prevented via strict debit/credit + validation rules

    Design Tradeoffs

    • Immutable ledger increases storage, but dramatically improves auditability
    • Balance derivation can be slower than stored balances, but ensures correctness
    • Strict validation reduces flexibility, but prevents silent data corruption

    🧰 Tech Stack

    Backend

    • Java 21
    • Spring Boot (Web, Data JPA, Validation, Security)
    • PostgreSQL
    • JWT (JJWT)
    • Swagger / OpenAPI (springdoc)

    Frontend

    • React + Vite
    • Tailwind CSS + shadcn/ui (Radix)

    🚀 Quick Start (4–5 steps)

    1) Prerequisites

    • Java 21
    • PostgreSQL running locally
    • Node.js 18+ (or Bun)

    2) Configure the backend

    Update the database configuration in src/main/resources/application.yml.

    Tip: use environment variables in real deployments. This repo’s default application.yml is aimed at local development.

    3) Start Spring Boot (port 8080)

    Windows:

    .\mvnw.cmd spring-boot:run

    macOS/Linux:

    ./mvnw spring-boot:run

    Backend base URL: http://localhost:8080

    4) Start the React frontend

    cd bank-frontend
    npm install

    Create bank-frontend/.env.local:

    VITE_API_BASE_URL=http://localhost:8080
    VITE_ENABLE_MOCKS=false

    Run:

    npm run dev

    Frontend URL: http://localhost:8081

    5) Open Swagger and try endpoints

    • Swagger UI: http://localhost:8080/swagger-ui.html
    • OpenAPI JSON: http://localhost:8080/v3/api-docs

    🛡️ Security Highlights (Must Read)

    ✅ Sender Ownership Enforcement – Implemented

    Critical protection against Insecure Direct Object References (IDOR):

    • Payment endpoints reject requests where the authenticated user does not own the source UPI/account.
    • Verified server-side in UpiResolver.resolveAndVerifyOwnership():
      • Matches JWT username against the UPI-linked Customer → User.username
      • Throws AccessDeniedException (→ HTTP 403) on mismatch
    • Prevents: guessing UPI IDs, using stolen account numbers, unauthorized debits

    🔐 Authentication & Authorization

    • Stateless JWT (Spring Security 6 + JJWT)
    • Protected endpoints require Authorization: Bearer <token>
    • @EnableMethodSecurity ready for future @PreAuthorize role/ownership checks

    🧩 Backend Updates (This Session, April 2026)

    The following backend hardening updates were implemented in this session:

    • Customer-only payment initiation

      • POST /transaction is restricted to ROLE_USER.
      • POST /upi/pay is restricted to ROLE_USER.
      • Result: admin/manager/auditor/customer-manager cannot initiate debits on behalf of customers.
    • Server-side sender ownership enforcement in transfer flow

      • TransactionServiceIMPL.makeTransaction() now validates authenticated principal ownership of sender account before ledger posting.
      • Result: even with a valid token, users cannot transfer from accounts they do not own.
    • Compliance/KYC update API added for account workflows

      • New endpoint: PATCH /account/{accNumber}/compliance
      • Supports update of accountStatus, kycStatus, and customerStatus.
      • Implemented through dedicated DTO + service method + mapper response fields.
    • Compliance fields exposed in account responses

      • AccountResponseDTO now includes customer compliance fields (kycStatus, customerStatus) for admin/compliance UIs.

    📚 API Documentation

    • Swagger UI (runtime): http://localhost:8080/swagger-ui.html
    • OpenAPI JSON (runtime): http://localhost:8080/v3/api-docs

    Bundled specs:


    🧾 Ledger Architecture (double-entry)

    This project implements traditional accounting:

    • Every transaction creates two ledger entries (DEBIT & CREDIT)
    • Ledger entries are treated as immutable financial records
    • Balances can be computed from ledger history
    Ledger example (transfer ₹5,000)
    1. Debit sender ₹5,000
    2. Credit receiver ₹5,000
    entry_type amount meaning
    DEBIT 5000 money leaves sender
    CREDIT 5000 money enters receiver

    🖥️ Frontend (React dashboard)

    Frontend lives in bank-frontend/.

    Includes:

    • Auth (token storage + protected routing)
    • Role-based dashboards
    • Banking flows (banks/customers/accounts/transactions)
    • UPI flows (profiles + payments)
    • Security/audit screens (audit logs, access logs, sessions)
    Frontend scripts
    cd bank-frontend
    npm run dev
    npm run build
    npm run preview
    npm run test

    🧯 Troubleshooting

    • Frontend shows empty data → confirm backend is running and VITE_API_BASE_URL points to it
    • Port conflicts → keep frontend on 8081 and backend on 8080 (or change Vite port)
    • Database connection fails → verify PostgreSQL is up and application.yml credentials match

    📎 Extra Docs (in this repo)


    About

    A comprehensive Spring Boot REST API implementing Double-Entry Ledger System for accurate financial transaction management.

    Resources

    License

    Stars

    Watchers

    Forks

    Packages

     
     
     

    Contributors