SSiddiq.in
Money code · treated with the respect it deserves

The rules for payment code are different. A refund bug isn't a JIRA ticket — it's a chargeback ticket.

Idempotency keys on every intent. HMAC-verified webhooks. Zero raw card data in your database. Reconciliation reports that match the gateway to the paisa. This is the checklist we ship against — because "works on happy path" is not a launch criterion.

The four failure modes we design against

Every payment bug is one of these four.

Double charge
Why: User double-clicks, network flaky, retry happens.
Fix: Idempotency key on the payment intent — gateway dedupes.
Silent success
Why: Gateway charges, our webhook drops, order shows unpaid.
Fix: Nightly reconciliation job matches gateway payouts to DB.
Refund not reflected
Why: Support refunds in gateway UI, our DB never knows.
Fix: Refund webhook is a first-class event — updates orders + notifies.
3DS drop-off
Why: Card requires 2FA, user closes tab, order half-created.
Fix: Order state machine: `awaiting_payment → paid | expired` with TTL.
Lifecycle of a single charge

Eleven state transitions. Every one persisted.

cart_created
→
checkout_started
→
intent_created
→
3ds_challenged
→
3ds_completed
→
gateway_authorized
→
webhook_received
→
reconciled
→
receipt_sent
→
fulfilled
→
refunded (optional)

Every transition writes to an event log. A support agent can answer "where's my money?" in one query — because the answer is always in the log, not inferred from the current state.

Gateway picks · without the PR fluff

India-heavy, global-heavy — different picks.

GatewaySweet spotFee (India)Watch out for
RazorpayD2C, SaaS, subscriptions in India~2%Occasional payout delays
CashfreeMarketplace payouts, high UPI volume~1.75%Docs less polished than Razorpay
StripeGlobal, subscriptions, dev-first~2.9% + INR convSetup for India accounts is fiddly
PhonePe / PayUUPI-heavy buyers, local B2B~1.75%Weaker dashboards

Payment FAQ

Razorpay for India-first D2C and SaaS. Cashfree for high-volume marketplace payouts. Stripe when you need global-first, subscriptions, and Payment Element polish (or plan to raise US funding). PhonePe/PayU when your buyers explicitly prefer local UPI-heavy stacks. We integrate all four monthly.

PCI scope reduction · practical

Seven design choices that keep you on SAQ-A, not SAQ-D.

SAQ-A: 22 questions, 1 afternoon. SAQ-D: 329 questions, quarterly ASV scans, annual audit. Design the difference in.

Hosted fields only
Card fields render in an iframe served by Razorpay / Stripe. Numbers never touch your DOM, network, or server. Non-negotiable.
Zero PAN storage
Store the tokenized `card_id` returned by the gateway. If you ever have a card number in a variable, you're already out of scope.
Tokenised recurring
Save the network token via the gateway's vault. Recurring charges reference the token, not the card. Also survives card re-issue.
TLS 1.2+ everywhere
TLS 1.0/1.1 fails PCI. Enforce at CDN, origin, and internal service mesh. Alert on any downgrade.
Segment the network
Payment services in a separate VPC / subnet. Blast radius shrinks; scope shrinks with it.
Log without PAN
Never log raw request bodies from checkout. Redact card fields at the reverse proxy, not just in app code.
Signed webhook receipts
Verify HMAC before parsing. Store the raw payload for 90 days for reconciliation — encrypted at rest.

Ship payments you can defend to a CFO.

Scope a payments project
Right tech, honest advice

Not sure if this is the right stack for you?

Tell us your goals — we'll recommend the right technology honestly, even if it isn't this one.

  • Personal reply from Siddiq within 24 hours
  • Honest advice — no pushy sales, no fake urgency
  • Clear next steps, timeline, and estimate

We only use your details to reply to this enquiry. Privacy.