Skip to content
Vinod Kumar PeddiSoftware Engineer
Section 1 of 7: Intro
01Payments · Distributed systems2026

Payment Orchestrator

A Stripe-style payment gateway with an asynchronous processing engine, resilient webhooks and idempotent APIs.

  • Node.js
  • Express
  • PostgreSQL 15
  • Redis 7
  • BullMQ
  • React
  • Vite
  • Docker Compose
Payments · Distributed systems
Services
api · worker · db · redis · dashboard · checkout
Pattern
Async job queue · idempotency keys · webhook retry
Data
PostgreSQL ledger with refund reconciliation
01Overview

Overview

Payment Orchestrator replicates the shape of a real payment processor: a merchant onboards, receives API credentials, creates orders through a REST API, sends customers to a hosted checkout, and watches transactions settle on a dashboard.

Six containers make up the system — api, worker, db, redis, dashboard and checkout — orchestrated with Docker Compose. A test merchant is seeded on startup so the full flow can be exercised with a single curl and a browser.

02Problem

Problem

A payment is not a single request. It is a lifecycle: order created, checkout opened, payment attempted, bank responds (eventually), merchant notified, maybe refunded. Every one of those steps can fail, be retried, or arrive twice.

The dashboard, the checkout page and the processing engine all read and write that lifecycle. If they disagree, a customer sees 'paid' while a merchant sees 'pending' — the exact class of inconsistency this project was built to handle.

03Architecture

Architecture

  1. 01The API accepts orders and payments and immediately enqueues processing on a Redis-backed BullMQ queue, so request latency is independent of bank latency.
  2. 02A dedicated worker consumes the queue, runs the payment engine (with fault injection for latency and bank failures), writes results to PostgreSQL and schedules webhook delivery.
  3. 03Hosted checkout is an isolated micro-frontend that supports UPI and card (with Luhn validation) and polls the API for status. The merchant dashboard is a separate React app with analytics, history and credential management.
04Implementation

Implementation

Order creation with API credentials

Merchant requests authenticate with an API key and secret sent as headers. Amounts are integers in the smallest currency unit.

Creating an order against the merchant API
1curl -X POST http://localhost:8000/api/v1/orders \2  -H "Content-Type: application/json" \3  -H "X-Api-Key: key_test_abc123" \4  -H "X-Api-Secret: secret_test_xyz789" \5  -d '{"amount": 50000, "currency": "INR", "receipt": "demo_1"}'

Async payment engine

Instead of processing inside the request, the API enqueues a job. The worker picks it up, simulates the bank round-trip, and persists the outcome — PROCESSED, FAILED or otherwise — before notifying the merchant.

Resilient webhooks

Merchant notifications are delivered by a retry loop with exponential backoff, up to five attempts. Failed deliveries never block the payment itself.

Refunds and the ledger

Partial and full refunds are first-class operations reconciled against a ledger, so the dashboard's numbers are derived from the same records the engine wrote.

05Challenges

Challenges

01

Duplicate requests

Retries from a flaky client or a double-clicked pay button must not create two charges. Idempotency keys make repeated requests return the original result.

02

Bank latency and failure

Fault injection simulates slow and failing bank responses; the queue absorbs the latency and the status polling on checkout keeps the customer informed.

03

Three surfaces, one truth

Dashboard, checkout and worker share PostgreSQL as the single source of truth; the queue carries intent, the database carries state.

06Technical decisions

Technical decisions

01

Redis + BullMQ over an in-process queue

A real broker gives durability, retries and a clean worker boundary that can be scaled independently of the API.

02

Separate checkout micro-frontend

Isolating checkout mirrors how hosted checkout pages work in production and keeps card handling out of the merchant dashboard.

03

PostgreSQL for the ledger

Financial records want transactions, constraints and reconciliation queries — a relational store is the natural fit.

07Result

Result

A reproducible, containerised payment system that demonstrates the hard parts — asynchronous processing, idempotency, retries with backoff and refund reconciliation — end to end, from `docker-compose up` to a settled transaction on the dashboard.

08Lessons

Lessons

  • 01The queue is not an optimisation; it is the boundary that makes the rest of the system reasonable.
  • 02Idempotency has to be designed at the API contract level, not bolted on later.
  • 03Fault injection during development surfaces the failure paths that tests written for the happy path never will.