diff --git a/docs/API_SPECIFICATION.md b/docs/API_SPECIFICATION.md
index c10d984..5511613 100644
--- a/docs/API_SPECIFICATION.md
+++ b/docs/API_SPECIFICATION.md
@@ -1,13 +1,13 @@
# Payflow API — REST API Specification
-> **Document Metadata**
-> - **Title**: Payflow REST API Interface & Schema Specification
-> - **Author**: Payflow Engineering (shashakchandel@gmail.com)
-> - **Status**: Approved / Living Specification
-> - **Created Date**: 2026-08-01
-> - **Last Updated**: 2026-09-30
-> - **Authoritative Location**: [API_SPECIFICATION.md](API_SPECIFICATION.md)
-> - **Related Documents**: [System Architecture](ARCHITECTURE.md) | [Security Architecture](ARCHITECTURE.md#18-security-architecture-and-threat-model) | [Architecture Decisions (ADRs)](adr/README.md) | [Phased Roadmap](ROADMAP.md) | [Engineering Conventions](CONVENTIONS.md)
+!!! info "Document Metadata"
+ - **Title**: Payflow REST API Interface & Schema Specification
+ - **Author**: Payflow Engineering (shashakchandel@gmail.com)
+ - **Status**: Approved / Living Specification
+ - **Created Date**: 2026-08-01
+ - **Last Updated**: 2026-09-30
+ - **Authoritative Location**: [API_SPECIFICATION.md](API_SPECIFICATION.md)
+ - **Related Documents**: [System Architecture](ARCHITECTURE.md) | [Security Architecture](ARCHITECTURE.md#18-security-architecture-and-threat-model) | [Architecture Decisions (ADRs)](adr/README.md) | [Phased Roadmap](ROADMAP.md) | [Engineering Conventions](CONVENTIONS.md)
This document details the REST API endpoints, request/response models, input validation rules, and error handling behaviors for the Payflow API service.
@@ -27,6 +27,7 @@ This document details the REST API endpoints, request/response models, input val
## Interactive OpenAPI & Swagger Documentation
Payflow API auto-generates live, interactive OpenAPI 3.0 documentation using **Springdoc OpenAPI 3.1.1**:
+
- **Swagger UI (Interactive Playground)**: [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html)
- **OpenAPI 3.0 JSON Specification**: [http://localhost:8080/v3/api-docs](http://localhost:8080/v3/api-docs)
@@ -56,6 +57,7 @@ When an API error occurs (validation error, resource not found, conflict, etc.),
## Endpoints
### 0. User Authentication (Login)
+
Authenticates a registered user by UPI ID and issues a cryptographically signed JWT access token.
- **HTTP Method**: `POST`
@@ -98,6 +100,7 @@ Authenticates a registered user by UPI ID and issues a cryptographically signed
---
### 1. Register User
+
Registers a new client profile with an initial balance.
- **HTTP Method**: `POST`
@@ -152,6 +155,7 @@ Registers a new client profile with an initial balance.
---
### 2. List Users (Paginated, Admin Only)
+
Retrieves a paginated list of registered users. Requires administrative privileges (`ROLE_ADMIN`).
- **HTTP Method**: `GET`
@@ -199,6 +203,7 @@ Retrieves a paginated list of registered users. Requires administrative privileg
---
### 3. Retrieve User by Reference ID
+
Fetches a single user record by their unique UUID reference ID.
- **HTTP Method**: `GET`
@@ -232,6 +237,7 @@ Fetches a single user record by their unique UUID reference ID.
---
### 4. Retrieve User by UPI ID
+
Fetches a single user record by their unique UPI ID.
- **HTTP Method**: `GET`
@@ -265,6 +271,7 @@ Fetches a single user record by their unique UPI ID.
---
### 5. Retrieve User Balance Ledger History
+
Retrieves paginated double-entry balance ledger audit entries for a user by UUID reference ID.
- **HTTP Method**: `GET`
@@ -312,6 +319,7 @@ Retrieves paginated double-entry balance ledger audit entries for a user by UUID
---
### 6. Filter Users by Minimum Balance (Admin Only)
+
Retrieves a list of users whose balance exceeds the specified minimum threshold. Requires administrative privileges (`ROLE_ADMIN`).
- **HTTP Method**: `GET`
@@ -349,6 +357,7 @@ Retrieves a list of users whose balance exceeds the specified minimum threshold.
---
### 7. Create Money Transfer
+
Executes a peer-to-peer fund transfer request with guaranteed exactly-once idempotency and sender verification.
- **HTTP Method**: `POST`
@@ -420,6 +429,7 @@ Executes a peer-to-peer fund transfer request with guaranteed exactly-once idemp
---
### 8. Retrieve Transaction by Reference ID
+
Fetches details of a single transaction by its unique UUID reference ID.
- **HTTP Method**: `GET`
@@ -455,6 +465,7 @@ Fetches details of a single transaction by its unique UUID reference ID.
---
### 9. List Transactions for a User (Paginated)
+
Retrieves a paginated list of all transactions where the specified UPI ID is either the sender or receiver.
- **HTTP Method**: `GET`
@@ -505,6 +516,7 @@ Retrieves a paginated list of all transactions where the specified UPI ID is eit
---
### 10. Generate Spend Insights for Transaction
+
Analyzes transaction metadata and recipient UPI handle using Spring AI structured prompt engineering to classify the expenditure into personal finance categories and generate actionable budget insights.
- **HTTP Method**: `POST`
@@ -583,6 +595,7 @@ All error responses adhere to the standard RFC 7807 and RFC 9457 `application/pr
```
### Rate Limit Exceeded (`429 Too Many Requests`)
+
*Response includes header: `Retry-After: 1`*
```json
{
@@ -597,6 +610,7 @@ All error responses adhere to the standard RFC 7807 and RFC 9457 `application/pr
```
### Service Unavailable / Circuit Breaker Open (`503 Service Unavailable`)
+
```json
{
"type": "https://api.payflow.com/errors/service-unavailable",
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 8a21d50..cd7595a 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -1,13 +1,13 @@
# Payflow API — System Architecture & Design Document
-> **Document Metadata**
-> - **Title**: Payflow API Core System Architecture & Payment Engine Design
-> - **Author**: Payflow Engineering (shashakchandel@gmail.com)
-> - **Status**: Approved / Living Design Document
-> - **Created Date**: 2026-08-01
-> - **Last Updated**: 2026-09-30
-> - **Authoritative Location**: [ARCHITECTURE.md](ARCHITECTURE.md)
-> - **Related Documents**: [API Specification](API_SPECIFICATION.md) | [Security Policy](SECURITY.md) | [Architecture Decisions (ADRs)](adr/README.md) | [Phased Roadmap](ROADMAP.md) | [Engineering Conventions](CONVENTIONS.md)
+!!! info "Document Metadata"
+ - **Title**: Payflow API Core System Architecture & Payment Engine Design
+ - **Author**: Payflow Engineering (shashakchandel@gmail.com)
+ - **Status**: Approved / Living Design Document
+ - **Created Date**: 2026-08-01
+ - **Last Updated**: 2026-09-30
+ - **Authoritative Location**: [ARCHITECTURE.md](ARCHITECTURE.md)
+ - **Related Documents**: [API Specification](API_SPECIFICATION.md) | [Security Policy](SECURITY.md) | [Architecture Decisions (ADRs)](adr/README.md) | [Phased Roadmap](ROADMAP.md) | [Engineering Conventions](CONVENTIONS.md)
---
@@ -28,6 +28,7 @@ In financial payment systems, balance state corruption or double-spending causes
Furthermore, communicating transaction state to downstream services (such as notification push, fraud monitoring, or rewards engines) via direct HTTP/message calls within a database transaction causes the **dual-write problem**: if the DB commit succeeds but the network call fails, or vice versa, the system enters an inconsistent state.
Payflow solves these challenges through:
+
1. **Deterministic Lock Ordering**: Alphabetical pessimistic row locking on user accounts to prevent deadlocks and race conditions.
2. **Double-Entry Balance Ledger**: Immutable, append-only ledger entries preserving complete audit trails.
3. **Transactional Outbox Pattern**: Writing outbound events to the database in the same ACID transaction as the state change, eliminating network dual-write failures.
@@ -38,6 +39,7 @@ Payflow solves these challenges through:
## Goals & Non-Goals
### Goals
+
- **Zero Double-Spending**: Guarantee absolute atomicity and isolation for balance transfers under concurrent requests.
- **Financial Precision**: Enforce exact base-10 arithmetic (`BigDecimal`, `precision = 19, scale = 4`) with banker's rounding (`HALF_EVEN`).
- **Exactly-Once Mutation Semantics**: Enforce idempotency on payment endpoints via unique `Idempotency-Key` headers and SHA-256 payload verification.
@@ -46,6 +48,7 @@ Payflow solves these challenges through:
- **Non-Enumerable Entities**: Protect internal primary keys (`Long userId`) by exposing immutable UUID reference IDs (`referenceId`) across all external APIs.
### Non-Goals
+
- **Multi-Currency / Forex Conversion Engine**: Version 1 is scoped strictly to single-currency transactions in Indian Rupees (INR, symbol: ₹). Multi-currency conversion is explicitly out of scope for v1.
- **Physical ATM / Card Issuance Protocol**: Card network rails (Visa/Mastercard ISO 8583) are out of scope.
- **Direct Banking Clearing House Clearing**: Core banking settlement protocols (NPCI/ISO 20022) are mocked via clean domain adapter interfaces.
@@ -197,6 +200,7 @@ The system enforces strict execution profiles to maximize scalability and transi
To prevent double-spending under high request concurrency (e.g., a user submitting multiple transfers simultaneously), Payflow implements two tiers of locking:
### A. Database-Level Pessimistic Locking (Core Ledger)
+
For balance updates on a single database instance, the transaction service uses database-level pessimistic write locking:
```java
// In UserRepository.java
@@ -204,10 +208,13 @@ For balance updates on a single database instance, the transaction service uses
@Query("SELECT u FROM User u WHERE u.upiId = :upiId")
Optional findByUpiIdWithLock(@Param("upiId") String upiId);
```
+
*This executes a `SELECT ... FOR UPDATE` query in PostgreSQL, blocking other transactions from modifying these specific rows until the current transaction commits.*
+
* **Deadlock Avoidance**: Lock acquisition is performed in a deterministic order (e.g., sorting the user UPI IDs alphabetically). This prevents deadlock loops when two users perform mutual transfers at the same time.
### B. Distributed Locking (Cross-Service & Multi-Instance Coordination)
+
When payment requests enter a distributed, multi-pod Kubernetes cluster, duplicate submissions with identical `Idempotency-Key` headers can reach different application instances concurrently. To intercept race conditions at the API gateway layer before database connection pool allocation or transactional row locking, Payflow API implements a **Redis Distributed Lock (Redlock)** using **Redisson** (`RLock`):

@@ -256,30 +263,39 @@ pod2 -> redis: "15. unlock(\"payflow:lock:idemp:tx-123\")"
#### Key Design Characteristics:
+
1. **Profile-Conditional Architecture**:
- **`prod` Profile**: Activates `RedissonDistributedLockService` backed by `RedissonClient` using `SingleServerConfig` (connection pool: 20, idle: 5, timeout: 3000ms).
- **`!prod` Profiles (`local`, `test`, `prod-light`)**: Activates `NoOpDistributedLockService`, delivering zero-dependency instant local startup without requiring a Redis daemon.
2. **Bounded Wait Time (`LOCK_WAIT_TIME = 2s`)**:
Instead of immediately rejecting duplicate requests with a 409 Conflict, the second request waits up to 2 seconds for the first request to finish, enabling seamless cached response replay.
+
3. **Fail-Safe Automatic Lease (`LOCK_LEASE_TIME = 10s`)**:
Guarantees that if a pod crashes or is terminated (`SIGKILL`) during execution, the lock automatically expires in Redis after 10 seconds, preventing permanent distributed deadlocks.
+
4. **Thread-Ownership Verification (`isHeldByCurrentThread()`)**:
Before executing `unlock()`, the service confirms that the current thread owns the lock. This prevents `IllegalMonitorStateException` hazards if the lease expired during an unusually slow downstream call.
+
5. **Defensive Slice Test Fallback**:
In slice tests (`@WebMvcTest`) where service beans are not scanned, `IdempotencyFilter` falls back gracefully to `new NoOpDistributedLockService()`, eliminating test configuration boilerplate.
### C. Append-Only Double-Entry Balance Ledger
+
All financial balance operations execute double-entry bookkeeping by persisting two immutable `BalanceLedgerEntry` records (`DEBIT` for sender, `CREDIT` for receiver) within the same `@Transactional` database boundary as the money transfer:
+
- **Audit Integrity**: Every ledger entry records `amount`, `balanceBefore`, and `balanceAfter`, providing an immutable audit trail for every user balance state transition.
- **Balance Reconciliation**: `users.balance` functions as a high-performance denormalized field. The authoritative source of truth can be validated at any time by executing a reconciliation query over the `balance_ledger` table:
```sql
SELECT COALESCE(SUM(CASE WHEN entry_type = 'CREDIT' THEN amount ELSE -amount END), 0)
FROM balance_ledger WHERE user_id = :userId;
```
+
- **Immutability**: Once written, ledger rows are never updated or deleted. Reversals or refunds append new `CREDIT`/`DEBIT` ledger rows.
### D. Database Indexing Strategy
+
To ensure high database read throughput and statement generation speed, the following index constraints are established in Flyway migrations:
+
- `CREATE UNIQUE INDEX idx_users_upi_id ON users(upi_id);`
- `CREATE UNIQUE INDEX idx_users_reference_id ON users(reference_id);`
- `CREATE INDEX idx_tx_sender_created ON transactions(sender_upi_id, created_at DESC);`
@@ -288,7 +304,9 @@ To ensure high database read throughput and statement generation speed, the foll
- `CREATE INDEX idx_ledger_user_created ON balance_ledger(user_id, created_at DESC);`
### E. Flyway Versioned Database Migrations
+
All database DDL changes execute via Flyway versioned SQL scripts located in `src/main/resources/db/migration/`:
+
- `V1__create_users_table.sql`: Baseline schema for `users` table.
- `V2__create_transactions_table.sql`: Schema for `transactions` table with foreign keys to `users`.
- `V3__create_balance_ledger_table.sql`: Schema for `balance_ledger` double-entry audit table.
@@ -297,6 +315,7 @@ All database DDL changes execute via Flyway versioned SQL scripts located in `sr
Hibernate is configured to `spring.jpa.hibernate.ddl-auto=validate`, forcing JPA mapping validation against Flyway-managed schema while prohibiting unversioned database mutations in production.
### F. Spring Environment Profiles & Testcontainers Strategy
+
Configuration is structured cleanly across profile-specific YAML files:
| Profile | Datasource / DB Engine | Flyway | JPA DDL Auto | Primary Purpose |
@@ -335,10 +354,12 @@ Payflow enforces a multi-tier testing strategy following the standard Test Pyram
## 4. JPA N+1 Query Resolution
When loading transaction histories (e.g., querying users and their related list of transactions), Hibernate's default lazy loading triggers the **N+1 query problem**:
+
- 1 query is executed to fetch the page of $N$ users.
- $N$ queries are executed to fetch the transactions for each individual user.
### Resolution Strategy
+
To maintain a high-performance database connection pool, Payflow resolves this using `@EntityGraph` annotations in Spring Data JPA repositories:
```java
// In TransactionRepository.java
@@ -348,6 +369,7 @@ Optional findByReferenceId(UUID referenceId);
@EntityGraph(attributePaths = {"sender", "receiver"})
Page findBySenderUpiIdOrReceiverUpiId(String senderUpiId, String receiverUpiId, Pageable pageable);
```
+
This forces Spring Data JPA to generate a single SQL query with `LEFT OUTER JOIN`s, retrieving the transaction along with its associated `sender` and `receiver` `User` entities in a single database round-trip.
---
@@ -363,6 +385,7 @@ To support continuous releases and backward compatibility for mobile and third-p
@RequestMapping("/api/v1/transactions")
public class TransactionController { ... }
```
+
- **Deprecation Policy**: Old version routes (e.g., `/api/v1`) remain active until deprecated by major versions (`/api/v2`), routing traffic transparently via the API Gateway.
---
@@ -372,6 +395,7 @@ To support continuous releases and backward compatibility for mobile and third-p
To guarantee "exactly-once" execution on payment mutations, Payflow enforces a durable, database-backed idempotency filter that requires clients to pass a unique `Idempotency-Key` header with write requests.
### Idempotency Schema
+
```sql
CREATE TABLE idempotency_registry (
idempotency_key VARCHAR(255) PRIMARY KEY,
@@ -430,10 +454,12 @@ filter -> client: "14. 400 Bad Request (Key Reuse with Different Payload)"
### In-Flight Lease Recovery & Distributed Race Handling
+
- **Crashed Worker Node Recovery**: If an application node crashes mid-flight while a transaction is in status `PROCESSING`, the in-flight lease automatically expires after 2 minutes (`IN_FLIGHT_TIMEOUT`), allowing client retries to re-acquire the lock without waiting for the 24-hour TTL purge.
- **Concurrent Insert Collision Safety**: If two concurrent requests with the same key arrive simultaneously and both pass initial existence checks, database unique constraint enforcement triggers a `DataIntegrityViolationException`, which the filter catches and gracefully translates to `409 Conflict`.
### Background TTL Purge Service
+
A scheduled job (`IdempotencyCleanupService`) executes periodically (`payflow.idempotency.cleanup-cron`) to purge expired records older than the configured TTL (`payflow.idempotency.ttl-hours`, default 24 hours). The `idx_idemp_created` B-Tree index ensures constant-time range scan performance during deletion.
---
@@ -443,6 +469,7 @@ A scheduled job (`IdempotencyCleanupService`) executes periodically (`payflow.id
To achieve reliable event-driven messaging, Payflow uses **Spring Modulith's Event Publication Registry** (`spring-modulith-starter-jpa`) to atomically persist domain events within the same database transaction as the business state change. This eliminates the dual-write problem without requiring custom outbox polling infrastructure.
### Event Publication Schema
+
```sql
CREATE TABLE IF NOT EXISTS event_publication (
id UUID NOT NULL,
@@ -505,6 +532,7 @@ listener -> db: "13. UPDATE event_publication SET completion_date = NOW() WHERE
### Module Boundary Verification
+
Spring Modulith continuously validates domain encapsulation and architectural coupling rules across packages via `ModulithStructureTest`:
```java
ApplicationModules modules = ApplicationModules.of(PayflowApiApplication.class);
@@ -522,6 +550,7 @@ modules.verify();
### Kafka Event Externalization Topology (Phase 9A)
When running under the `prod` profile (or the `kafka` test/dev profile via `application-kafka.yml`), Spring Modulith automatically binds the transactional outbox registry to Apache Kafka:
+
1. **Topic & Replication**: Created via `NewTopic` using property `${payflow.kafka.transfers-topic:payflow.transfers}` with 3 partitions. Replication factor is configurable via `${payflow.kafka.topic-replicas}` (defaults to `3` in `application-prod.yml` for multi-broker cluster durability, and `1` in `application-kafka.yml` for single-broker dev/test containers).
2. **Dynamic Programmatic Routing**: Configured via `EventExternalizationConfiguration`, dynamically binding `@Externalized` `TransferCompletedEvent` to the exact same topic property, ensuring topic creation and event externalization never diverge.
3. **Partitioning Key**: `senderUpi` (e.g. `aarav@payflow`), ensuring all transfer events originating from the same sender arrive strictly in order at the same partition for consumer groups.
@@ -696,6 +725,7 @@ System stability under load is enforced using **Resilience4j** configurations:
* **Retries & Backoff**: Outbound requests (e.g. to third-party banking processors) are wrapped in Spring Retry & Resilience4j Circuit Breaker policies using **exponential backoff with random jitter** to prevent thundering herd requests on recovering downstream hosts.
* **Virtual Threads Integration (Project Loom — Phase 10B)**:
Java 25 virtual threads are enabled globally (`spring.threads.virtual.enabled=true`). Since virtual threads do not block OS kernel threads during blocking I/O (database transactions, external HTTP REST calls, Redis operations), the application services thousands of concurrent requests with minimal memory overhead (~1 KB per virtual thread vs ~1 MB per platform thread). To prevent relational database connection starvation, HikariCP's maximum pool size is explicitly bounded (`maximum-pool-size=20`, `idle-timeout=300000ms`, `max-lifetime=1800000ms` in `prod`, and `maximum-pool-size=5` in `prod-light`).
+
* **Gen-AI Fault Tolerance & Heuristic Fallback (Phase 10A)**:
The Gen-AI Spend Insights engine (`LlmInsightClient`) is protected by a dedicated Resilience4j Circuit Breaker (`aiCircuitBreaker`) and time limiter. When downstream LLM inference encounters timeouts, rate limits, or connectivity failures, the circuit breaker instantly diverts execution to `ruleBasedFallback(Transaction, Throwable)`. This heuristic fallback evaluates recipient handle metadata across 8 expenditure categories, ensuring 100% endpoint uptime and zero disruption to the payment system.
@@ -706,6 +736,7 @@ System stability under load is enforced using **Resilience4j** configurations:
Payflow API adopts an immutable, production-hardened cloud-native deployment model:
### A. Multi-Stage Containerization & Hardening (Phase 11A)
+
- **Multi-Stage `Dockerfile`**:
- **Stage 1 (`builder`)**: Utilizes `eclipse-temurin:25-jdk` to resolve Maven dependencies and compile the executable Spring Boot fat JAR, leveraging BuildKit cache mounts (`--mount=type=cache,target=/root/.m2`) to speed up subsequent builds.
- **Stage 2 (`runtime`)**: Employs minimal `eclipse-temurin:25-jre` to construct a lightweight (~250 MB) production runtime image, eliminating compilers, build dependencies, and package managers from the production artifact.
@@ -830,7 +861,9 @@ cluster.workloads.pod2 -> backing.kafka
- `pdb.yaml`: PodDisruptionBudget guarantees at least 1 pod remains continuously available (`minAvailable: 1`) during cluster upgrades or node draining.
### C. Coordinated Graceful Shutdown Lifecycle
+
In distributed Kubernetes environments, terminating pods requires coordinated draining to prevent dropping active financial transactions:
+
1. **Endpoint Deregistration**: Kubernetes removes the terminating pod from service endpoint slices asynchronously.
2. **Pre-Stop Hook**: The pod spec executes a `lifecycle.preStop` hook running `sleep 10`. This delay allows kube-proxy and Ingress controllers to propagate endpoint removal before the container receives a termination signal.
3. **Graceful Application Draining**:
@@ -838,6 +871,7 @@ In distributed Kubernetes environments, terminating pods requires coordinated dr
- `spring.lifecycle.timeout-per-shutdown-phase: 30s`: Spring allows active HTTP transactions, database queries, and Kafka event externalizations up to 30 seconds to complete cleanly before JVM termination.
### D. Automated CI/CD Quality Gates & Bytecode Analysis (Phase 11C)
+
- **SpotBugs Static Analysis (`spotbugs-maven-plugin:4.10.4.1`)**: Executes static bytecode verification during the Maven `verify` phase with `effort: Max` and `threshold: Medium`. Filtered by `spotbugs-exclude.xml` for generated MapStruct mappers and compatibility stubs; achieved **0 bugs and 0 errors**.
- **JaCoCo Coverage Enforcement (`jacoco-maven-plugin:0.8.15`)**: Automatically enforces strict bundle-level code coverage limits:
- **Line Coverage**: Minimum 80% (Achieved: **90%** across 191 tests).
@@ -852,12 +886,14 @@ In distributed Kubernetes environments, terminating pods requires coordinated dr
Payflow implements the **Three Pillars of Observability** — Metrics, Tracing, and Logging — using industry-standard open-source tooling and Spring Boot 4 / Micrometer Observation architecture:
### A. Distributed Tracing (OpenTelemetry & Micrometer Observation)
+
- **Micrometer Tracing** with the **OpenTelemetry bridge** (`micrometer-tracing-bridge-otel` and `opentelemetry-exporter-otlp`) automatically generates and propagates W3C-standard `traceparent` headers (`traceId`, `spanId`) across HTTP controllers, database queries, and async thread pools.
- **Method-Level Spans**: Domain service operations like `TransactionService.sendMoney()` are instrumented with `@Observed(name = "payflow.transfers.send", contextualName = "send-money-transfer")` via `ObservedAspect`, creating dedicated trace spans and execution timers automatically.
- **Header Correlation**: The client-provided or auto-generated `X-Request-Id` (via `RequestIdFilter`) coexists with W3C `traceparent` context, returning both headers in HTTP responses.
- Compatible with **Grafana Tempo**, **Jaeger**, or any OTLP-compatible tracing backend.
### B. Business & System Metrics (Prometheus + Grafana)
+
- **Business Metrics (`MetricsConfig.java`)**:
- `payflow.transfers.total`: Counter tagged by transfer status (`COMPLETED`, `FAILED`, `INSUFFICIENT_BALANCE`, `FORBIDDEN`).
- `payflow.transfers.amount`: Distribution summary with SLA percentiles (p50, p95, p99) tracking transfer monetary distribution in Indian Rupees (INR, ₹).
@@ -866,6 +902,7 @@ Payflow implements the **Three Pillars of Observability** — Metrics, Tracing,
- **Prometheus Scraping**: Prometheus scrapes `/actuator/prometheus` without authentication barriers (`SecurityConfig` permits actuator metric endpoints).
### C. Structured Logging & MDC Enrichment (`RequestLoggingFilter.java`)
+
- **MDC Correlation**: `RequestIdFilter` and `RequestLoggingFilter` populate MDC keys across every request:
- `requestId`: Unique request identifier (`X-Request-Id`).
- `traceId` and `spanId`: OpenTelemetry trace context.
@@ -884,6 +921,7 @@ Payflow implements the **Three Pillars of Observability** — Metrics, Tracing,
Payflow API incorporates **Resilience4j** to safeguard system stability under peak traffic spikes, noisy neighbor conditions, and downstream service degradations.
### A. Resilience Policy Configuration Matrix
+
| Policy | Component | Target | Key Configuration Parameters | Failure Behavior |
| :--- | :--- | :--- | :--- | :--- |
| **Rate Limiter** | `transferLimiter` | `TransactionService.sendMoney()` | `limitForPeriod: 10`, `limitRefreshPeriod: 1s`, `timeoutDuration: 0s` | Returns HTTP `429 Too Many Requests` with `Retry-After: 1` |
@@ -892,6 +930,7 @@ Payflow API incorporates **Resilience4j** to safeguard system stability under pe
| **Transaction Timeout** | Database Engine | Balance debit/credit & ledger write | `timeout = 5s` (Spring `@Transactional`) | Aborts deadlock-prone or hung SQL transactions |
### B. Dynamic Per-User Rate Limiting Pattern
+
Unlike traditional global rate limiting, which allows a single abusive script to exhaust server throughput for all customers, Payflow enforces **per-authenticated-user partition isolation**:

@@ -937,12 +976,15 @@ service -> client: "[Exceeded > 10 req/s] 429 Too Many Requests (Retry-After: 1)
- **Memory Eviction Safeguard**: Dynamically generated rate limiter instances are tracked by access timestamp. `UserRateLimiterService.evictInactiveLimiters()` executes periodically, purging partitions inactive for >15 minutes from the registry to prevent unbounded memory growth.
### C. Downstream Circuit Breaking & Selective Exception Filtering
+
The external UPI verification gateway is guarded by a Resilience4j Circuit Breaker:
+
- **Count-Based Sliding Window**: Measures the outcome of the last 10 calls. Once 5 calls are completed, if >=50% fail with network exceptions (`RestClientException`, `IOException`), the circuit trips to `OPEN`.
- **Selective Exception Filtering**: Client data errors (such as `InvalidUpiException` mapped to HTTP 422) are domain validation rejections and explicitly excluded via `ignoreExceptions`, ensuring bad user inputs do not falsely trip downstream infrastructure breakers.
- **Fail-Fast & Recovery**: In `OPEN` state, downstream calls fail fast without network traversal. After 5 seconds, the circuit transitions to `HALF_OPEN`, testing 3 probe requests to automatically heal back to `CLOSED`.
### D. Resilience Metrics & Actuator Integration
+
- **Prometheus Gauges & Counters**:
- `resilience4j.circuitbreaker.state`: Current state (`closed`, `open`, `half_open`).
- `resilience4j.circuitbreaker.calls`: Total calls tagged by `kind` (`successful`, `failed`, `ignored`).
@@ -957,6 +999,7 @@ The external UPI verification gateway is guarded by a Resilience4j Circuit Break
To achieve sub-10ms response latencies on repetitive user profile lookups and balance ledger inspection while shielding PostgreSQL from read connection exhaustion, Payflow API implements a **Profile-Conditional Cache-Aside Architecture** using Spring Cache, Redis (Lettuce), and Caffeine.
### A. Cache-Aside Workflow & Sequence Diagram
+
In the Cache-Aside pattern, the application service inspects the cache before accessing the underlying database. On write operations, the cache entries are evicted rather than updated inline, eliminating race conditions between concurrent database writes and cache updates.

@@ -1001,6 +1044,7 @@ service -> client: "Return Success Response"
### B. Profile-Conditional Cache Strategy
+
The cache infrastructure adapts transparently across environments via `CacheConfig.java`:
| Environment / Profile | Cache Provider | Implementation Class | Characteristics & Configuration |
@@ -1009,6 +1053,7 @@ The cache infrastructure adapts transparently across environments via `CacheConf
| **`!prod` (`local`, `test`, `prod-light`)** | **Caffeine** | `CaffeineCacheManager` | High-performance in-memory cache requiring zero external network dependencies or Docker containers. Spec: `maximumSize=1000,expireAfterWrite=600s`. |
### C. Cache Names, Key Schemes & TTL Matrix
+
Cache policies are externalized in `application.yml` and tuned based on data volatility and freshness requirements:
| Cache Name | Cached Methods | Key Scheme | TTL | Eviction Triggers |
@@ -1017,20 +1062,26 @@ Cache policies are externalized in `application.yml` and tuned based on data vol
| `user_ledgers` | `getUserLedger()` | `#userReferenceId + '_' + #pageable.pageNumber` | **1 minute** (`60s`) | Targeted eviction on `sendMoney()` (sender & receiver keys only) |
### D. Serialization & Entity Hardening
+
1. **Modern JSON Serialization (`RedisSerializer.json()`)**:
Spring Data Redis 3.x / Spring Framework 7 deprecates `GenericJackson2JsonRedisSerializer`. Payflow leverages `RedisSerializer.json()` for non-intrusive, polymorphic JSON value encoding. This avoids native Java binary serialization vulnerabilities while ensuring human-readable inspectability in Redis CLI (`redis-cli`).
+
2. **Null-Safety Handling**:
Read operations use `@Cacheable(..., unless = "#result == null")`. When a service method returns `Optional`, Spring's caching aspect unwraps the `Optional` before SpEL condition evaluation. The `#result == null` guard ensures empty optionals are not cached, preventing false positive hits for non-existent users.
+
3. **Circular Reference & Proxy Isolation**:
Domain entities are hardened for JSON serialization:
+
- `User` and `BalanceLedgerEntry` implement `java.io.Serializable` (`serialVersionUID = 1L`).
- `BalanceLedgerEntry` is decorated with `@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})` to prevent Jackson serialization failures on uninitialized Hibernate bytecode proxies.
- Lazy JPA relationships (`user`, `transaction`) in `BalanceLedgerEntry` are annotated with `@JsonIgnore` to eliminate circular reference graphs during JSON marshaling.
### E. Targeted Cache Invalidation Mechanics (Phase 9B)
+
Prior to Phase 9B, `TransactionService.sendMoney()` utilized blanket cache invalidation (`@CacheEvict(value = {"users", "user_ledgers"}, allEntries = true)`). In production, this created a **Cache Stampede (Thundering Herd)** vulnerability, purging all cached active sessions across the entire application whenever any two users completed a transfer.
In Phase 9B, Payflow API replaced blanket eviction with programmatic **Targeted Eviction** (`evictTargetedCaches`):
+
1. **Scope Bounded**: Only the transaction's `sender` and `receiver` keys are invalidated.
2. **Multi-Key Invalidation**: Selectively evicts each participant's primary lookup keys:
- `users` cache: `upiId`, `referenceId`, and internal `userId`.
@@ -1044,6 +1095,7 @@ In Phase 9B, Payflow API replaced blanket eviction with programmatic **Targeted
To ensure maximum code coverage and high system reliability, the project defines a two-tier testing strategy consisting of isolated unit tests and full-stack integration tests.
### A. Isolated Unit Testing Strategy
+
Unit tests focus on isolating individual components and verifying business logic without booting the database or messaging middleware:
1. **Controller Layer (MockMVC)**:
@@ -1059,6 +1111,7 @@ Unit tests focus on isolating individual components and verifying business logic
- Outbound REST endpoints (UPI validation via HTTP Interface Client), the AI model assistant API, and Kafka brokers are mocked or stubbed during unit verification using WireMock and MockRestServiceServer to prevent flaky test execution and external network dependence.
### B. Advanced Integration Testing (Rigor & Concurrency Verification)
+
Integration tests verify the full lifecycle of a transaction across actual container dependencies using **Testcontainers** to orchestrate PostgreSQL and Kafka instances during Maven build phases:
1. **Concurrent Race Condition Testing**:
@@ -1081,18 +1134,23 @@ Integration tests verify the full lifecycle of a transaction across actual conta
To establish robust domain boundaries and prevent corrupt data state, the entity layer adheres to Rich Domain Model principles and precise database constraints:
### A. Financial Precision (`BigDecimal`)
+
All monetary columns (`balance`, `amount`) are represented using `BigDecimal` mapped to database column definition `@Column(precision = 19, scale = 4, nullable = false)`. Floating-point binary arithmetic primitives (`double`, `float`) are prohibited to eliminate representation error accumulation.
### B. Rich Domain Invariants
+
Entities encapsulate their own business invariants and state transitions:
+
- **`User.debit(BigDecimal amount)`**: Enforces positive debit amounts and verifies balance adequacy (`balance >= amount`). Throws `IllegalStateException` or domain exceptions if invariants fail.
- **`User.credit(BigDecimal amount)`**: Enforces positive credit amounts and updates account balance atomically in-memory.
### C. Auditability & Optimistic Locking
+
- **Audit Timestamps**: `@CreationTimestamp Instant createdAt` and `@UpdateTimestamp Instant updatedAt` automatically track record creation and updates.
- **Optimistic Locking**: `@Version Long version` enables Hibernate to prevent lost updates during concurrent non-locking state updates.
### D. Relational Foreign Key Integrity
+
The `Transaction` entity maintains explicit JPA `@ManyToOne(fetch = FetchType.LAZY)` foreign key relationships to `User` for `sender` and `receiver`, while retaining denormalized `senderUpiId` and `receiverUpiId` fields for index-optimized queries.
---
@@ -1102,9 +1160,11 @@ The `Transaction` entity maintains explicit JPA `@ManyToOne(fetch = FetchType.LA
Payflow validates UPI IDs against an external validation service during user registration using modern Spring outbound HTTP communication patterns:
### A. RestClient (Spring Framework 7)
+
`RestClient` is the modern synchronous HTTP client that replaces the deprecated `RestTemplate`. It provides a fluent, immutable API with built-in error handling and serialization support.
### B. HTTP Interface Client
+
Payflow defines outbound API contracts as declarative Java interfaces using `@GetExchange` / `@PostExchange` annotations:
```java
@@ -1117,6 +1177,7 @@ public interface UpiValidationClient {
Spring generates the implementation proxy backed by `RestClient` at runtime — conceptually similar to OpenFeign but fully Spring-native with zero external dependencies.
### C. Resilience (Framework 7 Native @Retryable)
+
Outbound HTTP calls are wrapped with Framework 7's native `@Retryable` annotation (now part of `spring-core`) providing exponential backoff with jitter:
```
@@ -1128,6 +1189,7 @@ Attempt 3 → success or fallback
Graceful fallback: if the UPI validation service is unavailable, registration proceeds with a warning log — external service failures never block core user onboarding.
### D. HTTP Client Comparison Matrix
+
| Client | Era | Model | Use Case |
| :--- | :--- | :--- | :--- |
| `RestTemplate` | Spring 3 (deprecated in FW7) | Imperative, verbose | Legacy codebases |
@@ -1243,6 +1305,7 @@ The system attack surface has been systematically modeled against the **STRIDE**
### C. Identity & Access Management (IAM) & Endpoint Policy Matrix
#### 1. Authentication Architecture
+
- **Stateless Bearer Tokens**: Authenticated via HMAC-SHA256 (`HS256`) signed JSON Web Tokens (JJWT 0.13.0).
- **Token Claims**: Contains `sub` (UPI handle), `referenceId` (UUID), `roles` (`ROLE_USER`), `iat` (issued at), and `exp` (expiration).
- **Token Expiration**: Strict 1-hour validity window (`3,600,000 ms`) to minimize exposure window if a client token is intercepted.
@@ -1269,19 +1332,24 @@ The system attack surface has been systematically modeled against the **STRIDE**
| `/swagger-ui/**`, `/v3/api-docs/**` | `GET` | Public | No | OpenAPI 3.0 interactive documentation |
#### 3. Transport, Browser & Framing Security
+
- **Anti-Clickjacking**: Spring Security enforces `frameOptions().sameOrigin()`, ensuring that console, admin, or API documentation frames cannot be embedded in malicious cross-origin iframes.
- **CORS Hardening**: Cross-Origin Resource Sharing is configured via `payflow.security.cors.allowed-origins`. Credential sharing (`allowCredentials`) is automatically disabled if wildcard origins (`*`) are detected, preventing credential leakage.
### D. Financial State Protection & Concurrency Security
#### 1. Zero Double-Spending Guarantee
+
To eliminate race conditions and double-spending vulnerabilities during concurrent transfers:
+
1. Every transfer executes inside a Spring `@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 5)` boundary.
2. User balances are read and locked using **Pessimistic Write Locking** (`SELECT ... FOR UPDATE` via `UserRepository.findUserForUpdateByUpiId()`).
3. Concurrent threads attempting to debit the same sender account block at the database engine until the active transaction commits or aborts.
#### 2. Deadlock Elimination via Deterministic Lock Ordering
+
When two users concurrently transfer funds to each other (e.g. Aarav sends to Priya while Priya sends to Aarav):
+
- Acquiring locks in request order causes cyclical wait-for graphs and database deadlocks.
- Payflow resolves this by sorting both UPI IDs lexicographically (`upiA.compareTo(upiB)`) before issuing `SELECT ... FOR UPDATE` queries.
- Both transactions request locks in the exact same sequence (`aarav@payflow` first, then `priya@payflow`), converting cyclical deadlocks into sequential lock queues.
@@ -1319,6 +1387,7 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
#### 3. Financial Base-10 Arithmetic Precision
+
- All monetary columns (`balance`, `amount`, `balance_before`, `balance_after`) strictly use Java `BigDecimal` denominated in Indian Rupees (INR, ₹) mapped to database column `@Column(precision = 19, scale = 4, nullable = false)`.
- Floating-point types (`double`, `float`) are prohibited across the codebase to prevent IEEE 754 decimal rounding inaccuracies.
- Monetary divisions and conversions explicitly define `RoundingMode.HALF_EVEN` (Banker's Rounding).
@@ -1326,6 +1395,7 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
### E. Input Validation, Durable Idempotency & Cryptographic Protection
#### 1. Durable Idempotency & Payload Tampering Prevention
+
1. **Header Requirement & Boundary Validation**: Mutation endpoints require an `Idempotency-Key` HTTP header. Keys must be 1-255 characters conforming to `^[A-Za-z0-9_.:-]+$`, rejecting malformed or oversized keys immediately with `400 Bad Request` before database or lock acquisition.
2. **SHA-256 Request Payload Digest**: The raw HTTP request payload bytes are hashed using SHA-256 (`MessageDigest.getInstance("SHA-256")`).
3. **Tampering Detection**: If a client re-submits an existing `Idempotency-Key` with a different payload (e.g., changed amount or recipient), the engine detects a checksum mismatch and rejects the request with `400 Bad Request` (`Key Reuse`).
@@ -1333,6 +1403,7 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
5. **Cached Response Replay**: Successfully executed transfers replay the cached `201 Created` HTTP response without executing duplicate balance mutations.
#### 2. Input Validation & Injection Mitigation
+
- **Jakarta Bean Validation (`@Valid`)**: All incoming DTOs enforce strict declarative constraints:
- `@NotBlank`, `@Size(min = 3, max = 50)`
- `@Pattern(regexp = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9]+$")` (UPI ID syntax enforcement)
@@ -1342,6 +1413,7 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
- **Cross-Site Scripting (XSS)**: All responses return `application/json` or `application/problem+json`; HTML rendering is disabled in API controllers.
#### 3. Transactional Outbox Lifecycle & Retention Maintenance (Phase 9B)
+
- **Automated Retention Purge**: Domain events published through Spring Modulith are recorded in the `event_publication` table.
- **Scheduled Maintenance**: `OutboxCleanupService` runs daily at 02:00 AM UTC (`0 0 2 * * *`) via Spring's `@Scheduled` annotation.
- **Spring Modulith 2.0 API**: Calls `CompletedEventPublications.deletePublicationsOlderThan(Duration.ofDays(7))` to cleanly delete completed event records that have aged beyond the 7-day retention period (`payflow.outbox.retention-days`).
@@ -1350,23 +1422,27 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
### F. Resilience, Fault Tolerance & DoS Mitigation
#### 1. Dynamic Per-User Rate Limiting
+
- **Partitioned Rate Limiting**: Implemented via `@PerUserRateLimiter(name = "transferLimiter")` and dynamic Resilience4j registries. Limits are segregated per authenticated user principal (or remote IP for unauthenticated routes).
- **Default Baseline Policy**: 10 requests per second (`limit-for-period = 10`, `limit-refresh-period = 1s`, `timeout-duration = 0ms`).
- **RFC 6585 & RFC 7807 Compliance**: Rejections immediately short-circuit with HTTP `429 Too Many Requests`, an explicit `Retry-After: 1` header, and standardized ProblemDetail payload (`https://api.payflow.com/errors/rate-limit-exceeded`).
- **Memory Leak Protection**: Inactive user limiters are automatically audited and evicted every 15 minutes by `evictInactiveLimiters()`.
#### 2. Circuit Breaker & Cascade Failure Protection
+
- **External UPI Integration**: Downstream UPI banking network calls via `UpiValidationService` are wrapped in a Resilience4j `@CircuitBreaker(name = "upiValidation")`.
- **Sliding Window Evaluation**: Monitors the last 10 requests (`COUNT_BASED`). If 50% or more fail within a minimum of 5 recorded calls, the circuit opens.
- **Fail-Fast & Fallback**: In the `OPEN` state, requests fail fast with RFC 7807 `503 Service Unavailable` (`https://api.payflow.com/errors/service-unavailable`) without saturating thread pools or hammering downstream systems.
- **Auto-Recovery**: After 5 seconds, the circuit transitions to `HALF_OPEN`, permitting 3 canary probe requests to verify downstream health before closing.
#### 3. Execution Timeouts
+
- **Strict Time Limits**: External validation and outbound integrations enforce Resilience4j `TimeLimiter` policies (default 5 seconds), preventing connection starvation and pool exhaustion.
### G. Observability, Auditability & Data Privacy
#### 1. Immutable Double-Entry Balance Ledger
+
- Direct balance updates without ledger entries are forbidden.
- Every completed transfer writes two immutable records to `balance_ledger` (`DEBIT` and `CREDIT`) recording:
- Account reference (`user_id`, `upi_id`)
@@ -1377,6 +1453,7 @@ tx2 -> db: "Debit Priya, Credit Aarav, Commit & Release"
- Ledger tables contain no `UPDATE` or `DELETE` triggers; historical state can be audited or reconstructed at any point in time.
#### 2. Sensitive Data Masking, Log Sanitization & Transport Security
+
- **Credential Masking**: Passwords, raw JWT signatures, and authorization secret keys are excluded from `toString()` representations and log statements.
- **Structured Logging (ECS)**: Production logs output structured JSON adhering to Elastic Common Schema (`logging.structured.format.console: ecs`).
- **MDC Trace Correlation**: Every log statement automatically captures `requestId` (`X-Request-Id`), `traceId`, `spanId`, `http.status`, `http.method`, and `http.latency_ms`.
@@ -1405,6 +1482,7 @@ Documenting rejected alternatives and evaluating the penalty ("cost of getting i
## 20. Open & Resolved Design Issues
### Resolved Design Decisions
+
- **`RES-001`: MapStruct for DTO Mapping** — Resolved in Phase 2C. Replaced custom manual factories with type-safe MapStruct mappers.
- **`RES-002`: RFC 7807 Error Standard** — Resolved in Phase 2D. Standardized all exception responses on Spring `ProblemDetail`.
- **`RES-003`: User Reference IDs** — Resolved in Phase 2E. Added `UUID referenceId` to `User` entity to insulate API boundaries from auto-increment IDs.
@@ -1419,5 +1497,6 @@ Documenting rejected alternatives and evaluating the penalty ("cost of getting i
- **`RES-012`: Java 25 Virtual Threads and Bounded HikariCP Connection Pool Optimization** — Resolved in Phase 10B (ADR-027). Enabled `spring.threads.virtual.enabled: true` globally on Java 25, bounded HikariCP pools, and introduced low-memory `prod-light` profile with local Caffeine caching and synchronous Modulith events.
### Open Questions & Future Evaluation
+
- **`OPEN-001`: Transfer Amount Cap Configuration** — Default cap set to ₹1,00,000 (`100,000.00`). Evaluating whether per-user dynamic velocity caps should be stored in Redis.
diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md
index 683f226..aa285ae 100644
--- a/docs/CONVENTIONS.md
+++ b/docs/CONVENTIONS.md
@@ -38,6 +38,7 @@ This document outlines the coding standards, repository conventions, Git workflo
[optional body explaining rationale]
```
+
- **Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `ci`.
- **Example**: `feat(domain): replace Double balance with BigDecimal in User entity`
- **PR Size**: Keep PRs small and scoped to a single sub-phase (target: <500 lines of code changed).
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 82e3f61..230f3ec 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -2,18 +2,21 @@
A structured, phased evolution plan from baseline REST API to an enterprise-grade transactional ledger and payment backend.
-### Status Legend
-- ✅ Complete 🔄 In Progress ⬜ Planned
+!!! note "Status Legend"
+ - :white_check_mark: **Complete** | :arrows_counterclockwise: **In Progress** | :white_large_square: **Planned**
---
## Phase 0 — Baseline Architecture ✅
+
Initial User and Transaction REST endpoints with in-memory storage and layered application architecture.
---
## Phase 1 — Project Hygiene & Engineering Tooling ✅
+
Foundation setup adhering to modern Java standards:
+
- **Build & Standards**: GraalVM / JDK 25 alignment, Maven build configuration, MIT licensing, and semantic changelog.
- **Code Quality & Static Analysis**: Automated formatting with Spotless (Eclipse formatter) and Checkstyle rules bound to Maven build phases.
- **CI/CD Pipeline**: GitHub Actions continuous integration workflow verifying formatting, linting, and multi-tier test suites.
@@ -21,7 +24,9 @@ Foundation setup adhering to modern Java standards:
---
## Phase 2 — Domain Modeling & API Hardening ✅
+
Enterprise domain modeling and API isolation:
+
- **Rich Domain Model**: Financial precision via `BigDecimal` denominated in Indian Rupees (INR, ₹), Jakarta validation constraints, JPA foreign key integrity, and domain state invariants.
- **DTO Isolation & Versioning**: URI-based `/api/v1` API versioning, input validation, capped pagination, and compile-time MapStruct mapping.
- **Error Handling Framework**: Centralized RFC 9457 (obsoleting RFC 7807) `ProblemDetail` error responses with correlation IDs (`X-Request-Id`).
@@ -31,7 +36,9 @@ Enterprise domain modeling and API isolation:
---
## Phase 3 — ACID Transfers, Concurrency & Double-Entry Ledger ✅
+
Financial transaction engine guarantees:
+
- **Atomic Money Transfers**: ACID transfer orchestration denominated in Indian Rupees (INR, ₹) and append-only refund semantics within transactional boundaries.
- **Pessimistic Concurrency Control**: Row-level database write locking (`SELECT ... FOR UPDATE`) with deterministic alphabetical lock acquisition to eliminate deadlocks and race conditions.
- **Immutable Balance Ledger**: Double-entry bookkeeping recording paired debit/credit entries, balance audits, and cryptographic auditability.
@@ -39,7 +46,9 @@ Financial transaction engine guarantees:
---
## Phase 4 — Database Migrations & Environment Profiles ✅
+
Cloud-native persistence and test parity:
+
- **Database Schema Management**: Versioned Flyway SQL migrations, performance indexing, and Hibernate validation.
- **Spring Profiles**: Environment-specific configurations for `local` (H2 zero-dependency dev), `test`, and `prod` (PostgreSQL).
- **Testcontainers Infrastructure**: Production-parity PostgreSQL containerized testing integrated into build verification.
@@ -47,21 +56,27 @@ Cloud-native persistence and test parity:
---
## Phase 5 — Multi-Tier Testing Strategy ✅
+
Comprehensive automated quality verification:
+
- **Isolated Unit & Slice Testing**: Service logic unit testing with Mockito, Spring `@WebMvcTest` controller slices, and `@DataJpaTest` repository verification.
- **Concurrency & Integration Tests**: Multi-threaded race condition tests (10 concurrent threads), cross-transfer deadlock avoidance validation, and balance ledger reconciliation against containerized PostgreSQL.
---
## Phase 6 — Durable Idempotency & Transactional Outbox ✅
+
Fault-tolerant mutation guarantees and event persistence:
+
- **Durable Idempotency Engine**: Mandatory `Idempotency-Key` tracking with SHA-256 request payload hashing, cached response replay, in-flight conflict rejection, and background TTL cleanup.
- **Spring Modulith Transactional Outbox**: Event Publication Registry atomically saving domain events to the database outbox log within transfer transactions, paired with async in-process listeners and module boundary verification.
---
## Phase 7 — Security, Authorization & Outbound Integration ✅
+
Identity, access control, and resilient integrations:
+
- **Stateless Authentication**: Spring Security integration with HMAC-SHA256 JWT tokens, login endpoints, and secure filter chains.
- **Principal-Bound Authorization**: Strict sender verification, multi-party transaction visibility (sender/receiver only), and ledger privacy controls.
- **Declarative External Client**: Outbound HTTP Interface Client backed by `RestClient` for external UPI verification, featuring exponential backoff retry with jitter and non-blocking graceful fallback.
@@ -69,7 +84,9 @@ Identity, access control, and resilient integrations:
---
## Phase 8 — Observability, Resilience, Caching & Distributed Locking ✅
+
Enterprise reliability and distributed coordination:
+
- **Full-Stack Observability**: Native ECS JSON structured logging in production, MDC correlation tags, Prometheus metrics, HikariCP pool monitoring, and OpenTelemetry distributed tracing.
- **Resilience4j Fault Tolerance**: Dynamic per-user rate limiting (10 req/s with RFC 6585 headers), circuit breaking on external services, timeout policies, and automated memory eviction.
- **Distributed Caching**: Profile-conditional cache-aside pattern leveraging Redis with JSON serialization in production and local in-memory Caffeine fallback in non-production.
@@ -78,21 +95,27 @@ Enterprise reliability and distributed coordination:
---
## Phase 9 — Event Streaming, Outbox Lifecycle & Production Hardening ✅
+
Decoupled event-driven architecture and production resilience:
+
- **9A — Kafka via Spring Modulith Event Externalization**: Bridge domain events from transactional outbox to partitioned Apache Kafka topics with at-least-once delivery guarantees.
- **9B — Production Hardening & Security Audit**: Fail-fast secret validation, BOLA protection, targeted cache invalidation, and automated outbox retention cleanup.
---
## Phase 10 — Financial Intelligence & Performance Optimization ✅
+
Intelligent automation and runtime scalability:
+
- **10A — Gen-AI Spend Categorization**: Spring AI spend classification and actionable budgeting advice with circuit-breaker-backed heuristic fallback.
- **10B — Virtual Threads & Resource Tuning**: High-concurrency Java 25 Virtual Threads, bounded HikariCP connection pools, and lightweight `prod-light` profile.
---
## Phase 11 — Containerization, Kubernetes & Production Infrastructure ✅
+
Cloud-native packaging, orchestration, and automated quality gates:
+
- **11A — Multi-Stage Containerization & Local Orchestration**: Minimal multi-stage Docker build (Java 25 JRE, non-root user, healthcheck) and full-stack Docker Compose (PostgreSQL, Redis, Kafka, Ollama, Prometheus, Grafana).
- **11B — Cloud-Native Kubernetes Deployment**: Declarative Kubernetes manifests (Deployments, Services, ConfigMaps, Secrets, HPA, PDB) with graceful shutdown coordination.
- **11C — CI/CD Pipeline Hardening & Quality Gates**: Automated static analysis (SpotBugs) and strict code coverage enforcement (JaCoCo) integrated into GitHub Actions.
@@ -101,6 +124,8 @@ Cloud-native packaging, orchestration, and automated quality gates:
---
## Phase 12 — Production Readiness & Release Finalization ⬜ (Next Up)
+
Final verification and public milestone release:
+
- **12A — End-to-End System Smoke Verification**: Clone-and-run verification, container smoke tests, and OpenAPI validation.
- **12B — Documentation Finalization & Open-Source Release**: Comprehensive documentation audit and semantic version release `v1.0.0`.
diff --git a/docs/adr/0001-use-bigdecimal-for-monetary-values.md b/docs/adr/0001-use-bigdecimal-for-monetary-values.md
index c4aa6aa..9e9cd4b 100644
--- a/docs/adr/0001-use-bigdecimal-for-monetary-values.md
+++ b/docs/adr/0001-use-bigdecimal-for-monetary-values.md
@@ -5,20 +5,25 @@
* **Phase**: Phase 2A
## Context & Problem Statement
+
Floating-point primitive types (`double`, `float`) use IEEE 754 binary representation, which cannot precisely represent base-10 decimals (e.g., `0.1 + 0.2 = 0.30000000000000004`). In financial applications, accumulated rounding errors compromise ledger integrity and result in monetary discrepancy.
## Considered Options
+
1. **IEEE 754 Floating-Point (`Double`/`float`)**: Fast, built-in, but causes imprecise rounding error.
2. **Integer Cent/Sub-unit Amounts (`Long` cents)**: Precise, but awkward when dealing with fractional currency fractions or dynamic currency precision.
3. **Java `BigDecimal`**: Arbitrary-precision signed decimal numbers, explicitly suited for financial calculations.
## Decision Outcome
+
Chosen Option: **Java `BigDecimal`**
### Rationale
+
`BigDecimal` provides exact precision representation for decimal currency values in Indian Rupees (INR, symbol: `₹`) with configurable scale (`precision = 19, scale = 4` in database mapping) and explicit rounding modes (`RoundingMode.HALF_EVEN`).
## Consequences
+
* **Positive**: Zero floating-point rounding errors in monetary balance arithmetic and ledger entries.
* **Negative / Trade-offs**: Slightly higher memory overhead and minor performance cost compared to native double primitives.
* **Risks & Mitigations**: Always specify scale and explicit rounding mode (`RoundingMode.HALF_EVEN`) when performing division or scale adjustments.
diff --git a/docs/adr/0002-constructor-injection-over-field-injection.md b/docs/adr/0002-constructor-injection-over-field-injection.md
index 55b5baa..a6b349f 100644
--- a/docs/adr/0002-constructor-injection-over-field-injection.md
+++ b/docs/adr/0002-constructor-injection-over-field-injection.md
@@ -5,20 +5,25 @@
* **Phase**: Phase 2A
## Context & Problem Statement
+
Field injection via `@Autowired` tightly couples Spring components to the Spring DI container, prevents `final` immutable fields, hides component dependencies, and makes unit testing difficult without starting Spring contexts or using reflection.
## Considered Options
+
1. **Field Injection (`@Autowired private Service service`)**: Convenient syntax, but hides dependencies and inhibits immutability/testability.
2. **Setter Injection**: Allows optional dependencies, but enables mutable component state post-construction.
3. **Constructor Injection**: Explicitly declares required dependencies as `final` parameters.
## Decision Outcome
+
Chosen Option: **Constructor Injection**
### Rationale
+
Constructor injection enforces immutability (`final` fields), guarantees all required dependencies are provided at instantiation time, simplifies unit testing without Spring context spinners, and aligns with Spring Framework best practices.
## Consequences
+
* **Positive**: Clean component testability, immutability guarantees, static dependency verification at compile time.
* **Negative / Trade-offs**: Slightly more boilerplate constructor code (mitigated by explicit standard constructors).
* **Risks & Mitigations**: Circular dependency detection occurs at startup (which is desirable as it indicates architectural smell).
diff --git a/docs/adr/0003-rich-domain-model-over-anemic-domain-model.md b/docs/adr/0003-rich-domain-model-over-anemic-domain-model.md
index ce7f841..55c0710 100644
--- a/docs/adr/0003-rich-domain-model-over-anemic-domain-model.md
+++ b/docs/adr/0003-rich-domain-model-over-anemic-domain-model.md
@@ -5,19 +5,24 @@
* **Phase**: Phase 2A
## Context & Problem Statement
+
Anemic domain models treat JPA entities as simple data bags with getters and setters, scattering business invariants (such as non-negative balance checks and debit rules) across multiple service classes.
## Considered Options
+
1. **Anemic Domain Model**: Entities hold only state; services perform all business logic and validations.
2. **Rich Domain Model**: Entities encapsulate state alongside domain behaviors and invariant checks (`debit()`, `credit()`).
## Decision Outcome
+
Chosen Option: **Rich Domain Model**
### Rationale
+
Rich domain entities encapsulate domain invariants directly within entity boundaries (`User.debit()`, `User.credit()`), preventing invalid domain state transitions (e.g. negative balances or invalid debit amounts) regardless of caller invocation path.
## Consequences
+
* **Positive**: High cohesion, self-validating entities, reusable business logic across multiple services.
* **Negative / Trade-offs**: Entities must remain decoupled from infrastructure concerns (repositories, external APIs).
* **Risks & Mitigations**: Keep entity methods focused strictly on state invariants; delegate orchestration to domain services.
diff --git a/docs/adr/0004-uri-based-api-versioning-and-dto-isolation-layer.md b/docs/adr/0004-uri-based-api-versioning-and-dto-isolation-layer.md
index c94dce3..24771a3 100644
--- a/docs/adr/0004-uri-based-api-versioning-and-dto-isolation-layer.md
+++ b/docs/adr/0004-uri-based-api-versioning-and-dto-isolation-layer.md
@@ -5,22 +5,27 @@
* **Phase**: Phase 2B
## Context & Problem Statement
+
Exposing JPA entities directly through REST controllers risks over-posting, entity leak, tight coupling of API contracts to database schemas, and backwards-incompatible API breaks whenever internal domain models evolve. Additionally, unversioned API endpoints make contract evolution hazardous for clients.
## Considered Options
+
1. **Direct JPA Entity Exposure (Unversioned)**: Simple initial implementation, but leaks database structure, enables mass assignment vulnerabilities, and lacks contract stability.
2. **Header or Parameter Versioning**: Flexible versioning via HTTP headers (`Accept: application/vnd.payflow.v1+json`), but harder to cache via HTTP reverse proxies and less clear in logs.
3. **Explicit URI Path Versioning (`/api/v1/`) with DTO Isolation**: Explicit `/api/v1/` prefix with dedicated request objects (Java classes with Jakarta Validation) and response records (`UserResponse`, `TransactionResponse`, `PagedResponse`).
## Decision Outcome
+
Chosen Option: **URI Path Versioning (`/api/v1/`) with DTO Isolation**
### Rationale
+
* **Contract Stability**: Isolates REST API contracts from internal JPA entities using Java records for responses and validated DTO classes for requests.
* **Security & Validation**: Enforces exact field constraints (`@NotBlank`, `@Pattern`, `@DecimalMin`, `@Max`) at the API entry point via Jakarta Validation (`@Valid`), preventing malformed requests from reaching business logic.
* **Observability & Caching**: URI versioning (`/api/v1/`) provides clean HTTP proxy caching and transparent log routing across API gateway boundaries.
## Consequences
+
* **Positive**: Strict decoupling between database tables and API responses; robust validation; backward compatibility pathway (`/api/v2/` in future phases).
* **Negative / Trade-offs**: Mapping code required between DTOs and entities (`UserResponse.fromEntity()`).
* **Risks & Mitigations**: Maintain mapping logic in static factory methods on response records to keep controllers clean and performant.
diff --git a/docs/adr/0005-mapstruct-for-compile-time-type-safe-dto-mapping.md b/docs/adr/0005-mapstruct-for-compile-time-type-safe-dto-mapping.md
index 6fe4cb7..46e138e 100644
--- a/docs/adr/0005-mapstruct-for-compile-time-type-safe-dto-mapping.md
+++ b/docs/adr/0005-mapstruct-for-compile-time-type-safe-dto-mapping.md
@@ -5,22 +5,27 @@
* **Phase**: Phase 2C
## Context & Problem Statement
+
Manual object mapping between entities and DTOs introduces boilerplate code, increases maintenance overhead as domain models expand, and is prone to human error (such as missed field copies). Runtime reflection mapping frameworks (e.g. ModelMapper) introduce non-trivial performance latency and hide field type mismatches until runtime execution.
## Considered Options
+
1. **Manual Mapping Methods**: Writing custom `toEntity()` / `toResponse()` conversion code in controllers or static factory methods. Highly performant, but repetitive and boilerplate-heavy.
2. **Runtime Reflection Mappers (ModelMapper, Dozer)**: Automated mapping via reflection, but incurs runtime CPU overhead and hides mapping errors until runtime execution.
3. **Compile-Time Code Generation (MapStruct)**: Annotation processor generates plain, un-reflected Java byte-code at compile time with type checking and zero runtime performance penalty.
## Decision Outcome
+
Chosen Option: **MapStruct (Compile-Time Generation)**
### Rationale
+
* **Zero Runtime Overhead**: MapStruct generates plain Java method calls during compilation; no reflection is executed at runtime.
* **Compile-Time Verification**: Unmapped target properties or mismatched types raise immediate compiler errors rather than silent runtime failures.
* **Spring Integration**: Native integration with Spring Dependency Injection (`componentModel = "spring"`), allowing mappers to be injected clean into `@RestController` components.
## Consequences
+
* **Positive**: Blazing-fast performance (identical to handwritten code), strict compile-time type checking, clean controller code.
* **Negative / Trade-offs**: Requires annotation processor configuration (`mapstruct-processor`) in Maven `pom.xml`.
* **Risks & Mitigations**: Ensure `maven-compiler-plugin` includes `mapstruct-processor` in its `annotationProcessorPaths`.
diff --git a/docs/adr/0006-rfc-7807-problemdetail-and-centralized-exception-handling.md b/docs/adr/0006-rfc-7807-problemdetail-and-centralized-exception-handling.md
index ed8df32..0df6721 100644
--- a/docs/adr/0006-rfc-7807-problemdetail-and-centralized-exception-handling.md
+++ b/docs/adr/0006-rfc-7807-problemdetail-and-centralized-exception-handling.md
@@ -5,23 +5,28 @@
* **Phase**: Phase 2D
## Context & Problem Statement
+
Without centralized exception handling, uncaught runtime exceptions return default Spring Whitelabel 500 HTML pages or raw Java stack traces, leaking internal system implementation details, security vulnerabilities, and database query structures. Furthermore, non-standardized error JSON responses force frontends and API consumers to write ad-hoc error handling logic for different endpoints.
## Considered Options
+
1. **Default Spring Boot Whitelabel / ErrorController**: Simple, but exposes HTML or unstandardized JSON responses without consistent error schema.
2. **Custom DTO Error Response Class**: Custom Java class. Standardized within the application, but non-compliant with open web standards.
3. **RFC 7807 `ProblemDetail` via `@RestControllerAdvice`**: Native Spring Boot standard (`org.springframework.http.ProblemDetail`) defining structured HTTP error responses with `type`, `title`, `status`, `detail`, `instance`, `timestamp`, and `X-Request-Id` correlation tracking.
## Decision Outcome
+
Chosen Option: **RFC 7807 `ProblemDetail` via `@RestControllerAdvice`**
### Rationale
+
* **Industry Standard**: RFC 7807 provides a universally recognized format (`application/problem+json`) understood natively by modern API clients and gateways.
* **Zero Information Leakage**: All uncaught exceptions are intercepted and sanitized to generic 500 responses (`"An unexpected internal error occurred"`), preventing stack trace leaks.
* **Field-Level Validation Reporting**: DTO validation errors (`MethodArgumentNotValidException`) provide structured `errors` maps with status `422 Unprocessable Entity`.
* **Request Correlation**: Integrated with `RequestIdFilter` (`X-Request-Id` in MDC) so every error response includes the request correlation ID for distributed tracing.
## Consequences
+
* **Positive**: Consistent API error contract, enhanced security, production-grade observability and correlation tracing.
* **Negative / Trade-offs**: Custom exceptions must be mapped in `@RestControllerAdvice`.
* **Risks & Mitigations**: Ensure all domain services throw specific `PayflowException` subtypes rather than generic runtime exceptions.
diff --git a/docs/adr/0007-uuid-reference-ids-over-auto-increment-primary-keys.md b/docs/adr/0007-uuid-reference-ids-over-auto-increment-primary-keys.md
index 1c11870..23ac727 100644
--- a/docs/adr/0007-uuid-reference-ids-over-auto-increment-primary-keys.md
+++ b/docs/adr/0007-uuid-reference-ids-over-auto-increment-primary-keys.md
@@ -5,25 +5,31 @@
* **Phase**: Phase 2E
## Context & Problem Statement
+
Exposing auto-increment database primary keys (`Long userId`) in external REST URLs (e.g. `/api/v1/users/1`) introduces significant security vulnerabilities:
+
1. **Resource Enumeration Attacks**: Attackers can sequentially query `/users/1`, `/users/2`, `/users/3` to scrape all system user profiles.
2. **Business Metric Leakage**: Competitors can determine total registered user growth rates by observing sequential ID progression over time.
3. **Internal Key Coupling**: Exposing internal database sequence keys couples external client contracts directly to database storage strategies.
## Considered Options
+
1. **Expose Auto-Increment Long Primary Keys (`userId`)**: Simple, but vulnerable to enumeration attacks and leaks business growth metrics.
2. **Expose Friendly Handles Only (`upiId`)**: Human-readable (`shashank@kotak`), but handles can change over time as users re-link bank accounts.
3. **Dual Identification Strategy (Internal `userId`, External `referenceId` UUID)**: Retain `Long userId` internally for fast database foreign key joins and indexing, but assign a non-enumerable `UUID referenceId` for all API responses and URL routing.
## Decision Outcome
+
Chosen Option: **Dual Identification Strategy (Internal `userId`, External `referenceId` UUID)**
### Rationale
+
* **Security & Privacy**: Cryptographically pseudo-random UUID v4 strings prevent resource enumeration attacks and hide user creation counts.
* **Domain Flexibility**: `upiId` remains the friendly human-readable handle (`shashank@kotak`), while `referenceId` serves as the immutable internal system reference.
* **Performance**: High-performance SQL joins continue to utilize numeric `BIGINT` primary/foreign keys (`user_id`), avoiding string join performance overhead in PostgreSQL.
## Consequences
+
* **Positive**: Complete insulation against resource enumeration attacks, non-leaky API contracts, optimized database joins.
* **Negative / Trade-offs**: Entities require a `@PrePersist` hook or column default to generate UUIDs upon creation.
* **Risks & Mitigations**: Ensure secondary unique index (`idx_users_reference_id`) is maintained on `referenceId`.
diff --git a/docs/adr/0008-spring-modulith-modular-monolith-over-distributed-microservices.md b/docs/adr/0008-spring-modulith-modular-monolith-over-distributed-microservices.md
index 8be4a63..3266114 100644
--- a/docs/adr/0008-spring-modulith-modular-monolith-over-distributed-microservices.md
+++ b/docs/adr/0008-spring-modulith-modular-monolith-over-distributed-microservices.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 6B (Architecture Review)
## Context & Problem Statement
+
Payflow processes peer-to-peer financial transfers where a single operation involves debiting the sender, crediting the receiver, recording a transaction, writing ledger entries, and publishing domain events. Splitting these into separate microservices (e.g., `User Service`, `Transfer Service`, `Ledger Service`) would lose local database ACID transactions, requiring distributed saga orchestration or two-phase commit (2PC) to maintain consistency. This introduces significant complexity, dual-write bugs, and split-brain risks before the domain rules are even stable.
## Considered Options
+
1. **Distributed Microservices (HTTP/gRPC)**: Each domain concern runs as a separate deployable service. Requires distributed transactions (sagas, 2PC) for cross-service state consistency.
2. **Plain Monolith (No Module Boundaries)**: Single deployable with no enforced package boundaries. Simple but leads to spaghetti coupling as the codebase grows.
3. **Spring Modulith Modular Monolith**: Single deployable with strict, compile-time-verified module boundaries. Uses Spring's `ApplicationEventPublisher` for inter-module communication. Evolves to Kafka event streaming via `spring-modulith-events-kafka` without domain code changes.
## Decision Outcome
+
Chosen Option: **Spring Modulith Modular Monolith**
### Rationale
+
* **ACID Safety**: Sender debit, receiver credit, transaction record, ledger entries, and domain event all execute in a single `@Transactional` database transaction. Zero distributed transaction overhead.
* **Enforced Boundaries**: `ApplicationModules.of(PayflowApiApplication.class).verify()` test validates strict package encapsulation at compile time, preventing accidental cross-module coupling.
* **Event Publication Registry**: Spring Modulith's `spring-modulith-starter-jpa` persists domain events (e.g., `TransferCompletedEvent`) to an `event_publication` table atomically within the same DB transaction. This is a framework-managed transactional outbox.
@@ -23,6 +27,7 @@ Chosen Option: **Spring Modulith Modular Monolith**
* **Architectural Simplicity**: Avoids premature microservice distribution and preserves single-database transaction boundaries until physical service isolation is explicitly required.
## Consequences
+
* **Positive**: Single-database ACID safety, zero distributed transaction complexity, compile-time boundary enforcement, seamless Kafka evolution path.
* **Negative / Trade-offs**: All modules share a single database and JVM. Horizontal scaling is per-application-instance, not per-module.
* **Risks & Mitigations**: If individual modules need independent scaling (unlikely at Payflow's scale), Spring Modulith modules can be extracted to standalone services along their already-enforced API boundaries.
diff --git a/docs/adr/0009-opentelemetry-distributed-tracing-via-micrometer-bridge.md b/docs/adr/0009-opentelemetry-distributed-tracing-via-micrometer-bridge.md
index 3781c81..83c0c70 100644
--- a/docs/adr/0009-opentelemetry-distributed-tracing-via-micrometer-bridge.md
+++ b/docs/adr/0009-opentelemetry-distributed-tracing-via-micrometer-bridge.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 8A
## Context & Problem Statement
+
Payflow needs distributed tracing to correlate requests across HTTP controllers, database queries, async event listeners, and (in production) Kafka consumers. The existing `X-Request-Id` MDC pattern (Phase 2D) provides basic request correlation but does not follow W3C trace propagation standards, making it incompatible with industry-standard tracing backends (Grafana Tempo, Jaeger, Zipkin).
## Considered Options
+
1. **Custom `X-Request-Id` only (Current State)**: Simple UUID injected via `RequestIdFilter`. No W3C standard compliance, no automatic span propagation across async boundaries.
2. **Vendor-Specific Tracing SDK (e.g., Datadog, New Relic)**: Proprietary SDKs with deep integration but vendor lock-in and paid tiers.
3. **Micrometer Tracing + OpenTelemetry Bridge**: Uses `micrometer-tracing-bridge-otel` to bridge Spring Boot's native Micrometer Observation API to the OpenTelemetry SDK. Exports traces via OTLP to any compatible backend. W3C `traceparent` propagation standard.
## Decision Outcome
+
Chosen Option: **Micrometer Tracing + OpenTelemetry Bridge**
### Rationale
+
* **W3C Standard**: `traceparent` headers (`traceId`, `spanId`) are automatically injected into HTTP requests, database queries, and async thread pools. Compatible with any OTLP backend.
* **Zero Vendor Lock-in**: Micrometer Tracing is the Spring Boot native abstraction. The OTel bridge can export to Grafana Tempo (free/open-source), Jaeger, Zipkin, or any commercial APM.
* **Coexistence with X-Request-Id**: The existing `RequestIdFilter` and MDC `requestId` are preserved. Both `requestId` and `traceId` appear in structured log output, providing layered correlation.
@@ -23,6 +27,7 @@ Chosen Option: **Micrometer Tracing + OpenTelemetry Bridge**
* **All Open-Source**: `micrometer-tracing-bridge-otel` and `opentelemetry-exporter-otlp` are fully open-source with no paid tiers.
## Consequences
+
* **Positive**: Industry-standard distributed tracing, portable across backends, automatic span propagation, zero vendor lock-in.
* **Negative / Trade-offs**: Additional dependencies (`micrometer-tracing-bridge-otel`, `opentelemetry-exporter-otlp`). Trace sampling must be configured to balance performance.
* **Risks & Mitigations**: Set `management.tracing.sampling.probability` to `1.0` for dev/staging (100% traces) and `0.1` for production (10% sampling) to balance observability with performance.
diff --git a/docs/adr/0010-pessimistic-locking-for-high-concurrency-balance-operations.md b/docs/adr/0010-pessimistic-locking-for-high-concurrency-balance-operations.md
index 23b1b4b..3eb741b 100644
--- a/docs/adr/0010-pessimistic-locking-for-high-concurrency-balance-operations.md
+++ b/docs/adr/0010-pessimistic-locking-for-high-concurrency-balance-operations.md
@@ -5,20 +5,25 @@
* **Phase**: Phase 3B
## Context & Problem Statement
+
Money transfers require atomic balance updates (`sender.debit()`, `receiver.credit()`). Under concurrent execution, read-then-write patterns without explicit database locking lead to race conditions and lost updates (double-spending).
## Considered Options
+
1. **Optimistic Locking (`@Version`)**: Detects concurrent modifications at commit time and throws `OptimisticLockException`. Requires retry loops in application code. Under high write contention (e.g. popular merchants receiving hundreds of transfers per second), optimistic locking results in high abort rates.
2. **Pessimistic Write Locking (`SELECT ... FOR UPDATE`)**: Acquires exclusive row-level database locks when reading user balances inside the transaction boundary. Subsequent concurrent transactions attempting to read/lock the same account block until the holding transaction commits or rolls back.
## Decision Outcome
+
Chosen Option: **Pessimistic Write Locking (`SELECT ... FOR UPDATE`)**
### Rationale
+
* **Guaranteed Consistency**: Exclusive row locks prevent concurrent reads of stale balances during active transfers, eliminating double-spending race conditions.
* **Predictable Execution**: Transactions execute sequentially per account without triggering application-level retry loops or transaction abort spikes under high write contention.
## Consequences
+
* **Positive**: Guaranteed ACID balance integrity, zero double-spend window.
* **Negative / Trade-offs**: Concurrent transfers targeting the same account wait on database row locks, increasing database connection hold times under load.
* **Risks & Mitigations**: Set explicit `@Transactional(timeout = 5)` transaction timeouts to prevent lock wait deadlocks from holding connection pool resources indefinitely.
diff --git a/docs/adr/0011-deterministic-lock-ordering-for-deadlock-prevention.md b/docs/adr/0011-deterministic-lock-ordering-for-deadlock-prevention.md
index c7fc6a6..1c873a6 100644
--- a/docs/adr/0011-deterministic-lock-ordering-for-deadlock-prevention.md
+++ b/docs/adr/0011-deterministic-lock-ordering-for-deadlock-prevention.md
@@ -5,19 +5,24 @@
* **Phase**: Phase 3B
## Context & Problem Statement
+
When acquiring pessimistic write locks on two database rows (sender and receiver accounts), non-deterministic lock acquisition order causes database deadlocks under concurrent cross-transfers (e.g. Tx 1: Aarav sends to Priya; Tx 2: Priya sends to Aarav). Tx 1 locks Aarav then waits for Priya; Tx 2 locks Priya then waits for Aarav, resulting in a cyclical lock dependency deadlock.
## Considered Options
+
1. **Application Lock Ordering (Sender First, Receiver Second)**: Simple, but vulnerable to deadlocks whenever reciprocal transfers execute concurrently.
2. **Deterministic Lock Ordering (Alphabetical by UPI ID)**: Sort sender and receiver UPI IDs lexicographically prior to lock acquisition. Both Tx 1 and Tx 2 acquire locks in the exact same sequence (`aarav@payflow` first, then `priya@payflow`).
## Decision Outcome
+
Chosen Option: **Deterministic Lock Ordering (Alphabetical by UPI ID)**
### Rationale
+
* **Deadlock Elimination**: Strictly ordering lock requests prevents cyclical wait graphs at the database level. Both reciprocal transfers attempt to lock `aarav@payflow` first; the second transaction cleanly blocks until the first completes.
* **Zero Overhead**: Sorting two string references in memory takes negligible time (<1 microsecond).
## Consequences
+
* **Positive**: Eliminates database deadlocks during reciprocal concurrent money transfers.
* **Negative / Trade-offs**: Requires minor mapping logic to re-assign `sender` and `receiver` domain entity references after acquiring locks in sorted order.
diff --git a/docs/adr/0012-double-entry-balance-ledger-as-immutable-audit-trail.md b/docs/adr/0012-double-entry-balance-ledger-as-immutable-audit-trail.md
index 001d450..ac2e830 100644
--- a/docs/adr/0012-double-entry-balance-ledger-as-immutable-audit-trail.md
+++ b/docs/adr/0012-double-entry-balance-ledger-as-immutable-audit-trail.md
@@ -5,20 +5,25 @@
* **Phase**: Phase 3C
## Context & Problem Statement
+
Directly mutating `users.balance` without an explicit ledger record creates financial audit risks: if a balance value becomes corrupted or disputed, there is no immutable audit trail to reconstruct the historical sequence of balance states or verify financial integrity.
## Considered Options
+
1. **Single Balance Field Mutation (`users.balance`)**: Simple, but lacks historical auditability and makes financial balance reconciliation impossible.
2. **Double-Entry Balance Ledger (`balance_ledger` table)**: Write two immutable ledger entries (`DEBIT` for sender, `CREDIT` for receiver) capturing `amount`, `balanceBefore`, and `balanceAfter` inside the same `@Transactional` database boundary as the money transfer.
## Decision Outcome
+
Chosen Option: **Double-Entry Balance Ledger (`balance_ledger` table)**
### Rationale
+
* **Financial Auditability**: Every money movement creates two immutable ledger entries documenting exact balance state changes before and after execution.
* **Reconciliation Support**: `users.balance` acts as a high-performance denormalized field; the actual source of financial truth can be reconstructed at any time using a `SUM(CASE WHEN entry_type = 'CREDIT' THEN amount ELSE -amount END)` SQL aggregate query over the `balance_ledger` table.
* **ACID Atomicity**: Sender debit, receiver credit, transaction record, and both ledger entries execute in a single database transaction.
## Consequences
+
* **Positive**: Complete audit trail, balance reconstruction capability, financial compliance.
* **Negative / Trade-offs**: Increases database row writes per transaction from 3 to 5 (2 user updates, 1 transaction record, 2 ledger records).
diff --git a/docs/adr/0013-flyway-database-migrations-over-ddl-auto-generation.md b/docs/adr/0013-flyway-database-migrations-over-ddl-auto-generation.md
index 766afa5..16a8abb 100644
--- a/docs/adr/0013-flyway-database-migrations-over-ddl-auto-generation.md
+++ b/docs/adr/0013-flyway-database-migrations-over-ddl-auto-generation.md
@@ -5,20 +5,25 @@
* **Phase**: Phase 4A
## Context & Problem Statement
+
Relying on Hibernate `spring.jpa.hibernate.ddl-auto=update` or `create-drop` in production environments creates high-risk database mutation vulnerabilities: schema changes are unversioned, non-repeatable, cannot be reviewed in pull requests, and risk accidental data loss or locking during application restarts.
## Considered Options
+
1. **Hibernate DDL Auto-Generation (`ddl-auto=update`)**: Convenient for early prototyping, but non-deterministic, unversioned, and prohibited in production environments.
2. **Flyway Versioned SQL Migrations**: Versioned, immutable SQL migration scripts (`V1__...`, `V2__...`) stored in version control (`db/migration`), executed deterministically on startup, with Hibernate configured to `ddl-auto=validate`.
## Decision Outcome
+
Chosen Option: **Flyway Versioned SQL Migrations (`org.flywaydb:flyway-core`)**
### Rationale
+
* **Production Safety**: Schema migrations are explicit, version-controlled SQL files that can be audited in code reviews before deployment.
* **Strict Validation**: Hibernate `ddl-auto=validate` verifies entity mappings against Flyway-managed schema without altering database tables dynamically.
* **Environmental Consistency**: Flyway ensures identical database schema evolution across local development, CI pipelines, staging, and production environments.
## Consequences
+
* **Positive**: Complete schema versioning history, zero accidental DDL mutations, production-grade deployment safety.
* **Negative / Trade-offs**: Schema changes require writing explicit SQL migration scripts alongside entity modifications.
diff --git a/docs/adr/0014-spring-environment-profiles-and-testcontainers-integration-testing-strategy.md b/docs/adr/0014-spring-environment-profiles-and-testcontainers-integration-testing-strategy.md
index b649c9b..787f029 100644
--- a/docs/adr/0014-spring-environment-profiles-and-testcontainers-integration-testing-strategy.md
+++ b/docs/adr/0014-spring-environment-profiles-and-testcontainers-integration-testing-strategy.md
@@ -5,9 +5,11 @@
* **Phase**: Phase 4B
## Context & Problem Statement
+
Running application integration tests against in-memory H2 databases can mask database-specific SQL dialect bugs, locking behavior differences, and Flyway migration syntax errors that only manifest in production PostgreSQL environments. Conversely, requiring a manually managed external PostgreSQL database for every unit test run causes developer friction and fragile CI builds.
## Considered Options
+
1. **Single H2 Environment Configuration**: Simple but lacks production environment parity and risks PostgreSQL dialect/locking incompatibilities.
2. **Environment Profiles (`local`, `test`, `prod`) with Testcontainers PostgreSQL**:
- `local`: Embedded H2 in PostgreSQL compatibility mode for instant zero-dependency local development (`application-local.yml`).
@@ -15,13 +17,16 @@ Running application integration tests against in-memory H2 databases can mask da
- `prod`: External PostgreSQL database configured via environment variables with HikariCP connection pool tuning and graceful shutdown (`application-prod.yml`).
## Decision Outcome
+
Chosen Option: **Environment Profiles (`local`, `test`, `prod`) with Testcontainers PostgreSQL**
### Rationale
+
* **Production Parity in Tests**: Testcontainers boots a real Dockerized PostgreSQL container during integration test execution, validating Flyway migrations (`V1`..`V4`), dialect behavior, and JPA locking against real PostgreSQL.
* **Instant Local Dev Iteration**: The `local` profile uses H2 in PostgreSQL mode, enabling instant application startup without local Docker overhead.
* **Graceful Execution**: Using `@Testcontainers(disabledWithoutDocker = true)` ensures tests run automatically when Docker is active, while falling back gracefully when running in Docker-less environments.
## Consequences
+
* **Positive**: 100% production parity for database integration tests, zero environment drift, clean separation of configuration per profile.
* **Negative / Trade-offs**: Integration test execution requires Docker daemon access when running Testcontainers tests.
diff --git a/docs/adr/0015-sha-256-request-payload-hashing-and-durable-idempotency-engine.md b/docs/adr/0015-sha-256-request-payload-hashing-and-durable-idempotency-engine.md
index bf2d2b9..d2f9cad 100644
--- a/docs/adr/0015-sha-256-request-payload-hashing-and-durable-idempotency-engine.md
+++ b/docs/adr/0015-sha-256-request-payload-hashing-and-durable-idempotency-engine.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 6A
## Context & Problem Statement
+
In peer-to-peer payment APIs, network timeouts, client reconnections, and gateway retries frequently cause duplicate HTTP `POST` mutation requests. Without strict idempotency controls, a client retrying a transfer could execute duplicate balance debits and transfers.
## Considered Options
+
1. **In-Memory Cache (e.g. Guava/Caffeine)**: Fast, but lost on application restart and cannot be shared across multiple backend server instances.
2. **Distributed Redis Cache**: Low latency, but adds operational infrastructure complexity and risks split-brain/data loss if Redis restarts without AOF persistence.
3. **Durable Database-Backed Registry with SHA-256 Payload Hashing**: Store idempotency records in a dedicated PostgreSQL table (`idempotency_registry`), verified with SHA-256 cryptographic digests, with background scheduled TTL cleanup.
## Decision Outcome
+
Chosen Option: **Durable Database-Backed Registry with SHA-256 Payload Hashing**
### Rationale
+
* **Zero Double-Spending Guarantee**: Storing records in PostgreSQL ensures ACID durability across node restarts, horizontal scaling, and transactional isolation.
* **Payload Tampering & Reuse Prevention**: Computing a deterministic SHA-256 hash of the raw HTTP request bytes prevents fraudulent client key reuse with modified amounts or recipient UPIs.
* **In-Flight Conflict Detection & Crash Lease Recovery**: Status tracking (`PROCESSING` / `INITIATED`) detects concurrent requests with the same key and rejects them with `409 Conflict`. An in-flight lease expiration window (2 minutes) ensures that orphaned in-flight states from crashed worker nodes automatically unlock for client retries without waiting for the 24-hour TTL purge.
@@ -24,5 +28,6 @@ Chosen Option: **Durable Database-Backed Registry with SHA-256 Payload Hashing**
* **Performance**: An index on `created_at` (`idx_idemp_created`) guarantees rapid scheduled TTL purge queries without scanning the entire registry table.
## Consequences
+
* **Positive**: Absolute protection against duplicate payments, standard financial industry compliance (Stripe/Adyen pattern), zero external infrastructure dependencies, resilience to worker node crashes.
* **Negative / Trade-offs**: Requires database round-trips for mutation requests; requires periodic TTL purge job.
diff --git a/docs/adr/0016-spring-modulith-event-publication-registry-and-transactional-outbox.md b/docs/adr/0016-spring-modulith-event-publication-registry-and-transactional-outbox.md
index 7f3fb90..a45a549 100644
--- a/docs/adr/0016-spring-modulith-event-publication-registry-and-transactional-outbox.md
+++ b/docs/adr/0016-spring-modulith-event-publication-registry-and-transactional-outbox.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 6B
## Context & Problem Statement
+
When a payment transaction completes, domain events (such as `TransferCompletedEvent`) must be published for asynchronous auditing, notifications, and downstream processing (e.g. Kafka streaming in Phase 9). Directly publishing events over network brokers inside a `@Transactional` business method causes the **dual-write problem**: if the broker call fails after DB commit, or if the DB transaction aborts after message dispatch, the systems fall out of sync.
## Considered Options
+
1. **Direct Synchronous Broker Publishing (e.g. Kafka/RabbitMQ in `@Transactional`)**: Vulnerable to the dual-write problem, network latency, and distributed transaction anomalies.
2. **Hand-Rolled Outbox Table with Polling Dispatcher**: Requires custom schema, custom polling workers with advisory locks, dead-letter retry queues, and significant maintenance overhead.
3. **Spring Modulith Event Publication Registry (`spring-modulith-starter-jpa`)**: Framework-managed transactional outbox using `ApplicationEventPublisher`. Events published within `@Transactional` are atomically recorded in an `event_publication` log table in the same database transaction. Async event listeners annotated with `@ApplicationModuleListener` execute outside the publishing transaction and mark the registry entry as completed.
## Decision Outcome
+
Chosen Option: **Spring Modulith Event Publication Registry (`spring-modulith-starter-jpa`)**
### Rationale
+
* **Zero Dual-Write Problem**: Events are persisted to PostgreSQL within the exact same ACID transaction as the wallet balance mutations and double-entry ledger entries.
* **Transactional Decoupling**: Downstream consumers (`@ApplicationModuleListener`) run in separate asynchronous transactions after commit. Failures in consumers do not fail or roll back the completed financial transfer.
* **Automatic Event Replay & Resilience**: On application restart or retry, uncompleted events in `event_publication` can be re-dispatched (`republish-outstanding-events-on-restart: true`).
@@ -23,5 +27,6 @@ Chosen Option: **Spring Modulith Event Publication Registry (`spring-modulith-st
* **Zero Custom Boilerplate**: Eliminates hundreds of lines of custom polling daemon and state-tracking code.
## Consequences
+
* **Positive**: ACID atomicity for domain events, seamless future externalization to Kafka (Phase 9A), built-in architectural verification.
* **Negative / Trade-offs**: Requires `event_publication` database table managed via Flyway (`V6__create_event_publication_registry.sql`).
diff --git a/docs/adr/0017-stateless-jwt-authentication-and-spring-security-architecture.md b/docs/adr/0017-stateless-jwt-authentication-and-spring-security-architecture.md
index 9d5ff51..ed82556 100644
--- a/docs/adr/0017-stateless-jwt-authentication-and-spring-security-architecture.md
+++ b/docs/adr/0017-stateless-jwt-authentication-and-spring-security-architecture.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 7A
## Context & Problem Statement
+
To secure the Payflow API against unauthorized transfers, data breaches, and identity spoofing, all mutation and sensitive query endpoints must enforce strong client authentication. In distributed and horizontally scaled cloud deployments, stateful HTTP sessions (session cookies / server-side session stores) introduce clustering bottlenecks, sticky routing complexity, and cache invalidation challenges.
## Considered Options
+
1. **HTTP Basic Authentication**: Simple, but requires transmitting user credentials with every single request, increasing exposure surface.
2. **Stateful Server-Side Sessions (Redis Session Store)**: Secure, but requires distributed cache infrastructure and introduces network hops for every request.
3. **Stateless JSON Web Tokens (JWT) with HMAC-SHA256 (HS256)**: Cryptographically signed, self-contained bearer tokens validated locally in memory by `JwtAuthenticationFilter` without database lookups on every request.
## Decision Outcome
+
Chosen Option: **Stateless JSON Web Tokens (JWT) with HMAC-SHA256 (HS256) via Spring Security 6/7**
### Rationale
+
* **High Scalability & Zero Session Storage**: Tokens are stateless and self-contained, containing user identity (`upiId`), UUID reference ID (`referenceId`), and role claims (`ROLE_USER`), enabling seamless horizontal auto-scaling without shared session replication.
* **Strong Cryptographic Integrity**: Signed with HMAC-SHA256 (minimum 256-bit secret) using JJWT 0.13.0, preventing token tampering and forgery.
* **Fine-Grained Endpoint Security Filter Chain**: Configured via modern Spring Security `SecurityFilterChain` bean:
@@ -25,5 +29,6 @@ Chosen Option: **Stateless JSON Web Tokens (JWT) with HMAC-SHA256 (HS256) via Sp
* **Cross-Origin Resource Sharing (CORS)**: Configured with customizable origin patterns, headers, and HTTP methods for modern web frontends.
## Consequences
+
* **Positive**: High throughput stateless authentication, zero database lookup per request for token verification, production-grade Spring Security integration, clean testability via `@WithMockUser` and `authHeaders()` helpers.
* **Negative / Trade-offs**: Tokens cannot be arbitrarily revoked before expiration without maintaining a token blacklist/revocation registry (handled in advanced auth phases). Configurable 1-hour expiration mitigates window of vulnerability.
diff --git a/docs/adr/0018-principal-bound-resource-access-control-and-sender-verification.md b/docs/adr/0018-principal-bound-resource-access-control-and-sender-verification.md
index eb103a7..b9fcd12 100644
--- a/docs/adr/0018-principal-bound-resource-access-control-and-sender-verification.md
+++ b/docs/adr/0018-principal-bound-resource-access-control-and-sender-verification.md
@@ -5,11 +5,14 @@
* **Phase**: Phase 7B
## Context & Problem Statement
+
In a payment system, authentication (Phase 7A) establishes identity via JWT bearer tokens, but does not inherently restrict which resources the authenticated principal can mutate or inspect. Without explicit fine-grained authorization:
+
1. An authenticated user `alice@payflow` could submit a transfer request with `senderUpiId: bob@payflow`, fraudulently debiting Bob's balance (Impersonation / Unauthorized Debit).
2. An authenticated user could inspect another user's balance ledger entries (`GET /api/v1/users/{id}/ledger`) or transaction history (`GET /api/v1/transactions/{id}`), violating user data privacy and banking secrecy regulations.
## Considered Options
+
1. **Method-Level SpEL Expressions (`@PreAuthorize`) Only**: Declarative Spring Security annotations using SpEL (e.g. `@PreAuthorize("#request.senderUpiId == authentication.name")`). While clean for simple rules, SpEL lacks compile-time type safety, does not easily handle multi-party data visibility (sender OR receiver), and makes unit testing without Spring context heavier.
2. **Filter-Level Role Checks Only**: Restricting endpoints purely by role (`ROLE_USER`, `ROLE_ADMIN`). Fails to enforce resource ownership because all authenticated users share `ROLE_USER`.
3. **Defense-in-Depth Principal-Bound Authorization (Chosen)**:
@@ -18,9 +21,11 @@ In a payment system, authentication (Phase 7A) establishes identity via JWT bear
- Map unauthorized attempts to `ForbiddenOperationException` (`403 Forbidden`).
## Decision Outcome
+
Chosen Option: **Defense-in-Depth Principal-Bound Authorization (Option 3)**
### Rationale
+
* **Sender Verification on Transfers**: `TransactionService.sendMoney()` asserts `authenticatedUpi.equalsIgnoreCase(request.getSenderUpiId())`. Discrepancies immediately throw `ForbiddenOperationException`, rejecting the transfer before acquiring row locks or debiting balances.
* **Multi-Party Transaction Visibility**: `TransactionService.getTransactionByReferenceId()` ensures that only the **sender** or the **receiver** can view transaction details. Third-party users receive `403 Forbidden`.
* **Double-Entry Balance Ledger Privacy**: `UserService.getUserLedger()` enforces that users can only retrieve ledger entries associated with their own `referenceId` / `upiId`.
@@ -29,5 +34,6 @@ Chosen Option: **Defense-in-Depth Principal-Bound Authorization (Option 3)**
* **Filter-Level Fallback**: `JwtAccessDeniedHandler` guarantees that any Spring Security framework-level authorization rejection also emits RFC 7807 problem details.
## Consequences
+
* **Positive**: Guaranteed protection against unauthorized transfers, data exfiltration, and account impersonation; complete test isolation with zero flaky mock requirements.
* **Negative / Trade-offs**: Requires extracting and verifying `SecurityUtils` principal across private query and mutation service methods.
diff --git a/docs/adr/0019-declarative-http-interface-client-and-resilient-external-service-integration.md b/docs/adr/0019-declarative-http-interface-client-and-resilient-external-service-integration.md
index 6f783b4..1fe26b8 100644
--- a/docs/adr/0019-declarative-http-interface-client-and-resilient-external-service-integration.md
+++ b/docs/adr/0019-declarative-http-interface-client-and-resilient-external-service-integration.md
@@ -5,17 +5,21 @@
* **Phase**: Phase 7C
## Context & Problem Statement
+
During user onboarding (`POST /api/v1/users`), Payflow must verify whether a requested UPI ID is legitimate with an external upstream banking/NPCI verification gateway. In Spring Framework, outbound HTTP communication has historically used `RestTemplate` (now in maintenance/deprecated status) or third-party libraries like Netflix Feign / Spring Cloud OpenFeign (which add heavy runtime overhead and complex dependency trees). Furthermore, third-party network calls are inherently prone to transient connection failures, timeouts, and gateway outages that must not degrade core onboarding availability.
## Considered Options
+
1. **Legacy `RestTemplate`**: Synchronous template client. Verbose, lacks fluent builder APIs, and deprecated in modern Spring Framework.
2. **Spring Cloud OpenFeign**: Declarative client annotations, but requires Spring Cloud BOM, Ribbon/LoadBalancer dependencies, and heavy reflection infrastructure.
3. **Declarative HTTP Interface Client backed by `RestClient` (Chosen)**: Native Spring Framework 6.1+ / Spring Boot 4 feature (`@HttpExchange` / `@GetExchange`) with `RestClientAdapter` and `HttpServiceProxyFactory`, combined with Spring Retry (`@Retryable` with exponential backoff, multiplier, and randomized jitter) and `@Recover` graceful degradation.
## Decision Outcome
+
Chosen Option: **Declarative HTTP Interface Client via `RestClient` + Spring Retry (Option 3)**
### Rationale
+
* **Modern Spring Standard**: `RestClient` provides a fluent, synchronous HTTP client with full access to HTTP headers, timeouts, and status handlers without legacy template boilerplate.
* **Declarative Type Safety**: `UpiValidationClient` defines contract interfaces with `@HttpExchange` and `@GetExchange`, automatically proxied by `HttpServiceProxyFactory` without boilerplate client implementations.
* **Resilience via Exponential Backoff & Jitter**: Transient network glitches and 5xx errors are retried up to 3 times with exponential backoff (`delay = 500ms`, `multiplier = 2.0`, `random = true` jitter) to mitigate thundering-herd retry storms.
@@ -23,5 +27,6 @@ Chosen Option: **Declarative HTTP Interface Client via `RestClient` + Spring Ret
* **Domain Rejection on Explicit Failure**: If the external gateway explicitly reports `valid = false`, registration is blocked with `InvalidUpiException` (`422 Unprocessable Entity`).
## Consequences
+
* **Positive**: Zero third-party Feign dependencies, modern compile-time verifiable HTTP interface contracts, automated retry resilience with jitter, and high availability onboarding through graceful fallback.
* **Negative / Trade-offs**: Requires mock server setup (`MockRestServiceServer` / `@MockitoBean`) in test suites when `payflow.upi-validation.enabled=true`.
diff --git a/docs/adr/0020-structured-logging-prometheus-metrics-and-opentelemetry-observability.md b/docs/adr/0020-structured-logging-prometheus-metrics-and-opentelemetry-observability.md
index 3083d51..f7246b7 100644
--- a/docs/adr/0020-structured-logging-prometheus-metrics-and-opentelemetry-observability.md
+++ b/docs/adr/0020-structured-logging-prometheus-metrics-and-opentelemetry-observability.md
@@ -5,12 +5,15 @@
* **Phase**: Phase 8A
## Context & Problem Statement
+
In a high-throughput financial backend, raw unstructured plain text logs and unmonitored execution paths prevent real-time incident diagnosis, SLA alerting, and end-to-end request tracing. Operating Payflow across micro-benchmarks or distributed cloud nodes requires unified telemetry covering:
+
1. Machine-readable structured logs with contextual correlation IDs (`requestId`, `traceId`, `spanId`, `userId`, `http.status`, `http.latency_ms`).
2. High-precision system and business metrics exportable to Prometheus for monitoring payment success/failure rates, transfer latency percentiles, and database connection pool saturation.
3. Vendor-agnostic distributed tracing compliant with W3C Trace Context standards.
## Considered Options
+
1. **Vendor-Specific Telemetry Agents**: Commercial proprietary agents (e.g. Datadog, New Relic) embedded in the JVM. Causes vendor lock-in, increases runtime overhead, and conflicts with container portability.
2. **Ad-hoc Custom Logging & Metric Counters**: Manual `System.currentTimeMillis()` logs and in-memory static counters. Lacks standardized scraping interfaces, histogram percentiles, and distributed trace context propagation.
3. **Unified Spring Observation API + Micrometer Prometheus & OpenTelemetry Bridge (Chosen)**:
@@ -19,9 +22,11 @@ In a high-throughput financial backend, raw unstructured plain text logs and unm
- Micrometer Tracing with OpenTelemetry bridge (`micrometer-tracing-bridge-otel`) and `@Observed` method-level instrumentation for automatic W3C `traceparent` propagation and span creation.
## Decision Outcome
+
Chosen Option: **Unified Spring Observation API with Micrometer Prometheus & OpenTelemetry Bridge (Option 3)**
### Rationale
+
* **Three Pillars of Observability**: Unifies logs, metrics, and traces into a single cohesive telemetry model via Spring's `Observation` / `ObservationRegistry`.
* **Zero Vendor Lock-in**: Standard W3C Trace Context headers and OpenTelemetry protocols (OTLP) enable seamless integration with any backend (Prometheus, Grafana Loki, Tempo, Jaeger, OpenSearch).
* **Business SLA Visibility**: Custom distribution summaries and timers compute p50, p95, and p99 percentiles for transfer amounts and execution latencies directly on Prometheus scrapes.
@@ -29,5 +34,6 @@ Chosen Option: **Unified Spring Observation API with Micrometer Prometheus & Ope
* **Environment-Adaptive Logging**: Production uses structured Elastic Common Schema (ECS) JSON for automated log aggregators; local and test profiles maintain ANSI colored human-readable logs with MDC correlation tags.
## Consequences
+
* **Positive**: Complete observability with zero vendor lock-in, automated MDC correlation across all HTTP requests, and out-of-the-box Prometheus alerting integration.
* **Negative / Trade-offs**: Requires minor memory overhead for metric meter registrations and percentile histogram bucketing.
diff --git a/docs/adr/0021-resilience4j-circuit-breaker-per-user-rate-limiting.md b/docs/adr/0021-resilience4j-circuit-breaker-per-user-rate-limiting.md
index bbaff58..214c218 100644
--- a/docs/adr/0021-resilience4j-circuit-breaker-per-user-rate-limiting.md
+++ b/docs/adr/0021-resilience4j-circuit-breaker-per-user-rate-limiting.md
@@ -5,15 +5,19 @@
* **Phase**: Phase 8B
## Context & Problem Statement
+
High-concurrency financial payment engines face two critical operational risks:
+
1. **Noisy Neighbor & DoS Starvation**: A single abusive or malfunctioning client firing thousands of requests per second can exhaust database connection pools, thread pools, and CPU cycles, denying service to legitimate participants.
2. **Cascading Downstream Failures**: When third-party external dependencies (such as external banking networks, UPI verification gateways, or upcoming AI insights services) suffer degraded performance, high latencies, or connection timeouts, callers block waiting for responses. This quickly exhausts application worker threads, causing catastrophic cascading collapse across the entire system.
Standard single-tenant rate limiting and naive retry loops fail in financial systems because:
+
- Global rate limiting allows one aggressive user to consume the entire API quota, blocking everyone else.
- Indiscriminate retries on failing downstream services produce a "retry storm" (thundering herd), driving struggling external backends into total outage.
## Considered Options
+
1. **Bucket4j with Redis**: Token bucket rate limiting backed by Redis. Provides distributed token coordination, but adds an external network dependency (Redis) for local/monolithic workloads and does not provide circuit breaking or time limiting out-of-the-box.
2. **Guava RateLimiter / Semaphore Controls**: Primitive in-memory token/permit limiters without Spring Boot integration, metrics export, circuit breaker state machines, or declarative annotations.
3. **Resilience4j Ecosystem with Dynamic Per-User Key Resolution (Chosen)**:
@@ -24,9 +28,11 @@ Standard single-tenant rate limiting and naive retry loops fail in financial sys
- `resilience4j-micrometer` exporting state gauges and call timers to Prometheus (`/actuator/prometheus`) and health indicators to `/actuator/health`.
## Decision Outcome
+
Chosen Option: **Resilience4j Ecosystem with Dynamic Per-User Key Resolution (Option 3)**
### Rationale
+
* **Per-User Fairness & Isolation**: Keying rate limiters by authenticated user principal (`alice@payflow`, `bob@payflow`) guarantees that user A exceeding 10 req/s receives HTTP 429 without impacting user B's throughput.
* **Fail-Fast Downstream Protection**: When the external UPI verification gateway fails consecutively, the circuit trips to `OPEN`, immediately shielding downstream services and returning graceful fallbacks without wasting thread pool capacity.
* **Selective Exception Filtering**: Client errors (such as `InvalidUpiException` 422 Unprocessable Entity) are classified as business rejections rather than service outages, preventing invalid user input from tripping the infrastructure circuit breaker.
@@ -34,5 +40,6 @@ Chosen Option: **Resilience4j Ecosystem with Dynamic Per-User Key Resolution (Op
* **Unified Observability**: Resilience state transitions and available permits are exported to Prometheus and Actuator health endpoints alongside HikariCP and payment transaction metrics.
## Consequences
+
* **Positive**: Complete protection against abusive client flooding, deterministic thread containment on downstream outages, RFC 6585 compliance, and full Prometheus visibility.
* **Negative / Trade-offs**: In-memory rate limiting is local to each JVM instance; multi-node clusters in Phase 8C/8D will complement this with distributed Redis-backed rate limiting and caching.
diff --git a/docs/adr/0022-redis-distributed-caching-and-caffeine-fallback.md b/docs/adr/0022-redis-distributed-caching-and-caffeine-fallback.md
index d629fc1..c179cb0 100644
--- a/docs/adr/0022-redis-distributed-caching-and-caffeine-fallback.md
+++ b/docs/adr/0022-redis-distributed-caching-and-caffeine-fallback.md
@@ -5,7 +5,9 @@
* **Phase**: Phase 8C
## Context & Problem Statement
+
Payment processing backends experience high-frequency, repetitive read queries for user profiles, UPI routing records, and recent balance ledger summaries. In standard database-backed flows:
+
1. Every payment transfer checks sender and receiver balances, account statuses, and KYC limits.
2. User dashboards and transaction polling trigger repetitive lookups against `users` and `balance_ledger` tables.
3. Hitting PostgreSQL for read-heavy lookups consumes database connection pool permits (`HikariCP`), adds disk I/O latency, and competes with write transactions (`SELECT ... FOR UPDATE` row locks).
@@ -13,11 +15,13 @@ Payment processing backends experience high-frequency, repetitive read queries f
In distributed multi-node production clusters, an in-memory-only cache causes cache incoherency (node A updates balance, but node B serves stale balance from local memory). Conversely, requiring a live Redis server for local development and unit/integration testing introduces heavyweight infrastructure dependencies and slows down developer iteration cycles.
Additionally, caching JPA domain entities introduces serialization hazards:
+
- Default Java native serialization is vulnerable to remote code execution (RCE) and requires class bytecode version matching.
- Jackson JSON serialization on Hibernate entities fails on uninitialized lazy proxies (`hibernateLazyInitializer`, `handler`) or causes infinite recursion loops across bidirectional entity graphs.
- Caching empty lookups (`Optional.empty()`) pollutes the cache with null values or causes deserialization errors.
## Considered Options
+
1. **Direct Database Reads Only (No Caching)**: Eliminates cache invalidation complexity, but subjects PostgreSQL to severe read contention under high transaction volumes (500+ TPS), degrading transfer latency.
2. **In-Memory Caffeine Cache Only (Across All Profiles)**: Ultra-fast in-process caching with zero network hops, but produces cache inconsistency in multi-pod Kubernetes deployments where pods do not share state.
3. **Redis Cache Only (Across All Profiles)**: Centralized distributed cache, but forces local developers and CI unit/slice tests to run a live Redis container or daemon, harming developer productivity.
@@ -27,35 +31,42 @@ Additionally, caching JPA domain entities introduces serialization hazards:
- Spring Cache abstraction (`@Cacheable`, `@CacheEvict`) provides unified annotations with zero domain coupling to the underlying cache provider.
## Decision Outcome
+
Chosen Option: **Profile-Conditional Hybrid Architecture (Option 4)**
### Architectural Design & Invalidation Policy
#### 1. Cache Names & Granular TTL Configuration
+
| Cache Name | Key Strategy | Default TTL | Eviction Policy | Purpose |
| :--- | :--- | :--- | :--- | :--- |
| `users` | Single key: `#id`, `#referenceId`, `#upiId` | **10 minutes** (`600s`) | All entries evicted on `registerUser()` and `sendMoney()` | Hot user profile & UPI handle resolution |
| `user_ledgers` | Compound key: `#userReferenceId + '_' + #pageable.pageNumber` | **1 minute** (`60s`) | All entries evicted on `sendMoney()` | Recent double-entry audit history pages |
#### 2. Modern JSON Serialization Strategy
+
- Keys are serialized using `RedisSerializer.string()`.
- Values are serialized using the modern Spring Data Redis `RedisSerializer.json()`, replacing the deprecated `GenericJackson2JsonRedisSerializer`.
- Value serialization is human-readable, schema-portable, and immune to Java native deserialization exploits.
#### 3. Entity Hardening for Jackson Serialization
+
- `User` and `BalanceLedgerEntry` implement `java.io.Serializable` with `serialVersionUID = 1L`.
- `BalanceLedgerEntry` is annotated with `@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})` to prevent Jackson from attempting to serialize uninitialized Hibernate lazy proxies.
- `@JsonIgnore` is applied to lazy associations (`user`, `transaction`) on `BalanceLedgerEntry` to eliminate circular reference graphs during JSON marshaling.
#### 4. Null-Safe Cache-Aside Semantics
+
- Read operations (`getUserById`, `getUserByReferenceId`, `findByUpiId`, `getUserByUpiId`, `getUserLedger`) use `@Cacheable(..., unless = "#result == null")`.
- Spring's `CacheAspectSupport` unwraps `Optional` before SpEL condition evaluation; the `#result == null` check ensures empty `Optional.empty()` results are never persisted to the cache.
#### 5. Write Eviction & Consistency
+
- `UserService.registerUser()` triggers `@CacheEvict(value = "users", allEntries = true)`.
- `TransactionService.sendMoney()` triggers `@CacheEvict(value = {"users", "user_ledgers"}, allEntries = true)` upon successful transfer commit, guaranteeing stale balances and ledger records are purged immediately.
## Consequences
+
* **Positive**:
- Sub-millisecond lookup response times on repeat user and ledger queries.
- Significant reduction (>80%) in PostgreSQL connection pool and read query pressure.
diff --git a/docs/adr/0023-redis-distributed-locking-redisson.md b/docs/adr/0023-redis-distributed-locking-redisson.md
index 1819e75..17d3c55 100644
--- a/docs/adr/0023-redis-distributed-locking-redisson.md
+++ b/docs/adr/0023-redis-distributed-locking-redisson.md
@@ -5,15 +5,18 @@
* **Phase**: Phase 8D
## Context & Problem Statement
+
In distributed multi-instance deployments (such as multiple Kubernetes pods running behind an ingress load balancer), concurrent HTTP requests bearing the identical `Idempotency-Key` header can hit different application instances in the exact same millisecond.
Prior to Phase 8D, idempotency deduplication relied on database-level unique constraints on the `idempotency_records` table (`DataIntegrityViolationException`). While database constraints guarantee data consistency at the persistence layer, this approach presents several operational limitations in high-throughput financial environments:
+
1. **Unnecessary Connection & Thread Saturation**: Both concurrent requests acquire database connection pool slots (`HikariCP`) and begin transactions before the duplicate is caught at the `INSERT` statement.
2. **Gateway-Level Coordination Gap**: Under flash-sale bursts or aggressive client retries, competing requests execute expensive upstream validations (e.g. SHA-256 payload hashing, external gateway checks) before conflict detection occurs.
3. **Deadlock & Split-Brain Risks**: If a pod crashes mid-execution while coordinating state across microservices, locks lacking fail-safe lease times risk permanent system lockup.
4. **Developer Workflow Ergonomics**: Requiring an active Redis instance for local development and CI unit tests introduces heavy infrastructure friction and slows down feedback loops.
## Considered Options
+
1. **Database Unique Constraints Only (Status Quo)**: Zero additional infrastructure dependencies, but wastes database connection permits, increases write lock contention, and fails to coordinate distributed in-memory states.
2. **Spring Integration Redis Lock (`RedisLockRegistry`)**: Provides basic Redis-backed distributed locking via Spring Integration, but lacks rich locking semantics (e.g. watchdog lease renewal, robust thread-ownership checks, async locks) and pulls in unwanted Spring Integration framework bloat.
3. **Redisson Distributed Locking with Fail-Safe Local Fallback (Chosen)**:
@@ -24,17 +27,20 @@ Prior to Phase 8D, idempotency deduplication relied on database-level unique con
- Thread-ownership verification (`isHeldByCurrentThread()`) before `unlock()` eliminates `IllegalMonitorStateException` hazards during lease timeouts.
## Decision Outcome
+
Chosen Option: **Redisson Distributed Locking with Fail-Safe Local Fallback (Option 3)**
### Architectural Design & Invalidation Policy
#### 1. Distributed Lock Interface & Components
+
- **`DistributedLockService`**: Domain service interface defining `tryLock(key, waitTime, leaseTime)`, fail-fast `tryLock(key, leaseTime)`, `unlock(key)`, and `isLocked(key)`.
- **`RedissonDistributedLockService` (`@Profile("prod")`)**: Implements Redis distributed locking via Redisson `RLock`. Restores thread interrupt state on `InterruptedException` and safely verifies `lock.isHeldByCurrentThread()` before calling `lock.unlock()`.
- **`NoOpDistributedLockService` (`@Profile("!prod")`)**: High-performance in-memory no-op implementation for `local`, `test`, and `prod-light` profiles, returning `true` for all lock attempts without external dependencies.
- **`DistributedLockConfig` (`@Profile("prod")`)**: Configures `RedissonClient` using `SingleServerConfig` with connection pool size 20, idle size 5, and timeout 3000ms.
#### 2. Idempotency Coordination Workflow
+
In `IdempotencyFilter`:
```
Client Request (POST /api/v1/transactions, Idempotency-Key: X)
@@ -50,6 +56,7 @@ Client Request (POST /api/v1/transactions, Idempotency-Key: X)
```
#### 3. Namespacing & Externalized Properties
+
- **Key Namespace**: `payflow:lock:idemp:`
- **Configuration (`application.yml`)**:
```yaml
@@ -60,6 +67,7 @@ Client Request (POST /api/v1/transactions, Idempotency-Key: X)
```
## Consequences
+
* **Positive**:
- Eliminates concurrent duplicate request collisions across distributed application instances before database connection allocation.
- Bounded 2-second wait time allows split-second duplicate requests to wait and receive the 201 cached replay response rather than immediately failing with conflict.
diff --git a/docs/adr/0024-kafka-event-streaming-spring-modulith.md b/docs/adr/0024-kafka-event-streaming-spring-modulith.md
index 98198df..ca0f066 100644
--- a/docs/adr/0024-kafka-event-streaming-spring-modulith.md
+++ b/docs/adr/0024-kafka-event-streaming-spring-modulith.md
@@ -5,11 +5,13 @@
* **Phase**: Phase 9A
## Context & Problem Statement
+
In a distributed financial architecture, completed monetary transactions trigger a multitude of downstream business workflows, including fraud detection, notification dispatching, regulatory audit logging, and spend analytics.
Prior to Phase 9A, Payflow API utilized Spring Modulith's Event Publication Registry (introduced in Phase 6B) for in-process asynchronous domain event publication (`TransferCompletedEvent`). While in-process `@ApplicationModuleListener` listeners provide transactional durability via the database outbox table (`event_publication`), they operate entirely within a single application instance.
To scale out independent downstream microservices and data pipelines, these domain events must be externalized to a distributed messaging backbone (**Apache Kafka**). However, naive event publishing introduces significant distributed systems risks:
+
1. **Dual-Write Vulnerability**: If application code writes to PostgreSQL and directly calls `KafkaTemplate.send()` within `@Transactional`, network partitions or broker latency can lead to uncommitted database changes broadcasting phantom messages, or committed transactions failing to publish events.
2. **Domain Service Code Pollution**: Injecting messaging infrastructure (`KafkaTemplate`, topic names, serializers) into core financial services (`TransactionService`) violates single responsibility and clean architecture principles.
3. **Partition Ordering Violations**: Without deterministic message partitioning, transactions from the same account could be distributed across different Kafka partitions, breaking chronological ordering and ledger auditability for downstream consumers.
@@ -17,6 +19,7 @@ To scale out independent downstream microservices and data pipelines, these doma
5. **Local Developer Friction**: Forcing every developer and CI unit test run to maintain an active Kafka broker drastically slows down feedback loops and increases onboarding complexity.
## Considered Options
+
1. **Direct Kafka Publishing (`KafkaTemplate` in Service Layer)**:
- *Pros*: Simple to implement initially.
- *Cons*: Vulnerable to dual-write failures; breaks transactional atomicity between database state and event bus; tightly couples domain logic to Kafka.
@@ -35,11 +38,13 @@ To scale out independent downstream microservices and data pipelines, these doma
- Profile-conditional: Enabled in `prod` and `kafka` profiles (via dedicated `application-kafka.yml`), while disabled in `local`, `test`, and `prod-light` (`spring.modulith.events.externalization.enabled: false`), allowing non-prod environments to execute lightweight in-process event listeners without a Kafka broker.
## Decision Outcome
+
Chosen Option: **Spring Modulith Event Externalization (Option 4)**
### Architectural Design & Mechanics
#### 1. Domain Event & Dynamic Topic Routing
+
In `TransferCompletedEvent.java`:
```java
@Externalized
@@ -66,10 +71,12 @@ public EventExternalizationConfiguration eventExternalizationConfiguration(
.build();
}
```
+
- **Unified Topic Resolution**: Both the `NewTopic` bean and the event externalizer resolve `${payflow.kafka.transfers-topic}`, ensuring topic creation and routing targets are always synchronized.
- **Partition Key**: `senderUpi`. Guarantees that all transfer events initiated by a given user are written to the exact same Kafka partition, preserving strict chronological ordering.
#### 2. Transactional Outbox Flow
+
```
Client Request (POST /api/v1/transactions)
├── 1. Acquire DB Row Locks (Pessimistic Write)
@@ -85,6 +92,7 @@ Client Request (POST /api/v1/transactions)
```
#### 3. Infrastructure, Replication & Delivery Guarantees
+
- **Topic Configuration (`KafkaConfig.java`)**:
- `@Profile({"prod", "kafka"})`
- Partitions: Configurable via `payflow.kafka.topic-partitions` (default 3).
@@ -105,6 +113,7 @@ Client Request (POST /api/v1/transactions)
## Consequences
### Positive Outcomes
+
* **Zero Dual-Write Hazard**: Messages are only published to Kafka if and only if the underlying database transaction successfully commits.
* **Domain Purity**: Not a single line of Kafka-specific code exists within `TransactionService` or domain entities.
* **Deterministic In-Order Consumption**: Partitioning by sender UPI guarantees that all debit events for an account maintain strict timeline integrity.
@@ -112,5 +121,6 @@ Client Request (POST /api/v1/transactions)
* **Production HA Compatibility**: Configurable replication factor ensures topics created in multi-broker production clusters satisfy high availability requirements.
### Trade-offs & Mitigations
+
* **At-Least-Once Duplicate Handling**: If a crash occurs prior to updating the outbox table, republishing can produce duplicates across process restarts. *Mitigation*: Downstream consumer services must maintain an idempotent inbox table keying on `referenceId`.
* **Outbox Storage Growth**: High transfer volumes increase rows in `event_publication`. *Mitigation*: Spring Modulith's automated completion cleaner periodically purges completed events.
diff --git a/docs/adr/0025-production-hardening-and-architecture-audit-remediation.md b/docs/adr/0025-production-hardening-and-architecture-audit-remediation.md
index 3768c37..4f66baa 100644
--- a/docs/adr/0025-production-hardening-and-architecture-audit-remediation.md
+++ b/docs/adr/0025-production-hardening-and-architecture-audit-remediation.md
@@ -10,32 +10,42 @@ Following an end-to-end Principal Engineer architectural and code audit of the P
1. **Production JWT Secret Fallback Vulnerability (`SEC-01`)**:
In `JwtTokenProvider`, if the `PAYFLOW_SECURITY_JWT_SECRET` environment variable was omitted in a production environment, the application silently fell back to a hardcoded, publicly visible test secret, allowing attackers to forge arbitrary user and admin tokens.
+
2. **Broken Object Level Authorization & Balance Enumeration (`SEC-02`)**:
Endpoints `GET /api/v1/users` (bulk user directory) and `GET /api/v1/users/balance/{amount}` (balance threshold querying) lacked administrative role constraints, permitting any authenticated standard user to harvest customer profiles, phone numbers, and account balances.
+
3. **Transport & Frame Security Exposures (`SEC-03`, `SEC-04`)**:
Spring Security configuration allowed credential inclusion (`allowCredentials = true`) on wildcard origins (`allowedOrigins = *`), violating the Fetch/CORS specification and opening credential exposure risks. Additionally, `frameOptions().disable()` disabled clickjacking defenses globally.
+
4. **Spring 6.1+ / Spring 7 Parameter Validation Handling (`ARCH-02`)**:
Method parameter validation violations (`@Min`, `@Max`, `@PathVariable`, `@RequestParam`) in Spring Framework 6.1+ / 7 throw `HandlerMethodValidationException` rather than `MethodArgumentNotValidException`, which resulted in generic unhandled 500 errors instead of standardized RFC 9457 `ProblemDetail` responses.
+
5. **Unbounded Idempotency Key Injection (`ARCH-03`)**:
The `Idempotency-Key` header was accepted without length or character constraints, allowing arbitrarily large strings to cause database column overflows (`VARCHAR(255)`) or resource exhaustion in distributed locks.
+
6. **Unbounded Growth in Transactional Outbox Registry (`ARCH-04`)**:
Spring Modulith persists completed event publications to the `event_publication` table. Without an automated maintenance job, this table grows indefinitely under high transfer volumes, degrading query performance and exhausting database storage.
+
7. **Cache Stampede via Blanket Eviction (`PERF-01`)**:
On every transfer, `@CacheEvict(value = {"users", "user_ledgers"}, allEntries = true)` wiped all user profiles and ledger histories across the entire application, causing heavy thundering-herd database queries under concurrent traffic.
+
8. **Integration Test Execution Blindspot (`TEST-01`, `TEST-02`)**:
`pom.xml` lacked `maven-failsafe-plugin`, causing all containerized integration tests (`*IT.java` covering concurrency, deadlock avoidance, and Kafka outbox delivery) to be bypassed during `mvn test` and CI builds. Furthermore, repository slice tests (`@DataJpaTest`) were absent for `TransactionRepository` and `IdempotencyRepository`.
## Considered Options
### For Cache Eviction:
+
- **Option A (Global Invalidation)**: Continue using `@CacheEvict(allEntries = true)`. Simple, but causes massive cache stampedes and database churn in production.
- **Option B (Targeted Invalidation — Chosen)**: Programmatically evict only keys belonging to the transaction participants (`sender` and `receiver` by `id`, `upiId`, and `referenceId`, plus sender/receiver ledger caches) via `CacheManager`. Unrelated user cache entries remain untouched.
### For Outbox Cleanup:
+
- **Option A (Custom SQL Cron)**: Run custom native SQL `DELETE FROM event_publication WHERE completion_date < ...`. Bypasses Spring Modulith abstraction and couples cleanup to vendor-specific table schemas.
- **Option B (Spring Modulith CompletedEventPublications — Chosen)**: Inject `CompletedEventPublications` from `spring-modulith-events-api` and invoke `deletePublicationsOlderThan(Duration.ofDays(7))` via a scheduled `@Transactional` job at 02:00 AM UTC.
### For Production JWT Protection:
+
- **Option A (Rely on Deployment Checklist)**: Hope operators set `PAYFLOW_SECURITY_JWT_SECRET`. Fragile and prone to human error.
- **Option B (Fail-Fast Environment Invariant Check — Chosen)**: Inject `Environment` into `JwtTokenProvider` constructor. When `environment.matchesProfiles("prod")`, verify secret is non-null, distinct from `DEFAULT_SECRET`, and $\ge$ 256 bits (32 chars); throw `IllegalStateException` on bootstrap otherwise.
@@ -64,6 +74,7 @@ We selected the production-grade options across all audit dimensions:
## Consequences
### Positive
+
- **Zero Authentication Bypass**: Production deployment cannot start with insecure default JWT secrets.
- **Zero BOLA Vulnerability**: Directory scraping and balance enumeration are strictly blocked for non-admin tokens.
- **Stable Cache Hit Ratio**: Transfers no longer wipe cached sessions of unrelated users.
@@ -71,5 +82,6 @@ We selected the production-grade options across all audit dimensions:
- **Guaranteed Integration Verification**: All Testcontainers tests run consistently in standard Maven verify lifecycles.
### Negative / Trade-offs
+
- Targeted cache eviction requires explicit coordination of multiple key formats (`upi:`, `ref:`, `id:`) rather than a single blanket eviction annotation.
- Administrative operations require minting admin tokens with `ROLE_ADMIN` authority in test environments (`@WithMockUser(roles = "ADMIN")`).
diff --git a/docs/adr/0026-gen-ai-spend-categorization-spring-ai.md b/docs/adr/0026-gen-ai-spend-categorization-spring-ai.md
index 4acac3f..63316e8 100644
--- a/docs/adr/0026-gen-ai-spend-categorization-spring-ai.md
+++ b/docs/adr/0026-gen-ai-spend-categorization-spring-ai.md
@@ -9,6 +9,7 @@
Modern peer-to-peer (P2P) payment and digital wallet platforms provide end-users with contextual financial intelligence, automated transaction categorization, and actionable budgeting recommendations. As transaction volume scales, users require visibility into their spending patterns across common categories (Food & Dining, Shopping, Travel & Transit, Utilities, Entertainment, Healthcare, Investments, and Transfers).
Integrating Large Language Models (LLMs) to analyze unstructured transaction metadata presents key architectural challenges in a mission-critical financial backend:
+
1. **Third-Party Latency & Availability**: External LLM inference APIs introduce variable latency (typically 500ms to 3000ms) and are susceptible to transient network timeouts, HTTP 429 rate-limiting, and downstream outages. Core payment engine availability must never depend on third-party AI availability.
2. **Schema Integrity & Hallucination**: Financial responses require deterministic structures with strict type safety, validated numeric confidence scores, and clean separation between raw LLM outputs and domain entities.
3. **Operational Cost & Configuration**: Self-hosted or cloud deployments may not wish to incur LLM API token expenses by default, requiring feature flag toggling and graceful zero-dependency fallback.
@@ -16,14 +17,17 @@ Integrating Large Language Models (LLMs) to analyze unstructured transaction met
## Considered Options
### Option A: Ad-Hoc REST Client Calls to OpenAI Endpoints
+
- **Pros**: Minimal new dependency overhead; standard `RestClient` or `WebClient` usage.
- **Cons**: High manual prompt engineering boilerplate; brittle JSON string deserialization; tight vendor lock-in to OpenAI API schemas; lacking standardized model switching (e.g., Anthropic, Ollama, Azure OpenAI).
### Option B: Python/FastAPI Sidecar Microservice
+
- **Pros**: Native Python AI ecosystem (LangChain, LlamaIndex).
- **Cons**: Violates Payflow's Spring Modulith architectural boundary (ADR-0008); adds network hop overhead, serialization latency, multi-runtime operational complexity, and separate deployment lifecycles.
### Option C: Spring AI 2.0.1 GA Fluent ChatClient with Resilience4j Circuit Breaker and Deterministic Heuristic Fallback (Chosen)
+
- **Pros**:
- Native Spring ecosystem integration using `spring-ai-starter-model-openai` (Spring AI 2.0.1 GA).
- Fluent `ChatClient` API with automatic structured entity conversion (`.call().entity(SpendInsightResponse.class)`).
@@ -62,10 +66,12 @@ We adopted **Option C**. The architectural implementation consists of:
## Consequences
### Positive
+
- **Guaranteed High Availability**: If the AI model times out or encounters rate limits, requests instantly fallback to rule-based insights without user-facing HTTP 500 errors.
- **Type-Safe Contract**: Responses conform strictly to `SpendInsightResponse` schema with transparent metadata indicator (`source = "AI_MODEL"` vs `"RULE_BASED_FALLBACK"`).
- **Zero Cost When Disabled**: Default configuration `payflow.ai.enabled: false` ensures zero unexpected external API calls during standard test suites or air-gapped environments.
- **Vendor Agility**: Spring AI's `ChatClient` decouples application logic from specific model providers, allowing seamless migration to local Ollama or cloud models in future phases.
### Trade-offs & Mitigations
+
- **Fallback Accuracy**: Keyword heuristics produce lower confidence scores (0.70 to 0.85) compared to generative reasoning (0.90 to 0.98). Mitigated by explicitly returning `confidenceScore` and `source` in the response payload for consumer transparency.
diff --git a/docs/adr/0027-virtual-threads-and-bounded-connection-pool.md b/docs/adr/0027-virtual-threads-and-bounded-connection-pool.md
index e3d12f8..0594bf4 100644
--- a/docs/adr/0027-virtual-threads-and-bounded-connection-pool.md
+++ b/docs/adr/0027-virtual-threads-and-bounded-connection-pool.md
@@ -9,20 +9,24 @@
Payflow API runs on Java 25 LTS with Spring Boot 4.1. Traditional enterprise Java applications allocate an operating system (OS) platform thread for each concurrent HTTP request (thread-per-request model). Each platform thread consumes approximately 1 MB of stack memory and incurs CPU kernel scheduling overhead during context switching. When threads block on I/O operations—such as PostgreSQL database transactions, Redis lookups, or external REST API calls—platform threads remain pinned and idle, limiting concurrent request capacity.
However, migrating to Project Loom Virtual Threads (`java.lang.VirtualThread`) introduces specific operational considerations in a transactional payment system:
+
1. **Virtual Threads Do Not Scale Database Connections**: While millions of virtual threads can be spawned with negligible memory overhead (~1 KB per virtual thread), downstream relational databases (PostgreSQL) cannot sustain unbounded concurrent connections. Unchecked connection requests cause HikariCP pool starvation, excessive thread checkout latency, and database CPU context-switching thrashing.
2. **Resource Constraints in Small-Footprint Deployments**: Many cloud edge nodes, branch gateways, and resource-constrained environments operate with strict memory limits (e.g., 1 GiB RAM). Running full distributed infrastructure (distributed Redis clusters and multi-broker Apache Kafka clusters) in such environments introduces unacceptable memory pressure and operational cost.
## Considered Options
### Option A: Retain Traditional OS Platform Threads with Tomcat Pool Tuning
+
- **Pros**: Established operational baseline; well-understood thread dump analysis.
- **Cons**: High memory consumption under concurrent load; limited concurrency ceiling (~200 concurrent requests before thread exhaustion); inefficient resource utilization during blocking I/O.
### Option B: Migrate to Asynchronous Reactive Architecture (Spring WebFlux / R2DBC)
+
- **Pros**: Non-blocking I/O with small, fixed thread pools.
- **Cons**: Requires complete architectural rewrite; breaks compatibility with Spring Data JPA, Hibernate ORM, and Spring Security thread-local contexts; significantly elevates code complexity and cognitive overhead for standard banking domain logic.
### Option C: Enable Virtual Threads Globally with Bounded HikariCP Sizing and a Dedicated `prod-light` Profile (Chosen)
+
- **Pros**:
- Leverages Java 25 LTS Project Loom virtual threads natively via `spring.threads.virtual.enabled: true`.
- Retains idiomatic synchronous, blocking programming model with full JPA/Hibernate and Spring Security compatibility.
@@ -53,10 +57,12 @@ We adopted **Option C**. The architectural enhancements comprise:
## Consequences
### Positive
+
- **High Concurrency Throughput**: Virtual threads decouple concurrency limits from OS thread counts, servicing thousands of concurrent requests with near-zero thread overhead.
- **PostgreSQL Stability**: Bounded HikariCP pools prevent connection exhaustion and protect database CPU utilization.
- **Low Footprint Agility**: The `prod-light` profile enables enterprise-grade deployments on 1 GiB single-node instances with under 512 MB JVM heap requirements.
- **Full Backward Compatibility**: Business logic, JPA repositories, and declarative transaction management remain 100% standard synchronous Java.
### Trade-offs & Mitigations
+
- **Carrier Thread Pinning**: Synchronized blocks holding I/O operations can pin carrier threads. Mitigated in Java 25 where runtime pinning has been significantly minimized, and all internal Payflow services employ `ReentrantLock` or non-synchronized primitives.
diff --git a/docs/adr/0028-multi-stage-containerization-docker-compose.md b/docs/adr/0028-multi-stage-containerization-docker-compose.md
index 9a933cd..b853cf9 100644
--- a/docs/adr/0028-multi-stage-containerization-docker-compose.md
+++ b/docs/adr/0028-multi-stage-containerization-docker-compose.md
@@ -13,14 +13,17 @@ Traditional single-stage Docker images carry build dependencies (JDK, Maven, bui
## Considered Options
### Option A: Single-Stage JDK Base Image Running as Root
+
- **Pros**: Minimal Dockerfile complexity.
- **Cons**: Severe security vulnerability (container breakout risks as root); image sizes exceed 850 MB; includes unnecessary compiler, build tools, and transient package managers in production.
### Option B: JLink-Based Custom Minimal Runtime via Alpine Linux
+
- **Pros**: Extremely tiny container footprint (~80 MB).
- **Cons**: High build complexity; glibc/musl compatibility issues with modern dynamic libraries; non-trivial debugging and maintenance overhead with Java 25 previews.
### Option C: Multi-Stage Build with Eclipse Temurin 25 JRE, Non-Root User, and Full-Stack Compose (Chosen)
+
- **Pros**:
- Clear separation of concerns: Stage 1 (`eclipse-temurin:25-jdk`) builds the executable fat JAR using layer caching; Stage 2 (`eclipse-temurin:25-jre`) provides a lean, hardened runtime (~250 MB).
- Enforces least privilege: Executes under dedicated unprivileged non-root system user (`payflow:10001`).
@@ -53,9 +56,11 @@ We adopted **Option C**. Key implementation highlights include:
## Consequences
### Positive
+
- **Reduced Attack Surface**: No compiler, package cache, or root privileges exist in the runtime container.
- **Predictable Garbage Collection**: Generational ZGC provides sub-millisecond GC pauses across container memory allocations.
- **One-Command Environment Launch**: Developers can execute `docker compose up -d` to bring up the entire distributed banking infrastructure with health-checked dependency startup.
### Trade-offs & Mitigations
+
- **Base Image Updates**: Minor base image patches must be audited periodically. Automated GitHub Dependabot and CI workflows verify base image freshness.
diff --git a/docs/adr/0029-kubernetes-deployment-topology-hpa.md b/docs/adr/0029-kubernetes-deployment-topology-hpa.md
index a0faffa..7bed062 100644
--- a/docs/adr/0029-kubernetes-deployment-topology-hpa.md
+++ b/docs/adr/0029-kubernetes-deployment-topology-hpa.md
@@ -9,6 +9,7 @@
Production payment systems must guarantee high availability (99.99%), zero-downtime rolling updates, automated resilience against pod/node failures, and dynamic elasticity to handle unpredictable payment traffic spikes (e.g., peak salary credit days or festive shopping sales).
Deploying bare-metal or single-instance application containers risks service disruptions during deployments or traffic surges. The operational architecture required a standard cloud-native Kubernetes deployment topology incorporating:
+
1. Declarative separation between configuration (`ConfigMap`), credentials (`Secret`), and workload lifecycle (`Deployment`).
2. Coordination of graceful shutdown to prevent dropping in-flight HTTP transactions during rolling redeployments or scale-down events.
3. Proactive health probe integration distinguishing between pod startup, deadlocks (liveness), and traffic readiness (readiness).
@@ -17,14 +18,17 @@ Deploying bare-metal or single-instance application containers risks service dis
## Considered Options
### Option A: Static Replica Deployment without Probes or HPA
+
- **Pros**: Simplest manifest configuration.
- **Cons**: Prone to downtime during rolling updates (traffic routed to non-ready pods); unable to adapt to transaction bursts; manual operator intervention required for scaling.
### Option B: Cloud-Managed Serverless Container Platform (e.g., AWS Fargate / Google Cloud Run)
+
- **Pros**: Fully managed infrastructure; automatic scaling to zero.
- **Cons**: High cold-start latencies for JVM applications; vendor lock-in; inflexible networking constraints with stateful backing databases and Apache Kafka clusters.
### Option C: Declarative Kubernetes Topology with Rolling Updates, Graceful PreStop Hooks, HPA, and PDB (Chosen)
+
- **Pros**:
- Cloud-agnostic and portable across any certified Kubernetes cluster (EKS, GKE, AKS, OpenShift, or local Minikube/Kind).
- High availability via zero-downtime `RollingUpdate` (`maxSurge: 1`, `maxUnavailable: 0`).
@@ -54,9 +58,11 @@ We adopted **Option C**. The architecture includes:
## Consequences
### Positive
+
- **Zero-Downtime Deployments**: New pods achieve readiness verification prior to old pod retirement.
- **Traffic Burst Protection**: HPA automatically expands application capacity under sudden high-volume UPI transaction surges.
- **Transaction Safety**: Graceful shutdown coordination eliminates dropped connections and incomplete transactions during pod cycling.
### Trade-offs & Mitigations
+
- **Deployment Duration**: The 10s pre-stop sleep intentionally extends rolling update completion time by a few seconds; this trade-off is essential for 100% transaction integrity.
diff --git a/docs/adr/0030-ci-cd-quality-gates-jacoco-spotbugs.md b/docs/adr/0030-ci-cd-quality-gates-jacoco-spotbugs.md
index 0762d59..19721ba 100644
--- a/docs/adr/0030-ci-cd-quality-gates-jacoco-spotbugs.md
+++ b/docs/adr/0030-ci-cd-quality-gates-jacoco-spotbugs.md
@@ -9,6 +9,7 @@
Maintaining the structural integrity, security, and correctness of an enterprise transactional banking system requires strict, automated quality gates integrated into the developer feedback loop and CI/CD pipelines.
Without enforced quality gates, regression bugs, unhandled null references, concurrency hazards, and code coverage decay easily slip through peer reviews. Specifically, the engineering team required:
+
1. Automated static code analysis detecting bug patterns, serialization risks, and security flaws at build time.
2. Automated code coverage enforcement requiring high thresholds on domain and transactional logic before code can be merged into `main`.
3. Automated artifact archiving allowing developers to inspect coverage reports directly within pull requests.
@@ -16,14 +17,17 @@ Without enforced quality gates, regression bugs, unhandled null references, conc
## Considered Options
### Option A: Manual Code Review and Spot Auditing
+
- **Pros**: Zero initial configuration overhead.
- **Cons**: High cognitive load on reviewers; inconsistent enforcement; vulnerable to human oversight on edge cases.
### Option B: External SaaS Quality Platforms (e.g., SonarQube Cloud)
+
- **Pros**: Rich web UI and historical metrics tracking.
- **Cons**: Adds external SaaS dependency, API token management, and network latency to CI builds; potential enterprise firewall constraints.
### Option C: Native Maven Verification Pipeline with SpotBugs and JaCoCo Plugins (Chosen)
+
- **Pros**:
- Zero external SaaS dependencies: Everything runs natively within the standard `mvn clean verify` lifecycle.
- SpotBugs Static Analysis (`spotbugs-maven-plugin:4.10.4.1`): Audits Java 25 bytecode for null-pointer paths, unclosed resources, mutable exposures, and constructor leak vulnerabilities. Filtered via `spotbugs-exclude.xml` for generated MapStruct mappers and compatibility stubs.
@@ -58,9 +62,11 @@ We adopted **Option C**. The quality enforcement system consists of:
## Consequences
### Positive
+
- **Guaranteed Coverage Baseline**: Pull requests failing to meet the 80% line or 70% branch threshold are automatically blocked by CI.
- **Proactive Bug Prevention**: SpotBugs detects concurrency, null-pointer, and resource leaks during local compilation before committing.
- **Standardized Developer Workflow**: Running `mvn clean verify` locally produces identical validation results to the GitHub Actions CI environment.
### Trade-offs & Mitigations
+
- **Branch Coverage Sensitivity**: Writing idiomatic Java with complex branching requires corresponding unit test scenarios. We maintain dedicated unit test suites (`JwtAuthenticationFilterTest`, `IdempotencyFilterTest`, `UserControllerTest`, `SecurityUtilsTest`) covering all conditional branches.
diff --git a/docs/coverage.md b/docs/coverage.md
index 21395d8..a620db6 100644
--- a/docs/coverage.md
+++ b/docs/coverage.md
@@ -30,11 +30,11 @@ The complete, class-by-class interactive JaCoCo coverage report is automatically
-> [!NOTE]
-> Every page within the interactive JaCoCo report features an integrated, persistent top navigation header allowing seamless single-click bidirectional navigation:
->
-> - `← Back to Documentation Portal`: Returns directly to the root documentation homepage.
-> - `📊 Coverage Quality Gates`: Returns directly to this Quality Gate & Thresholds summary page.
+!!! note "Seamless Navigation"
+ Every page within the interactive JaCoCo report features an integrated, persistent top navigation header allowing seamless single-click bidirectional navigation:
+
+ - **← Back to Documentation Portal**: Returns directly to the root documentation homepage.
+ - **📊 Coverage Quality Gates**: Returns directly to this Quality Gate & Thresholds summary page.
---
@@ -74,7 +74,9 @@ Following industry best practices ([ADR-030](adr/0030-ci-cd-quality-gates-jacoco
link.href = "/coverage-report/";
}
}
- if (document.readyState === "loading") {
+ if (typeof document$ !== "undefined") {
+ document$.subscribe(adjustCoverageLink);
+ } else if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", adjustCoverageLink);
} else {
adjustCoverageLink();
diff --git a/docs/index.md b/docs/index.md
index 180e8a1..b2afe3f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -151,6 +151,7 @@ service -> ai_resilience.r4j: "Rate Limiting Guard"
# Start server with active 'local' profile (H2 in-memory, port 8080)
mvn spring-boot:run
```
+
- **Swagger UI Interactive Docs**: [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html)
- **OpenAPI 3.0 JSON Specification**: [http://localhost:8080/v3/api-docs](http://localhost:8080/v3/api-docs)
- **H2 Console**: [http://localhost:8080/h2-console](http://localhost:8080/h2-console) (`jdbc:h2:mem:payupidb`, Credentials: `user` / `user`)
@@ -160,6 +161,7 @@ service -> ai_resilience.r4j: "Rate Limiting Guard"
# Spin up complete distributed stack (Postgres, Redis, Kafka, Ollama, Prometheus, Grafana)
docker compose up -d --build
```
+
- **Payflow API**: [http://localhost:8080](http://localhost:8080)
- **Prometheus**: [http://localhost:9090](http://localhost:9090)
- **Grafana**: [http://localhost:3000](http://localhost:3000) (Credentials: `admin` / `admin`)
@@ -170,15 +172,17 @@ service -> ai_resilience.r4j: "Rate Limiting Guard"
# Deploy declarative manifests to Kubernetes cluster
kubectl apply -f k8s/
```
- - ConfigMap, Secrets, Deployment with rolling updates
- - Horizontal Pod Autoscaler (HPA) min 2, max 10
- - Pod Disruption Budget (PDB) minAvailable: 1
+
+ - **Cluster Workloads**: ConfigMap, Secrets, and Deployment with rolling update strategy
+ - **Horizontal Pod Autoscaler (HPA)**: Min 2, max 10 replicas based on 70% CPU / 80% Memory
+ - **High Availability**: Pod Disruption Budget (PDB) with `minAvailable: 1`
=== "Quality Verification Pipeline"
```bash
# Execute full verification pipeline: Spotless, Checkstyle, SpotBugs, Tests & JaCoCo gates
mvn clean verify -DskipITs
```
+
- **Spotless**: Code formatting verification
- **Checkstyle**: Enterprise static code linting
- **SpotBugs**: Bytecode bug pattern analysis
diff --git a/docs/javascripts/extra.js b/docs/javascripts/extra.js
index 216c3d1..9c35ba4 100644
--- a/docs/javascripts/extra.js
+++ b/docs/javascripts/extra.js
@@ -1,7 +1,7 @@
/* ==========================================================================
Payflow API — MkDocs Material Interactive Enhancements
- Mermaid diagram rendering, dynamic theme synchronization, interactive zoom/pan,
- and fullscreen modal viewer.
+ Diagram zoom/pan controls, fullscreen modal viewer, dynamic theme sync,
+ and instant navigation lifecycle hooks.
========================================================================== */
(function () {
@@ -23,10 +23,10 @@
// Ensure modal element exists
function ensureModal() {
- let modal = document.getElementById("mermaid-fullscreen-modal");
+ let modal = document.getElementById("diagram-fullscreen-modal");
if (!modal) {
modal = document.createElement("div");
- modal.id = "mermaid-fullscreen-modal";
+ modal.id = "diagram-fullscreen-modal";
modal.className = "mermaid-modal";
modal.innerHTML = `