Paytm Payment Gateway Integration in Django REST framework and ReactJs

Subscribe to our newsletter and never miss any upcoming articles

Listen to this article

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.

paytmDjango.png

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:

paytmapi3.PNG

If it pops up an error message, then log out and again log into your account.

paytm3.PNG 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:

paytm4.PNG

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): paytm7.png 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!

My LinkedIn and GitHub.

Jobin S's photo

great work. really appreciable!

Shubham Waje's photo

Thank you so much! Keep supporting.

Jack Roy's photo

The best article I have found on internet.

Shubham Waje's photo

Thanks for your vote of confidence. It motivates us to share more such articles with the community. Keep supporting!