Skip to main content
Business & GrowthPayment Services224 lines

Razorpay

Accept payments with Razorpay. Use this skill when the project needs to integrate

Quick Summary33 lines
You are a payments specialist who integrates Razorpay into projects. Razorpay is
India's leading payment gateway supporting UPI, cards, net banking, wallets, and
international payments. It dominates the Indian market and is expanding globally.

## Key Points

- Always create orders server-side before opening Checkout
- Verify payment signatures — never trust client callbacks alone
- Use notes on orders and subscriptions for your business references
- Test with Razorpay test mode and test card numbers
- Handle `subscription.halted` — it means payment retries exhausted
- Use webhooks as source of truth, not checkout callbacks
- Support UPI for Indian customers — it's the dominant payment method
- Accepting payments without signature verification
- Creating checkout without a server-side order
- Not handling subscription halted/cancelled webhooks
- Using live keys in development
- Ignoring UPI — it's how most Indian users pay

## Quick Example

```bash
npm install razorpay
```

```typescript
const refund = await razorpay.payments.refund(paymentId, {
  amount: 29900, // Full or partial refund in paise
  notes: { reason: 'Customer request' },
});
```
skilldb get payment-services-skills/RazorpayFull skill: 224 lines
Paste into your CLAUDE.md or agent config

Razorpay Payment Integration

You are a payments specialist who integrates Razorpay into projects. Razorpay is India's leading payment gateway supporting UPI, cards, net banking, wallets, and international payments. It dominates the Indian market and is expanding globally.

Core Philosophy

Orders before payments

Razorpay uses an order-first flow. Create a server-side order, pass the order ID to the client checkout, and verify the payment signature on completion. Never accept a payment without creating an order first.

UPI is essential for India

UPI (Unified Payments Interface) accounts for the majority of digital payments in India. Razorpay's integration handles UPI intent flows, QR codes, and collect requests natively.

Verify signatures, always

Every successful payment must be verified by checking Razorpay's signature using your key secret. Without verification, anyone can forge a payment callback.

Setup

Install

npm install razorpay

Initialize

import Razorpay from 'razorpay';

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

Key Techniques

Create order + Checkout.js

// Server: create order
const order = await razorpay.orders.create({
  amount: 29900, // INR 299.00 in paise
  currency: 'INR',
  receipt: `order_${Date.now()}`,
  notes: { userId: '123', plan: 'pro' },
});
<!-- Client: Razorpay Checkout -->
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<script>
const options = {
  key: 'rzp_live_xxx',
  amount: order.amount,
  currency: order.currency,
  order_id: order.id,
  name: 'Your App',
  description: 'Pro Plan',
  handler: async (response) => {
    // Send to server for verification
    await fetch('/api/razorpay/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        orderId: response.razorpay_order_id,
        paymentId: response.razorpay_payment_id,
        signature: response.razorpay_signature,
      }),
    });
  },
  prefill: { email: 'user@example.com' },
  theme: { color: '#d4a843' },
};

const rzp = new Razorpay(options);
rzp.open();
</script>

Verify payment signature

import crypto from 'crypto';

export async function POST(req: Request) {
  const { orderId, paymentId, signature } = await req.json();

  const expectedSignature = crypto
    .createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
    .update(`${orderId}|${paymentId}`)
    .digest('hex');

  if (expectedSignature !== signature) {
    return Response.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Payment verified — grant access
  await grantAccess(orderId);
  return Response.json({ status: 'verified' });
}

Subscriptions

// Create plan
const plan = await razorpay.plans.create({
  period: 'monthly',
  interval: 1,
  item: {
    name: 'Pro Plan',
    amount: 29900,
    currency: 'INR',
    description: 'Monthly Pro subscription',
  },
});

// Create subscription
const subscription = await razorpay.subscriptions.create({
  plan_id: plan.id,
  total_count: 12,
  quantity: 1,
  notes: { userId: '123' },
});

// Cancel subscription
await razorpay.subscriptions.cancel(subscriptionId);

Payment Links

const paymentLink = await razorpay.paymentLink.create({
  amount: 29900,
  currency: 'INR',
  description: 'Pro Plan',
  customer: { email: 'user@example.com' },
  callback_url: 'https://yourdomain.com/success',
  notes: { userId: '123' },
});

// Share paymentLink.short_url with the customer

Refunds

const refund = await razorpay.payments.refund(paymentId, {
  amount: 29900, // Full or partial refund in paise
  notes: { reason: 'Customer request' },
});

Webhook Processing

EventAction
payment.authorizedLog authorization
payment.capturedGrant access
payment.failedNotify user
subscription.activatedSet up subscription
subscription.chargedRenew access
subscription.cancelledRevoke access
subscription.haltedRestrict access (payment failures)
refund.processedAdjust access
import crypto from 'crypto';

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get('x-razorpay-signature');
  const secret = process.env.RAZORPAY_WEBHOOK_SECRET;

  const expectedSig = crypto.createHmac('sha256', secret).update(body).digest('hex');
  if (expectedSig !== signature) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.event) {
    case 'payment.captured':
      const userId = event.payload.payment.entity.notes?.userId;
      await grantAccess(userId);
      break;
    case 'subscription.cancelled':
      await revokeAccess(event.payload.subscription.entity.notes?.userId);
      break;
  }

  return new Response('OK');
}

Best Practices

  • Always create orders server-side before opening Checkout
  • Verify payment signatures — never trust client callbacks alone
  • Use notes on orders and subscriptions for your business references
  • Test with Razorpay test mode and test card numbers
  • Handle subscription.halted — it means payment retries exhausted
  • Use webhooks as source of truth, not checkout callbacks
  • Support UPI for Indian customers — it's the dominant payment method

Anti-Patterns

  • Accepting payments without signature verification
  • Creating checkout without a server-side order
  • Not handling subscription halted/cancelled webhooks
  • Using live keys in development
  • Ignoring UPI — it's how most Indian users pay
  • Not implementing refund handling

Install this skill directly: skilldb add payment-services-skills

Get CLI access →