The Complete Guide to Razorpay Integration with Django and React
Introduction
For businesses operating in India, international gateways like Stripe can sometimes face friction with domestic cards (due to 3D Secure mandates) or lack support for local favorites like UPI (Unified Payments Interface).
Razorpay is the de-facto standard for Indian SaaS and e-commerce. It natively supports:
- UPI (Google Pay, PhonePe, Paytm, etc.)
- Domestic Debit/Credit Cards
- Netbanking (50+ banks)
- Wallets
This guide shows you how to integrate Razorpay into a Django Rest Framework (DRF) backend and a React frontend.
The Flow
Razorpay's flow is unique compared to Stripe:
- Backend: Create an "Order" using the Razorpay API.
- Frontend: Use the Order ID to open the Razorpay Checkout Modal.
- Frontend: User pays; Razorpay returns a
payment_idandsignature. - Backend: You verify the signature to ensure the payment is authentic.
1. Backend Setup (Django)
First, install the Razorpay Python client.
pip install razorpay
Configure Keys
Add your keys to settings.py. You can get these from the Razorpay Dashboard.
# settings.py
import os
RAZORPAY_KEY_ID = os.environ.get('RAZORPAY_KEY_ID')
RAZORPAY_KEY_SECRET = os.environ.get('RAZORPAY_KEY_SECRET')
Create Order API
We need an endpoint to initialize the transaction.
# views.py
import razorpay
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
client = razorpay.Client(auth=(settings.RAZORPAY_KEY_ID, settings.RAZORPAY_KEY_SECRET))
class CreateOrderView(APIView):
def post(self, request):
try:
amount = int(request.data.get('amount')) * 100 # Amount in paise (100 paise = 1 INR)
currency = "INR"
# Create Razorpay Order
payment_order = client.order.create(dict(
amount=amount,
currency=currency,
payment_capture='1' # Auto capture
))
payment_order_id = payment_order['id']
return Response(
{
'order_id': payment_order_id,
'amount': amount,
'currency': currency,
'key': settings.RAZORPAY_KEY_ID
},
status=status.HTTP_201_CREATED
)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Verify Signature API
This is crucial. Without this, anyone could spoof a success response to your frontend.
# views.py (continued)
import hashlib
import hmac
class VerifyPaymentView(APIView):
def post(self, request):
data = request.data
try:
# We need these 3 parameters specifically
ord_id = data.get('razorpay_order_id')
pay_id = data.get('razorpay_payment_id')
sig = data.get('razorpay_signature')
# Razorpay's signature verification method
params_dict = {
'razorpay_order_id': ord_id,
'razorpay_payment_id': pay_id,
'razorpay_signature': sig
}
# This will raise a SignatureVerificationError if it fails
client.utility.verify_payment_signature(params_dict)
# If we reach here, payment is successful
# TODO: Mark order as paid in your DB
return Response({'status': 'Payment Verified'}, status=status.HTTP_200_OK)
except razorpay.errors.SignatureVerificationError:
return Response({'error': 'Invalid Signature'}, status=status.HTTP_400_BAD_REQUEST)
2. Frontend Integration (React)
Razorpay uses a script that injects the checkout modal. A popular wrapper hook is react-razorpay, or you can simply load the script.
Installation
npm install react-razorpay
Payment Component
import React from 'react';
import useRazorpay from 'react-razorpay';
export default function RazorpayPayment() {
const [Razorpay] = useRazorpay();
const handlePayment = async () => {
// 1. Create Order on your Backend
const response = await fetch('/api/create-order/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 500 }) // ₹500
});
const orderData = await response.json();
// 2. Configure Razorpay Options
const options = {
key: orderData.key,
amount: orderData.amount,
currency: orderData.currency,
name: "Acme Corp",
description: "Test Transaction",
image: "https://example.com/your_logo.png",
order_id: orderData.order_id,
handler: async function (response) {
// 3. Verify Payment on Backend
const verifyRes = await fetch('/api/verify-payment/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
razorpay_payment_id: response.razorpay_payment_id,
razorpay_order_id: response.razorpay_order_id,
razorpay_signature: response.razorpay_signature
})
});
const verifyData = await verifyRes.json();
if (verifyRes.ok) {
alert("Payment Successful!");
} else {
alert("Payment Verification Failed");
}
},
prefill: {
name: "Sumit Kumar",
email: "sumit@example.com",
contact: "9999999999",
},
notes: {
address: "Razorpay Corporate Office",
},
theme: {
color: "#3399cc",
},
};
const rzp1 = new Razorpay(options);
// Handle failure
rzp1.on("payment.failed", function (response) {
alert(response.error.description);
});
rzp1.open();
};
return (
<div className="flex flex-col items-center justify-center min-h-[400px]">
<h1 className="text-2xl font-bold mb-4">Pay with Razorpay</h1>
<button
onClick={handlePayment}
className="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 transition"
>
Pay ₹500
</button>
<p className="mt-4 text-gray-500 text-sm">
Supports UPI, Cards, Netbanking
</p>
</div>
);
}
Enhancing the User Experience
For Indian users, UPI is king. Razorpay automatically detects if the user is on a mobile device and can open their UPI apps (Google Pay, PhonePe) directly (Deep Linking), providing a seamless experience compared to typing out card details.
Common Pitfalls
- Amount format: Razorpay expects the amount in paise (integer), not Rupees. ₹500.50 must be sent as
50050. - CSRF: Ensure your Django views are CSRF exempt or you are passing the CSRF token correctly in the headers only if you are using session authentication. For DRF with Token/JWT, this is usually handled automatically.
- Signature Verification: Never skip the backend verification step.
Conclusion
Razorpay provides the most robust solution for Indian businesses. By combining it with Django's secure backend and React's dynamic frontend, you can accept payments from millions of Indian customers instantly.
Comments
Sign in to join the conversation