Accept Payments with Paystack in Django REST Framework
To accept Paystack payments in DRF, create an APIView that receives email and amount via a serializer, initializes a transaction with the Paystack API, and returns the authorization URL as JSON. Your SPA or mobile frontend redirects to Paystack or opens the inline popup. A separate APIView verifies the payment.
Setup
Install DRF and requests if not already installed:
pip install djangorestframework requests python-decouple
Add 'rest_framework' to INSTALLED_APPS in settings.py. Configure your Paystack keys as shown in the Django guide.
Payment Serializer
# payments/serializers.py
from rest_framework import serializers
class InitializePaymentSerializer(serializers.Serializer):
email = serializers.EmailField()
amount = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=1)
class VerifyPaymentSerializer(serializers.Serializer):
reference = serializers.CharField(max_length=100)
Initialize Transaction APIView
# payments/views.py
import uuid
import requests as http_requests
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import InitializePaymentSerializer
from .models import Order
class InitializePaymentView(APIView):
def post(self, request):
serializer = InitializePaymentSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
email = serializer.validated_data['email']
amount = serializer.validated_data['amount']
reference = 'order_' + uuid.uuid4().hex[:12]
# Save order to database
order = Order.objects.create(
email=email,
amount=int(amount * 100),
reference=reference,
)
# Call Paystack
response = http_requests.post(
'https://api.paystack.co/transaction/initialize',
json={
'email': email,
'amount': order.amount,
'reference': reference,
'callback_url': request.build_absolute_uri('/api/payments/callback/'),
},
headers={
'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
)
result = response.json()
if not result.get('status'):
return Response(
{'error': result.get('message', 'Initialization failed')},
status=status.HTTP_400_BAD_REQUEST,
)
return Response({
'authorization_url': result['data']['authorization_url'],
'access_code': result['data']['access_code'],
'reference': result['data']['reference'],
})
Verify Payment APIView
# payments/views.py (continued)
from django.utils import timezone
class VerifyPaymentView(APIView):
def get(self, request):
reference = request.query_params.get('reference')
if not reference:
return Response(
{'error': 'Missing reference'},
status=status.HTTP_400_BAD_REQUEST,
)
# Call Paystack verify
response = http_requests.get(
'https://api.paystack.co/transaction/verify/' + reference,
headers={
'Authorization': 'Bearer ' + settings.PAYSTACK_SECRET_KEY,
},
)
result = response.json()
if not result.get('status') or result['data']['status'] != 'success':
return Response(
{'error': 'Payment not successful'},
status=status.HTTP_400_BAD_REQUEST,
)
# Look up and validate order
try:
order = Order.objects.get(reference=reference)
except Order.DoesNotExist:
return Response(
{'error': 'Order not found'},
status=status.HTTP_404_NOT_FOUND,
)
if result['data']['amount'] != order.amount:
return Response(
{'error': 'Amount mismatch'},
status=status.HTTP_400_BAD_REQUEST,
)
# Idempotent fulfillment
updated = Order.objects.filter(
reference=reference, paid=False
).update(paid=True, paid_at=timezone.now())
return Response({
'success': True,
'already_fulfilled': updated == 0,
'amount': order.amount / 100,
'currency': order.currency,
})
URL Configuration
# payments/urls.py
from django.urls import path
from .views import InitializePaymentView, VerifyPaymentView
urlpatterns = [
path('initialize/', InitializePaymentView.as_view(), name='initialize_payment'),
path('verify/', VerifyPaymentView.as_view(), name='verify_payment'),
]
# project/urls.py
from django.urls import path, include
urlpatterns = [
path('api/payments/', include('payments.urls')),
]
Frontend Integration
Your React, Vue, or mobile frontend calls the API:
// Frontend (React/Vue/etc.)
async function initializePayment(email, amount) {
var res = await fetch('/api/payments/initialize/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Token ' + authToken,
},
body: JSON.stringify({ email: email, amount: amount }),
});
var data = await res.json();
if (data.authorization_url) {
// Redirect checkout
window.location.href = data.authorization_url;
}
}
For inline popup, use the access_code with Paystack Inline.js. For redirect checkout, use the authorization_url.
Security Notes
- Authentication. Add authentication to your initialize endpoint so only logged-in users can start payments.
- Amount validation. Look up the correct price from your product catalog. Never trust the client-submitted amount.
- Webhooks. Set up a webhook endpoint for reliable payment notification. See Handle Paystack Webhooks in DRF.
- CORS. Configure django-cors-headers to allow requests from your frontend domain.
Key Takeaways
- ✓DRF APIViews return JSON responses, making them ideal backends for React, Vue, Angular, or mobile apps using Paystack.
- ✓Use serializers to validate payment input (email, amount) before calling the Paystack API.
- ✓The Paystack secret key stays in your Django settings. The API returns only the authorization URL and access code to the client.
- ✓Set authentication_classes and permission_classes appropriately. Initialize endpoints usually require authentication. Webhook endpoints require none.
- ✓Amounts are in kobo (NGN), pesewas (GHS), or cents (ZAR/KES/USD). Validate and convert on the server.
- ✓For inline popup checkout, return the access_code. For redirect checkout, return the authorization_url.
Frequently Asked Questions
- Should I use function-based views or class-based APIViews?
- APIViews are the DRF convention and give you built-in serializer validation, content negotiation, and authentication handling. Use them for consistency with the rest of your DRF API.
- How do I add authentication to the payment endpoint?
- Set authentication_classes = [TokenAuthentication] and permission_classes = [IsAuthenticated] on your APIView. DRF handles the rest. You can then use request.user to get the customer email.
- Can I use ViewSets and Routers for Paystack endpoints?
- ViewSets work best for CRUD operations on models. Payment initialization and verification are action-oriented, not model CRUD, so APIViews are a better fit.
- How do I handle CORS with DRF and Paystack?
- Install django-cors-headers and add your frontend domain to CORS_ALLOWED_ORIGINS. This has nothing to do with Paystack directly. It allows your SPA to call your DRF backend.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon