Paytm Payment Gateway Integration in Django REST framework and ReactJs
In this article, you will learn how to integrate Paytm payment gateway with Django RF as backend and ReactJs as frontend. We will also be seeing how to set up environment variables for our Merchant ID and Merchant Key.
You would require a Paytm Merchant Account to be able to use the Payment Gateway
Visit business.paytm.com, sign up there, and activate your Merchant Account by adding necessary information.
GET MERCHANT KEY AND MERCHANT ID:
To accept payment from the user in your Paytm business account you have to give Paytm your Merchant Key and Merchant ID.
Merchant ID - This is a unique identifier provided to every merchant by Paytm. MID is part of your account credentials and is different in the testing and production environment.
Merchant Key - This is a unique secret key used for secure encryption of every request. This needs to be kept in the backend.
To get your Merchant ID and Merchant Key, log into your Paytm business account and look for, Developer settings --> API Keys as shown below:
If it pops up an error message, then log out and again log into your account.
These are the credentials that are required to process the payment from the user to your Paytm account.
LETS BUILD APIs USING DJANGO RF:
Create a Django project:
#install Django and virtualenv if not installed already
pip install Django virtualenv
mkdir paytm_integration
cd paytm_integration
django-admin startproject paytm_backend
cd paytm_backend
python manage.py startapp api
Create a virtual environment for our project:
virtualenv venv
#activate our virtual environment
source ./venv/Scripts/activate
#if you are using windows cmd run the following command to activate the virtual env
.\venv\Scripts\activate
Install all the required packages:
pip install djangorestframework
pip install django-environ
pip install django-cors-headers
pip install pycryptodome
Create .env, urls.py, serializers.py, and Checksum.py files in api folder. Also, create a new folder named templates in that, create another folder named paytm and in that, create a new HTML file and call it as paymantstatus.html as shown below:
Include api.urls.py in paytm_backend/urls.py:
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/',include("api.urls"))
]
Now, let’s setup cors and add TEMPLATES_DIR in the TEMPLATES list. Also, we have to register our apps in INSTALLED_APPS.
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# join templates directory path
TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
CORS_ALLOW_ALL_ORIGINS=True
#...rest will be the same
# install API and corsheaders
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api',
'corsheaders',
]
# add corsheaders middleware
MIDDLEWARE = [
#always keep corsheaders middleware at the top
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [TEMPLATES_DIR,], # add TEMPLATES_DIR
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
#...rest will be the same
Setup urls for payment in api/urls.py:
from django.urls import path
from .views import *
urlpatterns = [
path('pay/', start_payment, name="start_payment"),
path('handlepayment/', handlepayment, name="handlepayment"),
]
# we will create these two functions in the future
Create an Order model in api/models.py:
from django.db import models
# Create your models here.
class Order(models.Model):
user_email = models.CharField(max_length=100)
product_name = models.CharField(max_length=100)
order_amount = models.CharField(max_length=25)
isPaid = models.BooleanField(default=False)
order_date = models.DateTimeField(auto_now=True)
def __str__(self):
return self.product_name
Register Order model in api/admin.py:
from django.contrib import admin
from .models import Order
admin.site.register(Order)
Create serializer class for Order model in api/serializers.py:
from rest_framework import serializers
from .models import Order
class OrderSerializer(serializers.ModelSerializer):
order_date = serializers.DateTimeField(format="%d %B %Y %I:%M %p")
class Meta:
model = Order
fields = '__all__'
depth = 2
Setup environment variables in api/.env:
Every time you change your environment variables, you have to restart the server.
MERCHANTID=**your merchant id**
MERCHANTKEY=**your merchant key**
Let's add checksum logic provided by Paytm in Checksum.py:
# pip install pycryptodome
import base64
import string
import random
import hashlib
from Crypto.Cipher import AES
IV = "@@@@&&&&####$$$$"
BLOCK_SIZE = 16
# This code is provided by paytm to create a checksum
def generate_checksum(param_dict, merchant_key, salt=None):
params_string = __get_param_string__(param_dict)
salt = salt if salt else __id_generator__(4)
final_string = '%s|%s' % (params_string, salt)
hasher = hashlib.sha256(final_string.encode())
hash_string = hasher.hexdigest()
hash_string += salt
return __encode__(hash_string, IV, merchant_key)
def generate_refund_checksum(param_dict, merchant_key, salt=None):
for i in param_dict:
if("|" in param_dict[i]):
param_dict = {}
exit()
params_string = __get_param_string__(param_dict)
salt = salt if salt else __id_generator__(4)
final_string = '%s|%s' % (params_string, salt)
hasher = hashlib.sha256(final_string.encode())
hash_string = hasher.hexdigest()
hash_string += salt
return __encode__(hash_string, IV, merchant_key)
def generate_checksum_by_str(param_str, merchant_key, salt=None):
params_string = param_str
salt = salt if salt else __id_generator__(4)
final_string = '%s|%s' % (params_string, salt)
hasher = hashlib.sha256(final_string.encode())
hash_string = hasher.hexdigest()
hash_string += salt
return __encode__(hash_string, IV, merchant_key)
def verify_checksum(param_dict, merchant_key, checksum):
# Remove checksum
if 'CHECKSUMHASH' in param_dict:
param_dict.pop('CHECKSUMHASH')
# Get salt
paytm_hash = __decode__(checksum, IV, merchant_key)
salt = paytm_hash[-4:]
calculated_checksum = generate_checksum(param_dict, merchant_key, salt=salt)
return calculated_checksum == checksum
def verify_checksum_by_str(param_str, merchant_key, checksum):
# Remove checksum
#if 'CHECKSUMHASH' in param_dict:
#param_dict.pop('CHECKSUMHASH')
# Get salt
paytm_hash = __decode__(checksum, IV, merchant_key)
salt = paytm_hash[-4:]
calculated_checksum = generate_checksum_by_str(param_str, merchant_key, salt=salt)
return calculated_checksum == checksum
def __id_generator__(size=6, chars=string.ascii_uppercase + string.digits + string.ascii_lowercase):
return ''.join(random.choice(chars) for _ in range(size))
def __get_param_string__(params):
params_string = []
for key in sorted(params.keys()):
if("REFUND" in params[key] or "|" in params[key]):
respons_dict = {}
exit()
value = params[key]
params_string.append('' if value == 'null' else str(value))
return '|'.join(params_string)
__pad__ = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
__unpad__ = lambda s: s[0:-ord(s[-1])]
def __encode__(to_encode, iv, key):
# Pad
to_encode = __pad__(to_encode)
# Encrypt
c = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
to_encode = c.encrypt(to_encode.encode('utf-8'))
# Encode
to_encode = base64.b64encode(to_encode)
return to_encode.decode("UTF-8")
def __decode__(to_decode, iv, key):
# Decode
to_decode = base64.b64decode(to_decode)
# Decrypt
c = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
to_decode = c.decrypt(to_decode)
if type(to_decode) == bytes:
# convert bytes array to str.
to_decode = to_decode.decode()
# remove pad
return __unpad__(to_decode)
if __name__ == "__main__":
params = {
"MID": "mid",
"ORDER_ID": "order_id",
"CUST_ID": "cust_id",
"TXN_AMOUNT": "1",
"CHANNEL_ID": "WEB",
"INDUSTRY_TYPE_ID": "Retail",
"WEBSITE": "xxxxxxxxxxx"
}
print(verify_checksum(
params, 'xxxxxxxxxxxxxxxx',
"CD5ndX8VVjlzjWbbYoAtKQIlvtXPypQYOg0Fi2AUYKXZA5XSHiRF0FDj7vQu66S8MHx9NaDZ/uYm3WBOWHf+sDQAmTyxqUipA7i1nILlxrk="))
# print(generate_checksum(params, "xxxxxxxxxxxxxxxx"))
Verify Checksum.py by executing it (refer following image):
Now let’s write our payment logic in api/views.py:
import environ
from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Order
from .serializers import OrderSerializer
from . import Checksum
# Create your views here.
env = environ.Env()
# you have to create .env file in same folder where you are using environ.Env()
# reading .env file which located in api folder
environ.Env.read_env()
@api_view(['POST'])
def start_payment(request):
# request.data is coming from frontend
amount = request.data['amount']
name = request.data['name']
email = request.data['email']
# we are saving an order instance (keeping isPaid=False)
order = Order.objects.create(product_name=name,
order_amount=amount,
user_email=email, )
serializer = OrderSerializer(order)
# we have to send the param_dict to the frontend
# these credentials will be passed to paytm order processor to verify the business account
param_dict = {
'MID': env('MERCHANTID'),
'ORDER_ID': str(order.id),
'TXN_AMOUNT': str(amount),
'CUST_ID': email,
'INDUSTRY_TYPE_ID': 'Retail',
'WEBSITE': 'WEBSTAGING',
'CHANNEL_ID': 'WEB',
'CALLBACK_URL': 'http://127.0.0.1:8000/api/handlepayment/',
# this is the url of handlepayment function, paytm will send a POST request to the fuction associated with this CALLBACK_URL
}
# create new checksum (unique hashed string) using our merchant key with every paytm payment
param_dict['CHECKSUMHASH'] = Checksum.generate_checksum(param_dict, env('MERCHANTKEY'))
# send the dictionary with all the credentials to the frontend
return Response({'param_dict': param_dict})
@api_view(['POST'])
def handlepayment(request):
checksum = ""
# the request.POST is coming from paytm
form = request.POST
response_dict = {}
order = None # initialize the order varible with None
for i in form.keys():
response_dict[i] = form[i]
if i == 'CHECKSUMHASH':
# 'CHECKSUMHASH' is coming from paytm and we will assign it to checksum variable to verify our paymant
checksum = form[i]
if i == 'ORDERID':
# we will get an order with id==ORDERID to turn isPaid=True when payment is successful
order = Order.objects.get(id=form[i])
# we will verify the payment using our merchant key and the checksum that we are getting from Paytm request.POST
verify = Checksum.verify_checksum(response_dict, env('MERCHANTKEY'), checksum)
if verify:
if response_dict['RESPCODE'] == '01':
# if the response code is 01 that means our transaction is successfull
print('order successful')
# after successfull payment we will make isPaid=True and will save the order
order.isPaid = True
order.save()
# we will render a template to display the payment status
return render(request, 'paytm/paymentstatus.html', {'response': response_dict})
else:
print('order was not successful because' + response_dict['RESPMSG'])
return render(request, 'paytm/paymentstatus.html', {'response': response_dict})
Run the migrations:
python manage.py makemigrations
python manage.py migrate
#start the server
python manage.py runserver
Add a template in template/paytm/paymentstatus.html to display whether payment is successful or failed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment status</title>
</head>
<body>
<!-- style it however you want -->
<!-- the 'response' is coming from the context -->
{% if response.STATUS == "TXN_SUCCESS" %}
<h1>Transaction successful</h1>
{% else %}
<h1>Transaction Failed</h1>
{% endif %}
<a href="http://localhost:3000"><button>Go Home</button></a>
</body>
</html>
LETS SET UP OUR REACT.JS FRONTEND:
Create react app:
npx create-react-app razorpayfrontend
cd razorpayfrontend
npm start
Install the required dependencies for our project:
npm install axios
Add Bootstrap CDN in public/index.html (optional):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
Create a server.js file in src and add your backend server URL as shown below (We are doing this to keep the backend URL centralized throughout the app):
export const server = "http://127.0.0.1:8000";
Now create a component in src/App.js that will handle the payment logic when the user clicks the “Pay with Paytm” button:
import React, { useState } from "react";
import "./App.css";
import Axios from "axios";
import { server } from "./server";
const App = () => {
const [name, setName] = useState("");
const [amount, setAmount] = useState("");
const [email, setEmail] = useState("");
const handleSuccess = (res) => {
// separate key and values from the res object which is nothing but param_dict
let keyArr = Object.keys(res);
let valArr = Object.values(res);
// when we start the payment verification we will hide our Product form
document.getElementById("paymentFrm").style.display = "none";
// Lets create a form by DOM manipulation
// display messages as soon as payment starts
let heading1 = document.createElement("h1");
heading1.innerText = "Redirecting you to the paytm....";
let heading2 = document.createElement("h1");
heading2.innerText = "Please do not refresh your page....";
//create a form that will send necessary details to the paytm
let frm = document.createElement("form");
frm.action = "https://securegw-stage.paytm.in/order/process/";
frm.method = "post";
frm.name = "paytmForm";
// we have to pass all the credentials that we've got from param_dict
keyArr.map((k, i) => {
// create an input element
let inp = document.createElement("input");
inp.key = i;
inp.type = "hidden";
// input tag's name should be a key of param_dict
inp.name = k;
// input tag's value should be a value associated with the key that we are passing in inp.name
inp.value = valArr[i];
// append those all input tags in the form tag
frm.appendChild(inp);
});
// append all the above tags into the body tag
document.body.appendChild(heading1);
document.body.appendChild(heading2);
document.body.appendChild(frm);
// finally submit that form
frm.submit();
// if you remember, the param_dict also has "'CALLBACK_URL': 'http://127.0.0.1:8000/api/handlepayment/'"
// so as soon as Paytm gets the payment it will hit that callback URL with some response and
// on the basis of that response we are displaying the "payment successful" or "failed" message
};
const startPayment = async () => {
let bodyData = new FormData();
// send data to the backend
bodyData.append("amount", amount);
bodyData.append("name", name);
bodyData.append("email", email);
await Axios({
url: `${server}/api/pay/`,
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
data: bodyData,
}).then((res) => {
// we will retrieve the param_dict that we are sending from the backend with
// all the necessary credentials, and we will pass it to the handleSuccess() func
// for the further process
if (res) {
handleSuccess(res.data.param_dict);
}
});
};
return (
<div id="paymentFrm" className="container" style={{ marginTop: "20vh" }}>
<form>
<h1>Payment page</h1>
<div className="form-group">
<label htmlFor="name">Product name</label>
<input
type="text"
className="form-control"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="exampleInputPassword1">Amount</label>
<input
type="text"
className="form-control"
id="amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="exampleInputPassword1">Email</label>
<input
type="text"
className="form-control"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</form>
<button onClick={startPayment} className="btn btn-primary btn-block">
Pay with PayTm
</button>
</div>
);
};
export default App;
Now you can accept payments through Paytm!
DEMO:
NOTE: Replace your Test Merchant ID and Test Merchant Key with a Production Merchant ID and Production Merchant Key to accept live payments.
If you face any problems during this project, you can go ahead and check out the code on my Github.
Click here for Django RF code and click here for React.Js code.
Also, make sure to subscribe to our newsletter on blog.learncodeonline.in and never miss any upcoming articles related to programming just like this one.
I hope this post will help you in your journey. Keep learning!