Business

Building Payment Systems for Indian Markets: UPI & Razorpay

Integrate UPI and Razorpay for Indian SaaS. Handle transactions, refunds, webhooks, and compliance for rupee payments.

All articles
BusinessNexaEx TeamDecember 2, 2025 9 min read
Building Payment Systems for Indian Markets: UPI & Razorpay

The Indian Payment Landscape

India's payment ecosystem evolved rapidly. Digital payments grew from 2% of transactions in 2015 to over 40% by 2024. As a SaaS serving India, supporting Indian payment methods is essential.

Understanding UPI

Unified Payments Interface (UPI) revolutionized payments in India. Managed by NPCI (National Payments Corporation of India), UPI enables instant, 24/7 fund transfers between bank accounts using only a phone number or app-based handle.

UPI for SaaS:

  • Direct bank transfers eliminate fraud risk
  • No card processing fees—typically 0% for B2B, 1-2% for B2C
  • Instant settlement reduces cash flow impact
  • NPCI regulates it, ensuring reliability

Integrating Razorpay

Razorpay aggregates payment methods—cards, UPI, wallets, bank transfers. For most Indian SaaS, Razorpay simplifies integration:

import Razorpay from 'razorpay';

const razorpay = new Razorpay({
  key_id: process.env.RAZORPAY_KEY,
  key_secret: process.env.RAZORPAY_SECRET
});

async function createPayment(amount: number, userId: string) {
  const order = await razorpay.orders.create({
    amount: amount * 100, // Amount in paise
    currency: 'INR',
    receipt: `receipt_${userId}_${Date.now()}`,
    notes: { userId }
  });

  return order;
}

Store order IDs in your database linking to users and subscriptions.

Handling Webhooks Securely

Razorpay sends webhooks confirming payments. Verify webhook authenticity:

import crypto from 'crypto';

function verifyWebhook(body: string, signature: string, secret: string) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  return hash === signature;
}

// In your webhook handler
app.post('/webhooks/razorpay', express.raw({ type: 'application/json' }),
  async (req, res) => {
    if (!verifyWebhook(
      req.rawBody,
      req.headers['x-razorpay-signature'],
      process.env.RAZORPAY_WEBHOOK_SECRET
    )) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    const { event, payload } = req.body;

    if (event === 'payment.authorized') {
      // Update subscription as active
      await updateSubscription(payload.payment.notes.userId, 'active');
    }
  }
);

Refund Management

Implement refund workflows:

async function refundPayment(paymentId: string, amount?: number) {
  try {
    const refund = await razorpay.payments.refund(paymentId, {
      amount, // Partial refund if specified
      notes: { reason: 'customer_request' }
    });

    return refund;
  } catch (error) {
    // Razorpay returns error if payment not settled
    // Queue for retry or manual processing
    await queueRefundRetry(paymentId);
  }
}

Partial refunds need approval workflows. Full refunds should auto-process within 24 hours of payment.

Compliance and Tax

India requires GST (18% standard) on digital services:

function calculateInvoiceTotal(amount: number, gstRate = 0.18) {
  const gst = amount * gstRate;
  return { subtotal: amount, gst, total: amount + gst };
}

Issue invoices with GSTIN (GST Identification Number). Track payment methods for audit compliance. Maintain transaction logs per RBI requirements.

Testing Payment Flows

Razorpay provides test keys and payment methods:

Test UPI ID: success@razorpay
Test Card: 4111111111111111 (Visa)
Test Amount: Any amount works

Implement comprehensive test scenarios: successful payments, failed payments, partial refunds, webhook delays.

Frequently asked questions

What's the best approach for subscription renewals with Indian payments?

Use emandate (e-authorization) for recurring charges. Customer authorizes one-time charge; you create recurring charges via API. If renewal fails, retry after 3 days, then 7 days. After third failure, send notification to customer for manual payment.

How do we handle payment failures and reconciliation?

Reconcile daily: fetch settled payments from Razorpay, compare against database. Mark discrepancies for investigation. For failed payments, alert users immediately with retry mechanism. Implement dead-letter queues for failed webhook processing.

Is Razorpay the only option for Indian payments?

Other options include Instamojo, Bill Desk, and direct bank integrations. Razorpay dominates SaaS due to excellent developer experience. For enterprise requirements, direct bank integration via NPCI offers lower fees but requires significant compliance work.

Let's build your next idea

One conversation to scope the work, meet the team, and get a proposal — usually within two business days.