DEV Community

Ashutosh Chauhan for IOTA, IIITS

Posted on • Updated on

Paytm Payment Gateway Integration in Django

Paytm Payment Gateway Integration in Django

Synopsis: This is a great way to learn how to integrate payments in your Django application. The official docs from Paytm are here. ​

Note: You would require a Paytm Merchant Account to be able to use the Payment Gateway

The process of sending the request to the gateway is mentioned in the following document:

Image Source: Paytm

We will create an Django App from scratch to achieve our goal.

Step 0: Setting up the Project

First we'll create a virtual environment for out project.$

# Install Django & virtualenv if not already
$ pip install Django virtualenv
# create a new Django project
$ django-admin startproject pay2me
$ cd pay2me
# Initialize a new virtualenv with Py3 since Python 2 is dead
$ virtualenv env --python=python3
# Activate the virtual environment and intall Django
$ source ./env/bin/activate
(env)$ pip install Django
Enter fullscreen mode Exit fullscreen mode

You can use the command $ source ./env/bin/activate to activate the virtual environment.

We would use the default Django User models for the authentication system to save time but you can use custom User model if you wish.

Now let's add login view to our project. open pay2me/urls.py and add entry to login/logout views.

from django.contrib.auth.views import LoginView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', LoginView.as_view(), name='login'),
]
Enter fullscreen mode Exit fullscreen mode

Now to make login and logout pages we first have to migrate our changes and create a superuser. Run the following commands in the terminal.

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
(env)$ python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Now we can run the server by executing python manage.py runserver and visit http://localhost:8000/login to see the login page.

The page will not load since the login page template is missing so we will create a template for LoginView. Create a directory templates in the project's root directory and another directory registration in templates. Now add a file login.html in the registration directory and add the following to it.

<!DOCTYPE html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Login</button>
    </form>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The login page contains contain a form and a csrf_token which will be provided by the LoginView so we don't have to worry about it.

We would also have to modify pay2me/settings.py. Open the file, Navigate to the TEMPLATES setting and add 'templates' in the DIRS list, it should look like this:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        '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',
            ],
        },
    },
]
Enter fullscreen mode Exit fullscreen mode

This would help Django locate our registration/login.html template.

Since the template is all set, now restart the server and go to http://localhost:8000/login and the login form should look like this:

Login Page

Let's continue to the next step.

Step 1: Making the UI

Since we are keeping this concise we will only have two pages:

  • Payment page : Where we will enter the amount we have to pay
  • Callback Page : The response received from Paytm carrying payment status

Lets start by making a new app to organize these features.

(env)$ python manage.py startapp payments
Enter fullscreen mode Exit fullscreen mode

First of all let's add the payments app to the project. To do that we will add 'payments' in the INSTALLED_APPS list in pay2me/settings.py .

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'payments',
]
Enter fullscreen mode Exit fullscreen mode

Now lets add the template files pay.html and callback.html in templates/payments in the payments app directory.

pay.html
<!DOCTYPE html>

<head>
    <title>Payment Home</title>
</head>

<body>
    <h2>Payment Home</h2>
    {% if error %}
    <h3>{{ error }}</h3>
    {% endif %}
    <form method="post">
        {% csrf_token %}
        <label for="username">Username: </label>
        <input type="text" name="username" required>
        <label for="password">Password: </label>
        <input type="text" name="password" required>
        <label for="amount">Amount: </label>
        <input type="number" name="amount" value="100">
        <input type="submit" name="submit" value="Submit" required>
    </form>
</body>

</html>

Enter fullscreen mode Exit fullscreen mode
callback.html
<!DOCTYPE html>
<head>
    <title>Callback</title>
</head>
<body>
    <h2>Callback Messsage: </h2>                    <br>
    <h3> Checksum Verification: {{ message }} </h3> <br>
    MID: {{ MID }}                                  <br>
    TXNID: {{ TXNID }}                              <br>
    ORDERID: {{ ORDERID }}                          <br>
    BANKTXNID: {{ BANKTXNID }}                      <br>
    TXNAMOUNT: {{ TXNAMOUNT }}                      <br>
    CURRENCY: {{ CURRENCY }}                        <br>
    <h3> STATUS: {{ STATUS }} </h3>                 <br>
    RESPCODE: {{ RESPCODE }}                        <br>
    RESPMSG: {{ RESPMSG }}                          <br>
    TXNDATE: {{ TXNDATE }}                          <br>
    GATEWAYNAME: {{ GATEWAYNAME }}                  <br>
    BANKNAME: {{ BANKNAME }}                        <br>
    BIN_NAME: {{ BIN_NAME }}                        <br>
    PAYMENTMODE: {{ PAYMENTMODE }}                  <br>
    CHECKSUMHASH: {{ CHECKSUMHASH }}
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

The Payment page asks for user login information and payment amount, whereas Callback page shows the values of a lot of parameters which would be provided by Paytm upon completion of payment.

Step 2: Make Transaction model

Since any payment related application would require transactions we would create a Transaction model in payments/models.py as follows:

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Transaction(models.Model):
    made_by = models.ForeignKey(User, related_name='transactions', 
                                on_delete=models.CASCADE)
    made_on = models.DateTimeField(auto_now_add=True)
    amount = models.IntegerField()
    order_id = models.CharField(unique=True, max_length=100, null=True, blank=True)
    checksum = models.CharField(max_length=100, null=True, blank=True)

    def save(self, *args, **kwargs):
        if self.order_id is None and self.made_on and self.id:
            self.order_id = self.made_on.strftime('PAY2ME%Y%m%dODR') + str(self.id)
        return super().save(*args, **kwargs)

Enter fullscreen mode Exit fullscreen mode

The transaction model is defined in such a way that the order_id is unique and generated based on date of transaction. We can see there is a checksum which will store the checksum generated by python file.

We overrode the save method to automatically generate order_id from the date and time of the transaction.

Let's run the migrations again to add the Transaction model in the database.

(env)$ python manage.py makemigration
(env)$ python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Step 3: Adding Paytm Settings

Add the following settings to pay2me/settings.py :

PAYTM_MERCHANT_ID = '<your_merchant_id>'
PAYTM_SECRET_KEY = '<your_paytm_secret_key>'
PAYTM_WEBSITE = 'WEBSTAGING'
PAYTM_CHANNEL_ID = 'WEB'
PAYTM_INDUSTRY_TYPE_ID = 'Retail'
Enter fullscreen mode Exit fullscreen mode

Step 4: Create Views for Payments

Paytm has provided a repository for the checksum generation code here but the code is using a deprecated library pycrypto, the following code contains the modified file compatible with the latest pycryptodome library. So download the file and save it in your payments app with file name paytm.py.

Now lets begin the create the initiate_payment view that would receive the username, password and amount. Open payments/views.py and add the following code.

from django.shortcuts import render
from django.contrib.auth import authenticate, login as auth_login
from django.conf import settings
from .models import Transaction
from .paytm import generate_checksum, verify_checksum


def initiate_payment(request):
    if request.method == "GET":
        return render(request, 'payments/pay.html')
    try:
        username = request.POST['username']
        password = request.POST['password']
        amount = int(request.POST['amount'])
        user = authenticate(request, username=username, password=password)
        if user is None:
            raise ValueError
        auth_login(request=request, user=user)
    except:
        return render(request, 'payments/pay.html', context={'error': 'Wrong Accound Details or amount'})

    transaction = Transaction.objects.create(made_by=user, amount=amount)
    transaction.save()
    merchant_key = settings.PAYTM_SECRET_KEY

    params = (
        ('MID', settings.PAYTM_MERCHANT_ID),
        ('ORDER_ID', str(transaction.order_id)),
        ('CUST_ID', str(transaction.made_by.email)),
        ('TXN_AMOUNT', str(transaction.amount)),
        ('CHANNEL_ID', settings.PAYTM_CHANNEL_ID),
        ('WEBSITE', settings.PAYTM_WEBSITE),
        # ('EMAIL', request.user.email),
        # ('MOBILE_N0', '9911223388'),
        ('INDUSTRY_TYPE_ID', settings.PAYTM_INDUSTRY_TYPE_ID),
        ('CALLBACK_URL', 'http://127.0.0.1:8000/callback/'),
        # ('PAYMENT_MODE_ONLY', 'NO'),
    )

    paytm_params = dict(params)
    checksum = generate_checksum(paytm_params, merchant_key)

    transaction.checksum = checksum
    transaction.save()

    paytm_params['CHECKSUMHASH'] = checksum
    print('SENT: ', checksum)
    return render(request, 'payments/redirect.html', context=paytm_params)

Enter fullscreen mode Exit fullscreen mode

Lets understand the view part by part:

def initiate_payment(request):
    if request.method == "GET":
        return render(request, 'payments/pay.html')
Enter fullscreen mode Exit fullscreen mode

The first step is to check the method of the request. When the request method is GET then we will just return the payment page.

try:
        username = request.POST['username']
        password = request.POST['password']
        amount = int(request.POST['amount'])
        user = authenticate(request, username=username, password=password)
        if user is None:
            raise ValueError
        auth_login(request=request,     user=user)
    except:
        return render(request, 'payments/pay.html', context={'error': 'Wrong Account Details or amount'})
Enter fullscreen mode Exit fullscreen mode

Then we verify the username and password entered by the user in the form and tries to log in the user. If the login details are invalid then the view returns back the login page with an error message.

    transaction = Transaction.objects.create(made_by=user, amount=amount)
    transaction.save()
    merchant_key = settings.PAYTM_SECRET_KEY
Enter fullscreen mode Exit fullscreen mode

Next we create a Transaction object for our user and get our merchant key from settings.py.

    params = (
        ('MID', settings.PAYTM_MERCHANT_ID),
        ('ORDER_ID', str(transaction.order_id)),
        ('CUST_ID', str(transaction.made_by.email)),
        ('TXN_AMOUNT', str(transaction.amount)),
        ('CHANNEL_ID', settings.PAYTM_CHANNEL_ID),
        ('WEBSITE', settings.PAYTM_WEBSITE),
        # ('EMAIL', request.user.email),
        # ('MOBILE_N0', '9911223388'),
        ('INDUSTRY_TYPE_ID', settings.PAYTM_INDUSTRY_TYPE_ID),
        ('CALLBACK_URL', 'http://127.0.0.1:8000/callback/'),
        # ('PAYMENT_MODE_ONLY', 'NO'),
    )
    paytm_params = dict(params)

Enter fullscreen mode Exit fullscreen mode

Then we create a dictionary for all the settings for the Paytm Checksum Generator to create a checksum.

    checksum = generate_checksum(paytm_params, merchant_key)

    transaction.checksum = checksum
    transaction.save()

    paytm_params['CHECKSUMHASH'] = checksum

Enter fullscreen mode Exit fullscreen mode

Next after generating the checksum we add the checksum to the paytm_params dictionary as well as the Transaction object.

    return render(request, 'payment/redirect.html', context=paytm_params)

Enter fullscreen mode Exit fullscreen mode

At the end we return the redirect page.

Step 5: Create a Redirect Page

Create a redirect page in payments/templates/payments/redirect.html:

<html>
    <head>
        <title>Merchant Check Out Page</title>
    </head>
    <body>
        <h1>Please do not refresh this page...</h1>
        <form method="post" action="https://securegw-stage.paytm.in/order/process/" name="f1">
            <table>
                <tbody>
                    <input type="hidden" name="MID" value="{{ MID }}">
                    <input type="hidden" name="WEBSITE" value="{{ WEBSITE }}">
                    <input type="hidden" name="ORDER_ID" value="{{ ORDER_ID }}">
                    <input type="hidden" name="CUST_ID" value="{{ CUST_ID }}">
                    <input type="hidden" name="INDUSTRY_TYPE_ID" value="{{ INDUSTRY_TYPE_ID }}">
                    <input type="hidden" name="CHANNEL_ID" value="{{ CHANNEL_ID }}">
                    <input type="hidden" name="TXN_AMOUNT" value="{{ TXN_AMOUNT }}">
                    <input type="hidden" name="CALLBACK_URL" value="{{ CALLBACK_URL }}">
                    <input type="hidden" name="CHECKSUMHASH" value="{{ CHECKSUMHASH }}">
                </tbody>
            </table>
        <script type="text/javascript">
            document.f1.submit();
        </script>
        </form>
    </body>
</html>

Enter fullscreen mode Exit fullscreen mode

The redirect page contains a simple form that contains the fields from the dictionary. We used the javascript code to automatically post the form to the paytm gateway.

Step 6: Create the Callback View

The callback view is the view which would be called when paytm will respond with the status of the Transaction.

Add the following callback view in the payments/views.py

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def callback(request):
    if request.method == 'POST':
        received_data = dict(request.POST)
        paytm_params = {}
        paytm_checksum = received_data['CHECKSUMHASH'][0]
        for key, value in received_data.items():
            if key == 'CHECKSUMHASH':
                paytm_checksum = value[0]
            else:
                paytm_params[key] = str(value[0])
        # Verify checksum
        is_valid_checksum = verify_checksum(paytm_params, settings.PAYTM_SECRET_KEY, str(paytm_checksum))
        if is_valid_checksum:
            received_data['message'] = "Checksum Matched"
        else:
            received_data['message'] = "Checksum Mismatched"
            return render(request, 'payments/callback.html', context=received_data)
        return render(request, 'payments/callback.html', context=received_data)


Enter fullscreen mode Exit fullscreen mode

The callback view receives a post request from the paytm server. Then we retrieve the received data in a dictionary and and verify the checksum sent from paytm as to prevent forged requests. We then return the page with the details of the transaction and the message.

Step 7: Creating the routes.

As the final step, Let's make the URLs for the views and get this long tutorial over. Create/open urls.py in the payments app. Add the following routes.

from django.urls import path
from .views import initiate_payment, callback

urlpatterns = [
    path('pay/', initiate_payment, name='pay'),
    path('callback/', callback, name='callback'),
]


Enter fullscreen mode Exit fullscreen mode

Now let's include these URLs in the pay2me/urls.py

from django.urls import include
urlpatterns = [
    # include 
    path('', include('payments.urls'))
]

Enter fullscreen mode Exit fullscreen mode

Test Run: Try out a Payment

Now let's run python manage.py runserver and go to http://localhost:8000/pay/ to create a payment.

Fill the form.

Payment page

This will redirect to the paytm payment gateway. Use any dummy payment methods.

Paytm Payment Gateway

You can also choose the status (Success/Failure) of the Transaction

Bank Demo Page

Then you will finally land on the callback page which will show you the status of the transaction.

Callback page

Github: Sample

Here is the link to the above sample code on Github.

Top comments (20)

Collapse
 
anandvmclt profile image
Anand

Thanks for this article.it worked for me. But it should have some small corrections.
1.) in step -1 "pay.html" make it Form method = POST.
2.) in step- 4 change " transaction = Transaction.objects.create(made_by=user, amount=20000)" shoud be change to amount = amount.
3.) on Step -6 , call back view has to add any HTTP responce.
# Like This
if is_valid_checksum:
received_data['message'] = "Checksum Matched"
return render(request, 'payments/callback.html', context=received_data)

    else:
Enter fullscreen mode Exit fullscreen mode
Collapse
 
devshree07 profile image
Devshree Patel

This post really helped me. Thank you for sharing! keep it up!

Collapse
 
raj1503 profile image
Raj1503

I am getting error MID
invalid: This MID is not available on our staging environment

Collapse
 
rajatsaxena14 profile image
rajat-saxena14

have you solved this problem? If yes, then please assist.

Collapse
 
akshayneje2823 profile image
Akshay Neje

I am facing the same issue can you please help me

Thread Thread
 
scofieldidehen profile image
Scofield Idehen

What is your issue, can you send me a repo to help you?

Thread Thread
 
akshayneje2823 profile image
Akshay Neje • Edited

I was integrating paytm payment getway for our MERN app
and got this error MID
invalid: This MID is not available on our staging environment.

can you please guide if have you solved this issue

Collapse
 
swapnilchand profile image
Swapnil Chand

Create a directory templates in the project's root directory and another directory registration in templates. Now add a file login.html in the registration directory and add the following to it.

Can someone say what is the meaning of preojects root directory in the above lines??

Collapse
 
kartavya profile image
Kartavya • Edited

[ModuleNotFoundError: No module named 'Crypto' ] this error comes while run server. I install Crypto module using pip. but It's not working. please give solution for this error.

Collapse
 
sandy_codes_py profile image
Santhosh (sandy inspires)

pip install paytmchecksum
instead of using the deprecated library pycrypto

Collapse
 
nileshmeharkar profile image
Nilesh-Meharkar

Please install pycryptodome

pip install pycryptodome

Collapse
 
syedameenuddin profile image
Syed Ameenuddin • Edited

try installing this
pip install pycryptodome

Collapse
 
venkateshkb profile image
Venkatesh Balasubramanian

Anybody tried it?? Is that working??

Collapse
 
sandy_codes_py profile image
Santhosh (sandy inspires)

yup!! works for me

Collapse
 
raj1503 profile image
Raj1503

in my case not working

Collapse
 
samankapur profile image
SamanKapur

Quick update - WhenThen provides a single SDK to access all your payment processors. They aim to reduce implementation headaches by doing a single integration once. Check it out here: documentation.whenthen.com/checkout

Collapse
 
rajatsainisim profile image
rajatsainisim

This post really helped me.

Collapse
 
apoorvashukla profile image
Apoorva Kumar Shukla
Collapse
 
apoorvashukla profile image
Apoorva Kumar Shukla

Great

Collapse
 
raj1503 profile image
Raj1503

sir i am getting Valuel error at /pay/ is incorrect AES key length..please help me