Back to Engineering Blog
Architecture7 min read

Taking INR Payments with Razorpay

A 40-character receipt limit meant no customer could ever complete a checkout. Finding it, fixing it, and why USD is still switched off.

Glacro · Published July 10, 2026

For a long stretch, no customer could pay us. The billing table was empty, which we read as a growth problem. It was a validation error.

Forty characters

Razorpay rejects an order whose receipt field exceeds 40 characters. Both checkout paths built the receipt as order_<userId>_<timestamp>. An account id is a 36-character UUID, so that string was 56 characters and every order was refused. The customer saw a generic failure and the payment modal never opened.

typescript
// 5 + 8 + 6 + 1 + 12 = 32 characters, always
const stamp  = Date.now().toString(36);
const nonce  = crypto.randomBytes(3).toString("hex");
const digest = sha256(userId).slice(0, 12);
return `rcpt_${stamp}${nonce}_${digest}`;

The user id is carried as a short digest rather than in full. The authoritative link from an order back to an account is a note set server-side at creation, which the verification route and the webhook both read — the receipt only needs to be unique and traceable.

Why the default currency mattered

The pricing page defaulted to USD, and USD routed to Stripe, which had never been configured on this deployment. A placeholder secret key hid the misconfiguration by looking valid to a startsWith check. So the default path to paying us was broken twice over. The default is now INR through Razorpay, and the interface reports which gateways are genuinely live rather than inferring it from the selected currency.

Key Takeaway
USD checkout is still switched off. When a gateway is not configured, the interface says so at the point of choice instead of failing at the click.

Related Articles

Product Updates

What Glacro Actually Runs, and What It Doesn't

August 26, 2026 · 6 min read
Tutorials

Deploying a Next.js 14 Static Export

August 20, 2026 · 7 min read