
Razorpay Patterns Demo
India-accessible sibling of stripe-payments-demo. Razorpay Standard Checkout end-to-end with webhook HMAC verification, Redis SETNX idempotency, exponential-backoff retry, and client-callback signature verification. 30 tests, live /demo page with Razorpay Checkout.js modal.
Performance & Impact
30 tests
Test Suite
signature verify (8) + retry (7) + webhook verify (6) + event-id extract (7) + helpers
HMAC-SHA256
Client verify
order_id|payment_id signed with KEY_SECRET on Checkout.js success
SETNX
Webhook dedup
Redis SET NX EX 86400 on entity ID — matches Razorpay's 24h retry window
The Problem
Stripe is invite-only in India, so Indian developers can't build production Stripe integrations locally. Razorpay is the India-accessible equivalent and uses nearly identical patterns — webhook HMAC signing, idempotency on event IDs, subscription + order APIs. Without a reference implementation, every integration rediscovers the same footguns: missing raw body reads that break HMAC verification, no dedup on at-least-once webhook delivery, naive retry on 4xx that spams the gateway with guaranteed-to-fail requests.
The Solution
A runnable reference that mirrors stripe-payments-demo at the pattern level but swaps in Razorpay's specifics: hand-rolled HMAC-SHA256 signature verification (Razorpay doesn't ship a constructEvent helper), derived event ID from payment/order/subscription/refund entity IDs (Razorpay doesn't have a top-level event.id), and client-side Standard Checkout flow (order creation + modal + signature verify on success callback). Shared idempotency + retry helpers make the two demos interchangeable reading material.
System Architecture
Core Engineering Achievements:
System Architecture
Client / UX
API (Standard Checkout)
Idempotency
Retry
"Two-tier trust boundary: client → /api/verify-payment (confirms success callback isn't forged), Razorpay → /api/webhook (authoritative async notification for billing state). Production architecture uses both."
The Engineering Challenge
Razorpay's signing scheme is subtly different from Stripe's in two ways that bite: (1) Razorpay signs the raw body only, while Stripe prepends a timestamp — if you copy-paste the Stripe verifier and just swap the header name, the HMAC will fail on every payload. (2) Razorpay uses a DIFFERENT secret for the Checkout.js client-callback verification (the pair-secret of KEY_ID) vs the webhook signature (a separate webhook secret generated per endpoint). Confusing the two is the most common integration bug. The demo makes both secret boundaries explicit in code comments and tests each path separately.
User Journey
Interested in the full engineering breakdown?
I'm always open to discussing technical implementations, from state management strategies to infrastructure scaling.