High-throughput payment gateway orchestrator built with Java 21, Spring Boot 3, Redis, and PostgreSQL.
Engineered to eliminate double-charging, network partition loss, and ledger state anomalies.
Table of Contents
- Overview
- System Design & Core Modules
- 1. Concurrency: Redis Distributed Mutex Lock
- 2. Idempotency: Double-Checked Locking
- 3. Messaging: Transactional Outbox Pattern
- 4. Sharding: Consistent Hashing 360° Ring Router
- 5. Integrity: Double-Entry Bookkeeping Ledger
- 6. Traffic: Sliding-Window Rate Limiter
- 7. Recovery: Automated Reconciliation Engine
- Architecture & Transaction Lifecycle
- Quickstart (Local Docker Setup)
- API Reference & Usage
- Operator Console (Frontend)
- Verification & Test Suite
LoomPay is an open architecture payment engine modeled after enterprise payment orchestrators (such as Hyperswitch and Stripe). When processing millions of transactions, financial systems encounter race conditions, network partition timeouts, and double-swipes.
LoomPay provides a composable, modular backend in Java 21 to solve these distributed systems challenges:
- Eliminates Double Charges: Serializes concurrent requests using atomic Redis distributed locks and idempotency keys.
- Guarantees Zero-Loss Webhooks: Decouples external network delivery from database mutations using the Transactional Outbox Pattern.
- Balances Merchant Workloads: Partitions traffic across processing nodes using a 360° Consistent Hashing ring with virtual replicas.
- Maintains Audit-Proof Balances: Double-entry bookkeeping ledger ensures debits and credits stay balanced down to the exact paisa.
- Self-Healing Reconciliation: Background cron scheduler rescues dangling
PROCESSINGtransactions during network interruptions.
- Mechanism: Implemented via Redis
SET key uuid NX EX 5. - Atomic Release: Uses a custom Lua script that validates UUID ownership before releasing the key, preventing accidental unlock if processing exceeds TTL.
- System Design Role: Protects against concurrent payment taps from impatient users or automated retry bursts without locking the entire relational database.
- Pattern: Checks whether an
idempotencyKeyalready exists before acquiring the Redis lock, and checks again immediately after acquiring it. - Safe Return: If an identical key was already submitted, returns the existing cached
PaymentResponsewith200 OKrather than authorizing a duplicate transaction.
- The Problem Solved: Eliminates the "Dual-Write" distributed transaction problem where a DB commit succeeds but an external webhook or Kafka dispatch fails.
- Execution: Saves the
PaymentOrderand anOutboxEvent(status = PENDING) inside the exact same ACID database transaction. - Worker: A background
@Scheduled(fixedDelay = 5000)worker polls pending events in batches using non-blocking pagination (Pageable), dispatches them, and quarantines poisoned records after 3 failed retries.
-
Mechanism: Built on an in-memory
TreeMap<Integer, String>representing a 360° hash ring. -
Hotspot Prevention: Each physical server receives
$N$ virtual replicas (server#0,server#1,server#2) scattered across the ring. -
Routing: Hashes incoming merchant IDs and walks clockwise using
tailMap().firstKey()to find the nearest server in$O(\log M)$ time. -
Zero Downtime: When a server joins or leaves, only
$K / N$ keys are relocated, preventing total cache invalidation.
- Immutable Accounting: Financial balances are never modified with simple
balance = balance + amountqueries. - Debit/Credit Pairing: Every successful transaction generates two immutable rows in
ledger_entries:DEBITfromCustomer_AccountCREDITtoMerchant_Account
- Integrity Invariant: Sum of all debits must equal sum of all credits across the entire ledger.
- Mechanism: Distributed sliding-window log using Redis Sorted Sets (
ZSET) where scores represent timestamps and elements are purged viaremoveRangeByScoreto prevent boundary burst anomalies. - Enforcement: Limits incoming requests per
X-Merchant-Id(default: 5 requests per 10-second sliding window). - HTTP 429 Response: Excess traffic is dropped at the Spring
HandlerInterceptoredge withHTTP 429 Too Many Requestsand a standardRetry-After: 10header.
- Cron Worker: A background scheduler (
@Scheduled(fixedDelay = 60000)) searches for transactions stuck inPROCESSINGstatus older than 5 minutes. - Resolution: Reconciles dangling states with upstream mock gateways and updates timed-out records to
FAILED, releasing temporary holds.
Customer Request (POST /api/v1/payments)
│
▼
┌───────────────────────────┐
│ RateLimitInterceptor │ ──(Exceeded?)──► HTTP 429 Too Many Requests
└───────────────────────────┘
│ (Allowed)
▼
┌───────────────────────────┐
│ ConsistentHashRouter │ ──► Routes to designated worker node
└───────────────────────────┘
│
▼
┌───────────────────────────┐
│ DistributedLockService │ ──► Acquires Redis Mutex (SET NX EX 5)
└───────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ SINGLE ACID DATABASE TRANSACTION │
│ │
│ 1. Check Idempotency Key │
│ 2. Insert PaymentOrder (STATUS = CREATED) │
│ 3. Insert Ledger Entries (Debit Customer, Credit Merch)│
│ 4. Insert OutboxEvent (STATUS = PENDING) │
└────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────┐
│ Release Redis Mutex │ ──► Atomic Lua Script verification
└───────────────────────────┘
│
▼
Return HTTP 201 Created
│
▼
┌────────────────────────────────────────────────────────┐
│ ASYNC BACKGROUND WORKERS │
│ │
│ ► OutboxPublisherService: Polls & Relays Webhook / MQ │
│ ► ReconciliationService: Rescues Stuck Orders (>5m) │
└────────────────────────────────────────────────────────┘
- Java 21
- Docker & Docker Compose
Clone the repository and run the full stack:
# Clone repository
git clone https://github.com/abhiramaab/loompay.git
cd loompay
# 1. Spin up PostgreSQL 16 & Redis 7
docker compose up -d
# 2. Run the Spring Boot application
./mvnw spring-boot:runThe service will start on http://localhost:8081.
Executes an idempotent, rate-limited payment swipe:
curl -i -X POST http://localhost:8081/api/v1/payments \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: merchant_nike" \
-d '{
"merchantId": "merchant_nike",
"amount": 499900,
"currency": "INR",
"idempotencyKey": "idem_nike_shoe_98234"
}'Response (201 Created):
{
"orderId": "ord_7f8a9b2c3d4e",
"merchantId": "merchant_nike",
"amount": 499900,
"currency": "INR",
"status": "CREATED",
"createdAt": "2026-09-25T18:30:00"
}curl -X GET http://localhost:8081/api/v1/payments/ord_7f8a9b2c3d4eFire 6 rapid requests in under 10 seconds:
for i in {1..6}; do
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8081/api/v1/payments \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: spam_merchant" \
-d '{"merchantId":"spam_merchant","amount":1000,"currency":"INR","idempotencyKey":"idem_'$i'"}'
doneOutput:
201
201
201
201
201
429 <-- Rate Limit Triggered (HTTP 429 Too Many Requests)
LoomPay includes a high-performance operator console built with Vite, React, TypeScript, and Tailwind CSS located in frontend/:
cd frontend
npm install
npm run devVisit http://localhost:5173 to view the live payment stream, inspect outbox event queues, and view double-entry balances.
Live Demo is accessible at: loompay.abhiram.tech
LoomPay maintains a strict, hermetic unit and integration test suite (Mockito, JUnit 5) verifying concurrency isolation, idempotency bypass paths, and lock release guarantees.
Run tests locally:
./mvnw clean testTest Execution:
[INFO] Running tech.abhiram.loompay.PaymentServiceTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] Running tech.abhiram.loompay.LoompayApplicationTests
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------