Integrating Paystack payments in Django website

Integrating Paystack payments in Django website

This article was originally published on dev.to and has been republished here with permission.

TABLE OF CONTENTS

Introduction

Paystack is a payment gateway that helps individuals and businesses in Africa to accept payments from anywhere in the world. In this guide, I will be showing you how to use Paystack as a third-party API to facilitate payments in your Django website.

Prerequisites

To follow through with this guide you will need:

  • A good understanding of Python
  • Basic understanding of Django
  • Basic understanding of Html
  • Usage of the Python request library
  • A Paystack account

Now, let's dive into the guide, all codes can be found on Github in this repository.

Project Setup

Create a folder, start a virtual environment and start basic project create a virtual environment using virtualenv env activate the virtual environment using source env/Scripts/activate install django using pip install django this will install the latest version of django, however I am going to use version 3.0 in this guide, to install that do pip install django==3.0

Create a project and install apps Now start the project using django-admin startproject zcore . zcore is the name of the project and dot is used to start the project in the current directory. Next step is two create two apps, one for authentication and another to handle all payments related code

python manage.py startapp accounts
python manage.py startapp payments

project setup 1

project setup 2

project setup 3

Add the newly installed apps in settings.py Also add paystack secret and public keys in settings.py

PAYSTACK_SECRET_KEY = ""
PAYSTACK_PUBLIC_KEY = ""

where to find them on paystack

keys on paystack

Navigate into both folders created and add a new file called urls.py and add the following code

from django.urls import path

urlpatterns = [

]

Include these urls in main url file zcore.urls Your file should look like this

from  django.contrib  import  admin
from  django.urls  import  path, include

urlpatterns  = [
path('admin/', admin.site.urls),
path('accounts/', include("accounts.urls")),
path('payments/', include("payments.urls")),
]

Creating models

Next step is to create a wallet model to keep track of funds deposited by a user and a payment model to describe what a payment is. In payments.model

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import secrets
from .paystack  import  Paystack

# Create your models here.
class UserWallet(models.Model):
    user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
    currency = models.CharField(max_length=50, default='NGN')
    created_at = models.DateTimeField(default=timezone.now, null=True)

    def __str__(self):
        return self.user.__str__()

class Payment(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
    amount = models.PositiveIntegerField()
    ref = models.CharField(max_length=200)
    email = models.EmailField()
    verified = models.BooleanField(default=False)
    date_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-date_created',)

    def __str__(self):
        return f"Payment: {self.amount}"

    def save(self, *args, **kwargs):
        while not self.ref:
            ref = secrets.token_urlsafe(50)
            object_with_similar_ref = Payment.objects.filter(ref=ref)
            if not object_with_similar_ref:
                self.ref = ref

        super().save(*args, **kwargs)

We will need another helper class to handle payment verification, so in the payments folder create a file called paystack.py paste this block of code in the file

from django.conf import settings
import requests

class Paystack:
    PAYSTACK_SK = settings.PAYSTACK_SECRET_KEY
    base_url = "https://api.paystack.co/"

    def verify_payment(self, ref, *args, **kwargs):
        path = f'transaction/verify/{ref}'
        headers = {
            "Authorization": f"Bearer {self.PAYSTACK_SK}",
            "Content-Type": "application/json",
        }
        url = self.base_url + path
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            response_data = response.json()
            return response_data['status'], response_data['data']

        response_data = response.json()

        return response_data['status'], response_data['message']

you will need to install requests for this so do a quick pip install requests with that we can now add a verify payment property on our payment model

def amount_value(self):
        return int(self.amount) * 100

def verify_payment(self):
    paystack = Paystack()
    status, result = paystack.verify_payment(self.ref, self.amount)
    if status:
        if result['amount'] / 100 == self.amount:
            self.verified = True
        self.save()
    if self.verified:
        return True
    return False

User registration and login

For the registration and login, I will be doing something very simple as it is not the focus of this guide. Paste the code in accounts.views

from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from payments.models import UserWallet
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.decorators import login_required

# Create your views here.
def register(request):
    if request.method == "POST":
        username = request.POST['username']
        password = request.POST['password']
        user = User.objects.create(username=username, password=password)
        UserWallet.objects.create(user=user)
    return render(request, "register.html")

def login_view(request):    
    if request.method == "POST":
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        if user is not None:
            login(request, user)
                    return redirect('initiate_payment')
        else:
            return redirect('login')

    return render(request, 'login.html')

@login_required(login_url="login")
def logout_view(request):
    logout(request)
    return redirect('login')

Add the corresponding url paths for the views in urls.py

from  django.urls  import  path
from . import views

urlpatterns = [
    path('register/', views.register, name="register"),
    path('login/', views.login_view, name="login"),
    path('logout/', views.logout_view, name="logout"),
]

We will use dummy login and register page from bootstrap, file can be found in the github repo .

Setup templates dirs by adding 'DIRS': [os.path.join(BASE_DIR, 'templates')] in settings.py

template dirs settings

At this point you can run python manage.py runserver and navigate to 127.0.0.1:8000/accounts/register in your browser window to see the registration page.

register page

You will see some warnings, to get rid of them makemigrations and migrate using;

python manage.py makemigrations 
python manage.py migrate

Create a superuser so that we can login to django admin via 127.0.0.1:8000/admin to see all the magic happening.

migrations

python manage.py createsuperuser

To see all payment objects in admin area register the payments model in payments.admin

from  django.contrib  import  admin
from .models  import  Payment, UserWallet

class  PaymentAdmin(admin.ModelAdmin):
    list_display  = ["id", "ref", 'amount', "verified", "date_created"]

admin.site.register(Payment, PaymentAdmin)
admin.site.register(UserWallet)

Implementing deposit view

Create two views, one to initiate the payment and another to verify payments. In payments.views

from django.shortcuts import render, redirect
from .models import Payment, UserWallet
from django.conf import settings

def initiate_payment(request):
    if request.method == "POST":
        amount = request.POST['amount']
        email = request.POST['email']

        pk = settings.PAYSTACK_PUBLIC_KEY

        payment = Payment.objects.create(amount=amount, email=email, user=request.user)
        payment.save()

        context = {
            'payment': payment,
            'field_values': request.POST,
            'paystack_pub_key': pk,
            'amount_value': payment.amount_value(),
        }
        return render(request, 'make_payment.html', context)

    return render(request, 'payment.html')


def verify_payment(request, ref):
    payment = Payment.objects.get(ref=ref)
    verified = payment.verify_payment()

    if verified:
        user_wallet = UserWallet.objects.get(user=request.user)
        user_wallet.balance += payment.amount
        user_wallet.save()
        print(request.user.username, " funded wallet successfully")
        return render(request, "success.html")
    return render(request, "success.html")

In payment.urls

from django.urls import path
from . import views

urlpatterns = [
    path('initiate-payment/', views.initiate_payment, name='initiate_payment'),
    path('verify-payment/<str:ref>/', views.verify_payment, name='verify_payment'),
]

The process flow goes like this;

  • User inputs an amount to fund his wallet
  • Clicks on pay then it takes them to page that displays the payment details
  • Then proceeds to pay by clicking the pay button, this will open a payment modal handled by paystack, when the process is complete paystack will send a request to the callback URL which we set up to confirm the details of the transaction.

payment flow 1

payment flow 2

payment flow 3

payment flow 4

payment flow 5 Code for the payment pages can be found in the repo

Conclusion

We have successfully integrated Paystack into our Django application and deposited an amount successfully.

All codes can be found in this repository

Reference

Paystack docs

Connect with me on Twitter | Github I hope this was helpful

Thanks for reading.