Integrating Khalti with Django is pretty simple! It might have some learning curve for individual developers but trust the process!
python3 -m venv .venv
Activating the env:
source .venv/bin/activate
python -m venv .venv
Activating the env:
source .venv/Scripts/activate
Install Django with pip:
pip install django
django-admin --version
django-admin startproject {project_name} .
Create your Django App:
django-admin startapp {app_name}
INSTALLED_APPSAdd your {app_name}, e.g. 'home':
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'home',
]
In {project_name}/urls.py, add:
from django.urls import path, include
Add this in urlpatterns:
path('', include('{app_name}.urls'))
For more detailed information visit the official Django Docs (v5.2).
Add urls.py in {app_name} folder.
Create a templates folder in {app_name}. Your folder structure should look like:
{app_name}/templates
Add home and payment folders with their respective HTML files:
{app_name}/templates/home/home.html
{app_name}/templates/payment/payment.html
Variable names:
name — for sending data to the backend ({app_name}/views.py)id — for labelWe'll use a Custom User model via AbstractUser and UUID to generate unique IDs.
In {project_name}/settings.py:
AUTH_USER_MODEL = "home.Custom_user"
{app_name}/models.py:from django.db import models
from django.contrib.auth.models import AbstractUser
import uuid
class Custom_user(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
age = models.IntegerField(null=True, blank=True)
contact = models.IntegerField(null=True, blank=True)
class Payment(models.Model):
# saving purchase details from our side !
class Status(models.TextChoices):
SUCESS = "SUCESS", "Sucess"
PENDING = "PENDNG", "Pending"
FAILURE = "FAILURE", "Failure"
INITIATED = "INITIATED", "Initiated"
REFUNDED = "REFUNDED", 'Refunded'
user = models.ForeignKey(Custom_user, on_delete=models.CASCADE)
purchase_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
purchase_order_name = models.CharField(verbose_name='purchase_order_name')
purchse_status = models.CharField(choices=Status.choices, default=Status.INITIATED)
purchase_amount = models.FloatField(null=True)
initiated_at = models.DateTimeField(auto_now_add=True)
# saving purchase Details from Khalti Side !
pidx = models.CharField(verbose_name='pidx', editable=False)
khalti_status = models.CharField(verbose_name='Status From Khalti', editable=False)
khalti_transaction_id = models.CharField(verbose_name='Khalti Transaction ID', editable=False)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Transaction of user {self.user.username} => Status : {self.purchse_status}"
python manage.py makemigrationspython manage.py migrateadmin.py:from django.contrib import admin
from .models import Custom_user, Payment
admin.site.register(Custom_user)
class PAYMENT_ADMIN(admin.ModelAdmin):
list_display = [
'user',
'purchase_id',
'purchse_status',
'purchase_amount',
'khalti_status',
'pidx',
'khalti_transaction_id',
'initiated_at',
'updated_at'
]
admin.site.register(Payment, PAYMENT_ADMIN)
In {app_name}/views.py, render the home page first, passing user ID as context:
from django.http import HttpResponse
from django.shortcuts import render
def home(request):
context = {
'id': request.user.id
}
return render(request, 'home/home.html', context)
{app_name}/urls.py):from django.urls import path
from . import views
app_name = "home"
urlpatterns = [
path('', views.home, name='home'),
path("payment/", views.payment_gate, name="payment_gate"),
path('payment/validation/', views.payment_validation, name="payment_validation"),
]
Every payment request should first be initiated from the merchant as a server-side POST request. Upon success, a unique request identifier called pidx is provided.
POST /epayment/initiate/
| URL | Method | Format | Authorization |
|---|---|---|---|
/epayment/initiate/ | POST | Required. application/json | Required |
| Field | Required | Description |
|---|---|---|
return_url | Yes | Landing page after the transaction. Must contain a valid URL. |
website_url | Yes | The URL of the website. Must contain a valid URL. |
amount | Yes | Total payable amount excluding service charge. Amount must be in Paisa. |
purchase_order_id | Yes | Unique identifier for the transaction generated by merchant. |
purchase_order_name | Yes | The name of the product. |
import requests
import json
url = "https://dev.khalti.com/api/v2/epayment/initiate/"
payload = json.dumps({
"return_url": "http://example.com/",
"website_url": "https://example.com/",
"amount": "1000",
"purchase_order_id": "1231231231kjhgjkgh",
"purchase_order_name": "test101",
"customer_info": {
"name": "bla bla",
"email": "example@gmail.com",
"phone": "9800000000"
}
})
headers = {
'Authorization': 'key live_secret_key_68791341fdd94846a146f0457ff7b455',
'Content-Type': 'application/json',
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
return_url — Redirects the user back to your website after paymentwebsite_url — Your current page URLOnce you have your key, create a .env file and add it there. Never share it publicly!
pip install python-decouple
Import your secret keys in settings.py:
Key variables used:
KHALTI_BASE_URL → https://dev.khalti.com/api/v2KHALTI_PAYMENT_VERIFICATION_URL → /epayment/lookup/KHALTI_URL → https://dev.khalti.com/api/v2/epayment/initiate/Things to consider:
order_id as a string
def payment_gate(request):
Debug = False
if request.method == 'POST':
amount = request.POST.get('payment_amount')
purchse_name = request.POST.get('purchase_name')
url = settings.KHALTI_URL
AMOUNT = int(amount) * 100
payment_obj = Payment.objects.create(
user=request.user,
purchase_order_name=purchse_name,
purchase_amount=amount
)
payload = json.dumps({
"return_url": request.build_absolute_uri(reverse("home:payment_validation")),
"website_url": request.build_absolute_uri('/'),
"amount": AMOUNT,
"purchase_order_id": str(payment_obj.purchase_id),
"purchase_order_name": payment_obj.purchase_order_name,
"customer_info": {
"name": payment_obj.user.username,
"email": payment_obj.user.email,
"phone": payment_obj.user.contact
}
})
headers = {
'Authorization': f'key {settings.SECRET_KEY}',
'Content-Type': 'application/json',
}
response = requests.request("POST", url, headers=headers, data=payload)
if Debug:
return JsonResponse(response.json(), safe=False)
else:
return redirect(response.json()['payment_url'])
else:
return render(request, 'payment/payment.html')
POST /epayment/lookup/
| URL | Method | Format | Authorization |
|---|---|---|---|
/epayment/lookup/ | POST | Required. application/json | Required |
POST request to https://dev.khalti.com/api/v2/epayment/lookup/ with the pidx{pidx, total_amount, status, transaction_id, fee, refund}Completed and updates our database accordingly
{
"detail": "Invalid token.",
"status_code": 401
}
pidx:{
"detail": "Not found.",
"error_key": "validation_error"
}
key prefix in Authorization header:{
"detail": "Authentication credentials were not provided.",
"status_code": 401
}
/payment/ → Go to the payment page/admin/ → Visit the admin page