Razorpay
Accept payments with Razorpay. Use this skill when the project needs to integrate
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 linesRazorpay 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
| Event | Action |
|---|---|
payment.authorized | Log authorization |
payment.captured | Grant access |
payment.failed | Notify user |
subscription.activated | Set up subscription |
subscription.charged | Renew access |
subscription.cancelled | Revoke access |
subscription.halted | Restrict access (payment failures) |
refund.processed | Adjust 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
Related Skills
Adyen
Accept payments with Adyen. Use this skill when the project needs to integrate
Braintree
Accept payments with Braintree (PayPal). Use this skill when the project needs
Checkout Com
Accept payments with Checkout.com. Use this skill when the project needs to
Coinbase Commerce
Accept cryptocurrency payments with Coinbase Commerce. Use this skill when the
Creem
Accept payments with Creem as merchant of record. Use this skill when the project
Klarna
Accept payments with Klarna. Use this skill when the project needs to integrate