A hands-on backend project to practice event-driven, cloud-native payment architecture — similar to how modern fintech systems (Razorpay, Cashfree, Stripe, PayU) orchestrate payments behind the scenes.
This repo implements a Serverless Multi-Payment Orchestrator with:
- Asynchronous payment processing via SQS + Worker
- Idempotent payment creation
- Pluggable provider adapters (Mock, RazorpayMock, CashfreeMock)
- Retries & failure handling
- DynamoDB as the source of truth for transactions
This project is designed to practice real-world backend patterns:
- Event-driven workflows using queues
- Idempotency for safe retries
- Asynchronous processing for scalability and resilience
- Provider abstraction so you can swap / add providers without changing core logic
You can evolve this into a full-blown orchestration layer for real payment gateways.
-
Payment Orchestrator API (Node.js + Express)
POST /paymentsto create a payment (returns immediately withPENDING)GET /payments/:idto fetch live status (PENDING / SUCCESS / FAILED)
-
Idempotency Layer
- Uses an
Idempotencytable to ensure the sameidempotencyKeyalways maps to the sametransactionId - Prevents duplicate charges when clients retry requests
- Uses an
-
Asynchronous Processing (SQS + Worker)
- API enqueues a payment job into SQS
- A background Provider Worker consumes jobs and calls the appropriate provider
-
Pluggable Provider Adapters
MockProvider– base mock providerRazorpayMockProvider– simulates Razorpay-like behaviorCashfreeMockProvider– simulates Cashfree-like behavior- Easily extendable to real Razorpay / Cashfree / Stripe adapters
-
Provider Routing Logic
- Simple rules (e.g. by currency/amount) in
src/providers/index.js:- INR → RazorpayMock
- High value → CashfreeMock
- Fallback → MockProvider
- Simple rules (e.g. by currency/amount) in
-
Retries & Failure Handling
- Worker tracks
attemptsin DynamoDB - Retries failed transactions up to
MAX_RETRIES - Marks transaction as
FAILEDafter max attempts
- Worker tracks
-
End-to-End Audit Trail
- DynamoDB
Transactionstable stores full state:PENDING → SUCCESS / FAILEDattempts,lastError,providerRef, timestamps
- DynamoDB
High-level flow:
- Client calls
POST /payments - API:
- validates input
- performs idempotency check
- creates/updates transaction in DynamoDB
- enqueues a job into SQS
- Worker:
- polls SQS
- loads transaction from DynamoDB
- selects provider (Mock / RazorpayMock / CashfreeMock)
- calls
provider.charge(txn) - updates status in DynamoDB (
SUCCESS/FAILED+ retries)
- Client (or another service) calls
GET /payments/:idto fetch status
flowchart LR
A[Client / Merchant App] -->|POST /payments| B[Payment API (Express)]
B --> C[Idempotency + Create Transaction]
C --> D[(DynamoDB\nTransactions + Idempotency)]
B -->|Send Job| E[[SQS Queue]]
E --> F[Provider Worker]
F --> G[Provider Router\n(Mock / RazorpayMock / CashfreeMock)]
G --> H[provider.charge(txn)]
H --> D
A -->|GET /payments/:id| I[Status API]
I --> D
D --> I
I --> A