Integrating Paystack payments in Django website
Tags: django, tutorial, python, api
Published: 2022-10-21 20:57:11

 **TABLE OF CONTENTS**
-   [Introduction](#introduction)
-   [Prerequisites](#prerequisites)
-   [Project Setup](#project-setup)
-   [Creating models](creating-models)
-   [User registration and login](#user-registration-and-login)
-   [Implementing deposit view](#Implementing-deposit-view)
-   [Conclusion](#conclusion)
-   [Reference](#reference)


## Introduction
[Paystack](https://paystack.com/) 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](https://docs.python-requests.org/en/latest/)  library
-  A Paystack account

Now, let's dive into the guide, all codes can be found on Github in this  [repository](https://github.com/skynette/paystack-django-integration).

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/656o76yyofjt2stcwvyg.png)

![project setup 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90btw7pbspv0lz5zvqta.png)

![project setup 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28nv82z3g1pezpzf9mom.png)


Add the newly installed apps in `settings.py`
Also add paystack secret and public keys in `settings.py`
```python
PAYSTACK_SECRET_KEY = ""
PAYSTACK_PUBLIC_KEY = ""
```
where to find them on paystack

![keys on paystack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k11ko9qb5u7p878vb0da.png)

Navigate into both folders created and add a new file called `urls.py` and add the following code
```python
from django.urls import path

urlpatterns = [

]
```
Include these urls in main url file `zcore.urls` 
Your file should look like this
```python
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`
```python
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
```python
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
```python
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`
```python
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`
```python
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](https://github.com/skynette/paystack-django-integration) .

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


![template dirs settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4hvfsecigztpzqfain8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/in32zu0ob3dzeub9sdhy.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j1vpidpbo0qi2mg1bavc.png)

    python manage.py createsuperuser
To see all payment objects in admin area register the payments model in `payments.admin`
```python
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`
```python
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`
```python
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0ws3ec3pxov4y11jx79.png)

![payment flow 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nszvsc7vwedr10ggdtzg.png)

![payment flow 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4fv75massei1n4b3bkn.png)

![payment flow 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6d0q5z38sjyd95mcps23.png)

![payment flow 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znger2hro0glilk30kip.png)
Code for the payment pages can be found in the [repo](https://github.com/skynette/paystack-django-integration)

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

All codes can be found in this [repository](https://github.com/skynette/paystack-django-integration)

## Reference
[Paystack docs](https://paystack.com/docs/)

Connect with me on  [**Twitter**](https://twitter.com/cut3josh)  |  [**Github**](https://github.com/skynette/)
I hope this was helpful

Thanks for reading.