Why Razorpay
If you're building a SaaS in India, Razorpay is where most founders end up. It handles UPI, net banking, wallets, EMI, and cards in one integration β no other gateway covers the full Indian payment stack as cleanly.
Setting Up Your Account
Sign up at razorpay.com with your business PAN, GST number, and bank account details. Approval takes 1β3 business days. Test mode is fully functional while you wait β build everything in test mode and switch keys when approved.
Create separate Razorpay accounts for products that bill independently. Mixing revenue in one account makes reconciliation painful.
The Basic Payment Flow
Always create the order on your backend first:
// Backend: create order
const order = await razorpay.orders.create({
amount: 49900, // paise
currency: 'INR',
receipt: `order_${userId}_${Date.now()}`
});
// Frontend: open checkout
const rzp = new Razorpay({
key: process.env.RAZORPAY_KEY_ID,
amount: order.amount,
currency: 'INR',
order_id: order.id,
handler: (response) => verifyPayment(response)
});
rzp.open();
Never trust the amount from the frontend. Verify every payment server-side.
Subscriptions
For recurring billing, create a plan then a subscription:
POST /v1/plans
{
"period": "monthly",
"interval": 1,
"item": { "name": "Pro Plan", "amount": 49900, "currency": "INR" }
}
Razorpay subscriptions use e-mandate for auto-debit. First payment needs user action; subsequent ones are automatic after e-mandate setup.
Webhooks β Don't Skip This
Webhooks are how Razorpay tells your backend what happened. Handle these events:
payment.capturedβ payment succeededsubscription.chargedβ renewal succeededsubscription.haltedβ payment failed after retriesrefund.processedβ refund completed
Always verify the webhook signature:
const expectedSig = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(req.body))
.digest('hex');
if (expectedSig !== req.headers['x-razorpay-signature']) {
return res.status(400).send('Invalid');
}
Store every webhook event in your database β you'll need them for debugging.
Failed Payments
When a subscription payment fails, Razorpay retries automatically. After all retries fail, subscription.halted fires. At that point:
- Downgrade the user's access immediately
- Send a payment link via WhatsApp or email
- Don't wait for them to notice the lockout
Proactive recovery converts 40β50% of failed payments. Silent lockouts recover almost none.
Common Mistakes
Not verifying payments server-side. Anyone can fake a success response from the frontend.
Using test keys in production. Use environment variables. Double-check before go-live.
Ignoring the notes field. Pass your internal user ID and plan ID in notes β makes reconciliation much easier later.
Handling duplicate webhooks. Store webhook IDs and check for duplicates before processing.
GST on Invoices
If GST-registered, enable GST invoicing in Dashboard β Settings β Tax. Enter your GSTIN and configure 18% rate for SaaS. Razorpay generates compliant invoices automatically.
The integration takes a day to build properly. The webhooks and error handling take another day. Budget for both and you'll have a payment system that works reliably from day one.