LEARNING
NOOBI
June 17 ,2023
In this tutorial, we will explore how to integrate the eSewa payment gateway into a Django project. By the end, you'll have a functioning payment system allowing customers to make payments using eSewa.
Setting Up the Django Project:
Before we dive into the payment integration, let's set up our Django project. Follow these steps:
django-admin startproject mainproject
cd mainproject
python manage.py startapp payment
In the 'payment' app, we need to define the models required to store order details. Open the 'payment/models.py' file and add the following code:
(Note that we are using a really simple implementation of Order model)
from django.db import models
class Order(models.Model):
name = models.CharField(max_length=50)
order_id = models.CharField(max_length=6, null=True, unique=True)
total_price = models.IntegerField()
is_paid = models.BooleanField(default=False)
paid_amount = models.IntegerField(null=True, blank=True)
This model represents an order and includes fields for the name, order ID, total price, payment status, and paid amount.
By default, the is_paid field is set to False, indicating that the order has not been paid yet. The paid_amount field is initially set to None. After a successful payment with eSewa, is_paid will be set to True, and the paid_amount will reflect the amount paid with esewa for the order.
Creating the Views:
Now, let's create the views for displaying the order list and the order checkout page. Open the 'payment/views.py' file and add the following code:
from django.shortcuts import render
from .models import Order
def orders_list(request):
orders = Order.objects.all()
context = {"orders": orders}
return render(request, 'payment/orders.html', context)
def order_checkout(request, id):
order = Order.objects.get(id=id)
context = {"order": order}
return render(request, 'payment/order_checkout.html', context)
The orders_list view retrieves all orders from the database and passes them to the 'payment/orders.html' template.
The order_checkout view retrieves a specific order based on the provided ID and passes it to the 'payment/order_checkout.html' template.
Defining URL Patterns:
Now, let's define the URL patterns for our views. Open the 'payment/urls.py' file and add the following code:
from django.urls import path
from .views import *
urlpatterns = [
path('', orders_list, name="orders_list"),
path('checkout/<int:id>/', order_checkout, name="order_checkout"),
]
The templates code :
Orders.html
{% extends 'payment/base.html' %}
{% block content %}
<h3>Orders List</h3>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Paid</th>
<th scope="col">Paid Amount</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<th scope="row">{{order.id}}</th>
<td>{{order.name}}</td>
<td>{{order.is_paid}}</td>
<td>{{order.paid_amount}}</td>
<td><a href="{% url 'order_checkout' order.id %}"><button class="btn btn-primary">Check Out</button></a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}
order_checkout.html
{% extends 'payment/base.html' %}
{% block content %}
<h2 class='mt-4'>Order Checkout</h2>
<div class="card">
<div class="card-body">
<p>Name : {{order.name}}</p>
<p>Price : {{order.total_price}}</p>
<p>Paid: {{order.is_paid}}</p>
<!-- This Form redirects us to esewa to carry out payment process -->
<form action="https://uat.esewa.com.np/epay/main" method="POST">
<input value="10" name="tAmt" type="hidden">
<input value="10" name="amt" type="hidden">
<input value="0" name="txAmt" type="hidden">
<input value="0" name="psc" type="hidden">
<input value="0" name="pdc" type="hidden">
<input value="EPAYTEST" name="scd" type="hidden">
<input value="{{order.order_id}}" name="pid" type="hidden">
<input value="http://127.0.0.1:8000/esewa-callback/" type="hidden" name="su">
<input value="http://127.0.0.1:8000/payment-failed/" type="hidden" name="fu">
<input value="Pay With Esewa" type="submit" class="btn btn-success">
</form>
</div>
</div>
{% endblock content %}
Explaining Esewa Form:
If you click on "Pay With Esewa" button, you will be redirected to esewa website for payment.
Credentials For Test Environment:
eSewa ID: 9806800001/2/3/4/5
MPIN: 1234
OTP: 123456
How is this working ?
<!--This url is for test environment -->
<form action="https://uat.esewa.com.np/epay/main" method="POST">
<!----- For Production --->
<form action="https://esewa.com.np/epay/main" method="POST">
We are calling POST method to given url where we will be redirected to after button is submitted.
Since it's POST request, what data should we give to ESEWA for it to know our payment information ?
We need to give following data to ESEWA.
<input value="10" name="tAmt" type="hidden">
<input value="10" name="amt" type="hidden">
<input value="0" name="txAmt" type="hidden">
<input value="0" name="psc" type="hidden">
<input value="0" name="pdc" type="hidden">
<!-- Name of the merchant ( For Test use 'EPAYTEST') -->
<input value="EPAYTEST" name="scd" type="hidden">
<!-- Unique ID of the product to identify the product
( Make it unique otherwise the payment will fail ) --->
<input value="{{order.order_id}}" name="pid" type="hidden">
<!-- Esewa will redirect to this url if the payment succeeds -->
<input value="http://127.0.0.1:8000/esewa-callback/" type="hidden" name="su">
<!-- Esewa will redirect to this url if the payment fails -->
<input value="http://127.0.0.1:8000/payment-failed/" type="hidden" name="fu">
Note: value of amt + txAmt + psc must be equal to value of tAmt. Otherwise the payment will fail.
Parameter Name | Description |
---|---|
amt | Amount of product or item or ticket etc |
txAmt | Tax amount on product or item or ticket etc |
psc | Service charge by merchant on product or item or ticket etc |
pdc | Delivery charge by merchant on product or item or ticket etc |
tAmt | Total payment amount including tax, service and deliver charge. [i.e tAmt = amt + txAmt + psc + tAmt ] |
pid | A unique ID of product or item or ticket etc |
scd | Merchant code provided by eSewa |
su | Success URL: a redirect URL of merchant application where customer will be redirected after SUCCESSFUL transaction |
fu | Failure URL: a redirect URL of merchant application where customer will be redirected after FAILURE or PENDING transaction |
If the payment is successfull, we will be redirected to the success url that we provided on the form.
Esewa will add some query paramaters on the urls to add information about the payment which we need to verify the transaction .
http://success_callback_url/?q=su&oid=ee2c&amt=100&refId=000AE01
?q=su&oid=ee2c&amt=100&refId=000AE01
are added to the success url by esewa.
We will use this value to hit another api for the verification of the transaction .
How should we verify the transaction?
To make sure that the transaction is correct and valid , we need to check the correctness using an API provided by the ESEWA .
Anyone can copy the success url and change the information. So before updating the paid status of order or any product, we first need to verify that the transaction is authentic.
We need to check the correctness of the transaction with given url:
"https://uat.esewa.com.np/epay/transrec"
As a body, pass
{
'amt': 'amount', //total amount of service
'scd': 'merchant_id', //merchant id
'rid': 'rid', //reference id provided by esewa
'pid':'"uniqueid', //unique id of the product
}
Implementation in Django View
The implementation for the callback verification and updating of the status of the model can be done in various ways.
It depends whether we are making it an API or using with MVT pattern. Since we are showing examples on MVT , the views would be :
Install requests and xmltodict
pip install requests
pip install xmltodict
from django.shortcuts import render,redirect,get_object_or_404
from .models import Order
import requests
import xmltodict
def esewa_callback_view(request):
oid = request.GET.get("oid")
amt = request.GET.get("amt")
refId = request.GET.get("refId")
url ="https://uat.esewa.com.np/epay/transrec"
data = {
'amt': amt,
'scd': 'EPAYTEST',
'rid': refId,
'pid':oid,
}
response = requests.post(url, data=data)
json_response =xmltodict.parse(response.content)
status = json_response["response"]["response_code"]
if status != "Success":
return redirect("payment_failed")
order = get_object_or_404(Order,order_id=oid)
# Do this in production (EXPLAINED BELOW IN DETAIL )
# Esewa test allows price upto Rs. 100 only
# if order.total_price != int(amt):
# return redirect("payment_failed")
order.is_paid=True
order.paid_amount =int(float(amt))
order.save()
return render(request,'payment/esewa-callback.html')
def payment_failed(request):
return render(request,'payment/payment_failed.html')
The verify api gives response in xml . To covert that to json, use xmltodict package.
If the transaction is authentic then, the response would be "Success" else it will be failed.We are updating the product status only if transaction is verified.
But What if the transaction is paid less than the actual cost of the product? In this the case the transaction will be authentic but the product shouldn't be changed cause the exact amount must be paid.
For that :
# In Test environment, Esewa only allows max 100 rupees
# Obviously this cannot handle every product price
# so either check this condition on live environment or
# make every product price lower than Rs 100
if order.total_price != int(amt):
return redirect("payment_failed")
# update product here
If this view is successfully rendered, it means it's a valid payment otherwise Payment Failed Page will be shown.
In this way , We can Integrate Esewa And Django using MVT Approach.
Feel Free to leave comment for questions on any confusion.