To accept Paystack payments in Django, create a view that initializes a transaction with the Paystack API using the requests library and your secret key. Return the authorization URL to your template, redirect the user to Paystack checkout, then verify the payment in a callback view before granting value.
Install the requests library if you have not already:
pip install requests python-decouple
Add your Paystack keys to a .env file:
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
PAYSTACK_PUBLIC_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxx
Add the keys to your Django settings:
# settings.py
from decouple import config
PAYSTACK_SECRET_KEY = config('PAYSTACK_SECRET_KEY')
PAYSTACK_PUBLIC_KEY = config('PAYSTACK_PUBLIC_KEY')
# payments/models.py
from django.db import models
import uuid
class Order(models.Model):
reference = models.CharField(max_length=100, unique=True)
email = models.EmailField()
amount = models.IntegerField(help_text='Amount in kobo')
currency = models.CharField(max_length=3, default='NGN')
paid = models.BooleanField(default=False)
paid_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
if not self.reference:
self.reference = 'order_' + str(uuid.uuid4().hex[:12])
super().save(*args, **kwargs)
def __str__(self):
return self.reference
Run migrations after creating the model:
python manage.py makemigrations payments
python manage.py migrate
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
# payments/views.py
import requests
from django.conf import settings
from django.shortcuts import redirect, render
from django.http import JsonResponse
from .models import Order
def initialize_payment(request):
if request.method == 'POST':
email = request.POST.get('email')
amount = int(request.POST.get('amount', 5000))
order = Order.objects.create(
email=email,
amount=amount * 100, # Convert to kobo
)
headers = {
'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
}
data = {
'email': email,
'amount': order.amount,
'reference': order.reference,
'callback_url': request.build_absolute_uri('/payments/callback/'),
}
response = requests.post(
'https://api.paystack.co/transaction/initialize',
json=data,
headers=headers,
)
result = response.json()
if result.get('status'):
return redirect(result['data']['authorization_url'])
return render(request, 'payments/checkout.html', {
'error': result.get('message', 'Initialization failed'),
})
return render(request, 'payments/checkout.html')
# payments/views.py (continued)
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
def payment_callback(request):
reference = request.GET.get('reference') or request.GET.get('trxref')
if not reference:
return render(request, 'payments/failed.html', {'error': 'Missing reference'})
headers = {
'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
}
response = requests.get(
'https://api.paystack.co/transaction/verify/' + reference,
headers=headers,
)
result = response.json()
if result.get('status') and result['data']['status'] == 'success':
try:
order = Order.objects.get(reference=reference)
except Order.DoesNotExist:
return render(request, 'payments/failed.html', {'error': 'Order not found'})
# Check amount
if result['data']['amount'] != order.amount:
return render(request, 'payments/failed.html', {'error': 'Amount mismatch'})
# Idempotent fulfillment
if not order.paid:
order.paid = True
order.paid_at = timezone.now()
order.save()
return render(request, 'payments/success.html', {
'reference': reference,
'amount': order.amount / 100,
})
return render(request, 'payments/failed.html', {
'error': 'Payment was not successful',
})
# payments/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('checkout/', views.initialize_payment, name='checkout'),
path('callback/', views.payment_callback, name='payment_callback'),
]
# project/urls.py
from django.urls import path, include
urlpatterns = [
path('payments/', include('payments.urls')),
]
<!-- templates/payments/checkout.html -->
<form method="post" action="{% url 'checkout' %}">
{% csrf_token %}
<input type="email" name="email" placeholder="Email" required />
<input type="hidden" name="amount" value="5000" />
<p>Amount: NGN 5,000</p>
<button type="submit">Pay Now</button>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
</form>
The form posts to your Django view, which initializes the transaction and redirects to Paystack. The CSRF token is needed because this is a Django form submission, not a Paystack request.
@csrf_exempt on your webhook view. Paystack requests will not have Django CSRF tokens.sk_live_ in production. Keep test keys for development only.KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course