Paytm Payment Gateway Integration in Django
Ashutosh Chauhan
Posted on February 2, 2020
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:
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.
This will redirect to the paytm payment gateway. Use any dummy payment methods.
You can also choose the status (Success/Failure) of the Transaction
Then you will finally land on the callback page which will show you the status of the transaction.
Github: Sample
Here is the link to the above sample code on Github.
Posted on February 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.