DEV Community

loading...
IOTA, IIITS

Paytm Payment Gateway Integration in Django

masterashu profile image Ashutosh Chauhan Updated on ・8 min read

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

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'),
]

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

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>

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',
            ],
        },
    },
]

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

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',
]

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>

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>

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)

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

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'

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)

Lets understand the view part by part:

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

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'})

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

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)

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

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)

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>

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)


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'),
]


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

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

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.

Discussion

pic
Editor guide
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:
Collapse
devshree07 profile image
Devshree Patel

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

Collapse
raj1503 profile image
Raj1503

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

Collapse
rajatsainisim profile image
rajatsainisim

This post really helped me.

Collapse
kartavya profile image
Kartavya

[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
raj1503 profile image
Raj1503

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

Collapse
venkateshkb profile image
Venkatesh Balasubramanian

Anybody tried it?? Is that working??

Collapse
raj1503 profile image
Raj1503

in my case not working