Usage Guide
This guide covers common use cases and advanced features of dj-payfast.
Basic Payment Flow
The typical payment flow with dj-payfast follows these steps:
Create Payment Record - Store payment details in the database
Generate Payment Form - Create a secure PayFast form
User Completes Payment - User is redirected to PayFast
Receive Webhook - PayFast sends ITN notification
Update Payment Status - Payment is marked as complete
Grant Access - Your application grants the purchased service
Creating Payments
Simple Payment
Create a basic one-time payment:
from payfast.models import PayFastPayment
import uuid
payment = PayFastPayment.objects.create(
user=request.user,
m_payment_id=str(uuid.uuid4()), # Unique payment ID
amount=199.99,
item_name='Django Course',
item_description='Complete Django Web Development Course',
email_address=request.user.email,
name_first=request.user.first_name,
name_last=request.user.last_name,
)
Payment with Custom Fields
Store additional data with your payment:
payment = PayFastPayment.objects.create(
user=request.user,
m_payment_id=str(uuid.uuid4()),
amount=499.00,
item_name='Premium Plan',
email_address=request.user.email,
# Custom string fields (useful for order IDs, SKUs, etc.)
custom_str1=f'order_{order.id}',
custom_str2=product.sku,
custom_str3='monthly_subscription',
# Custom integer fields (useful for user IDs, quantities, etc.)
custom_int1=request.user.id,
custom_int2=subscription.duration_months,
custom_int3=order.quantity,
)
Payment for Guest Checkout
Handle payments without requiring login:
payment = PayFastPayment.objects.create(
user=None, # No user for guest checkout
m_payment_id=str(uuid.uuid4()),
amount=99.00,
item_name='Guest Purchase',
email_address=request.POST.get('email'),
name_first=request.POST.get('first_name'),
name_last=request.POST.get('last_name'),
cell_number=request.POST.get('phone'),
)
Generating Payment Forms
Basic Form
from payfast.forms import PayFastPaymentForm
from django.urls import reverse
form = PayFastPaymentForm(initial={
'amount': payment.amount,
'item_name': payment.item_name,
'm_payment_id': payment.m_payment_id,
'email_address': payment.email_address,
'notify_url': request.build_absolute_uri(reverse('payfast:notify')),
})
Form with All Callback URLs
form = PayFastPaymentForm(initial={
'amount': payment.amount,
'item_name': payment.item_name,
'item_description': payment.item_description,
'm_payment_id': payment.m_payment_id,
'email_address': payment.email_address,
'name_first': payment.name_first,
'name_last': payment.name_last,
'cell_number': payment.cell_number,
# Callback URLs
'return_url': request.build_absolute_uri(reverse('payment_success')),
'cancel_url': request.build_absolute_uri(reverse('payment_cancel')),
'notify_url': request.build_absolute_uri(reverse('payfast:notify')),
# Pass custom fields
'custom_str1': payment.custom_str1,
'custom_int1': payment.custom_int1,
})
Rendering the Form
In your template:
<form action="{{ form.get_action_url }}" method="post">
{{ form.as_p }}
<button type="submit">Pay Now</button>
</form>
Or with Bootstrap styling:
<form action="{{ form.get_action_url }}" method="post" class="payfast-form">
{% for field in form %}
{{ field }}
{% endfor %}
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-lock"></i> Secure Payment - R{{ payment.amount }}
</button>
</form>
<div class="payment-info mt-3">
<small class="text-muted">
You will be redirected to PayFast to complete your payment securely.
</small>
</div>
Querying Payments
Get User’s Payments
# All payments for a user
payments = PayFastPayment.objects.filter(user=request.user)
# Completed payments only
completed = PayFastPayment.objects.filter(
user=request.user,
status='complete'
)
# Recent payments (last 30 days)
from django.utils import timezone
from datetime import timedelta
recent = PayFastPayment.objects.filter(
user=request.user,
created_at__gte=timezone.now() - timedelta(days=30)
)
Get Payment by ID
from django.shortcuts import get_object_or_404
# By merchant payment ID
payment = get_object_or_404(
PayFastPayment,
m_payment_id=payment_id
)
# By PayFast payment ID (after payment completed)
payment = get_object_or_404(
PayFastPayment,
pf_payment_id=pf_payment_id
)
Calculate Revenue
from django.db.models import Sum, Count, Avg
# Total revenue
total = PayFastPayment.objects.filter(
status='complete'
).aggregate(Sum('amount'))
# Revenue this month
from django.utils import timezone
import calendar
now = timezone.now()
month_start = now.replace(day=1, hour=0, minute=0, second=0)
monthly_revenue = PayFastPayment.objects.filter(
status='complete',
completed_at__gte=month_start
).aggregate(Sum('amount'))
# Average transaction value
avg_payment = PayFastPayment.objects.filter(
status='complete'
).aggregate(Avg('amount'))
# Total number of transactions
total_transactions = PayFastPayment.objects.filter(
status='complete'
).count()
Handling Payment Status
Check Payment Status
payment = PayFastPayment.objects.get(m_payment_id=payment_id)
if payment.status == 'complete':
# Payment successful
grant_access(request.user)
elif payment.status == 'pending':
# Payment still processing
show_pending_message()
elif payment.status == 'failed':
# Payment failed
show_error_message()
Manual Status Update
# Mark as complete
payment.mark_complete()
# Mark as failed
payment.mark_failed()
# Manual status change
payment.status = 'cancelled'
payment.save()
Using Django Signals
Listen for Payment Events
Create a signals handler to respond to payment events:
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from payfast.models import PayFastPayment
from django.core.mail import send_mail
@receiver(post_save, sender=PayFastPayment)
def handle_payment_update(sender, instance, created, **kwargs):
"""Handle payment status changes"""
if created:
# New payment created
print(f"New payment created: {instance.m_payment_id}")
elif instance.status == 'complete' and instance.payment_status == 'COMPLETE':
# Payment completed successfully
handle_successful_payment(instance)
def handle_successful_payment(payment):
"""Process successful payment"""
# Grant access to user
if payment.user:
activate_premium(payment.user)
# Send confirmation email
send_mail(
subject='Payment Confirmation',
message=f'Thank you for your payment of R{payment.amount}',
from_email='noreply@example.com',
recipient_list=[payment.email_address],
)
# Log the event
print(f"Payment {payment.m_payment_id} completed successfully")
Register signals in your app’s apps.py:
# apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
import myapp.signals # Register signals
Working with Webhooks
Understanding ITN Notifications
PayFast sends Instant Transaction Notifications (ITN) to your webhook URL when:
Payment is completed
Payment fails
Payment is cancelled
Subscription is created/cancelled
Payment status changes
The webhook handler at /payfast/notify/ automatically:
Validates the request signature
Verifies the request comes from PayFast
Validates with PayFast servers
Updates the payment record
Logs the notification
Accessing Notification History
from payfast.models import PayFastNotification
# Get all notifications for a payment
notifications = PayFastNotification.objects.filter(
payment=payment
)
# Get failed notifications
failed = PayFastNotification.objects.filter(
is_valid=False
)
# Check why validation failed
for notification in failed:
print(f"Error: {notification.validation_errors}")
print(f"Data: {notification.raw_data}")
Custom Webhook Processing
If you need to extend webhook handling, you can subclass the view:
# views.py
from payfast.views import PayFastNotifyView
class CustomPayFastNotifyView(PayFastNotifyView):
def post(self, request, *args, **kwargs):
# Call parent processing
response = super().post(request, *args, **kwargs)
# Add custom logic
if response.status_code == 200:
post_data = request.POST.dict()
m_payment_id = post_data.get('m_payment_id')
# Your custom logic here
self.send_custom_notifications(m_payment_id)
return response
def send_custom_notifications(self, payment_id):
"""Send custom notifications"""
# Your implementation
pass
Then update your URLs:
from .views import CustomPayFastNotifyView
urlpatterns = [
path('payfast/notify/', CustomPayFastNotifyView.as_view(), name='payfast_notify'),
]
Advanced Use Cases
Dynamic Pricing
Calculate prices based on user input:
def checkout_view(request):
# Get selected plan
plan = request.POST.get('plan')
# Calculate amount
prices = {
'basic': 99.00,
'pro': 199.00,
'enterprise': 499.00,
}
amount = prices.get(plan, 99.00)
# Apply discount if applicable
if request.user.has_discount():
amount = amount * 0.9 # 10% discount
payment = PayFastPayment.objects.create(
user=request.user,
m_payment_id=str(uuid.uuid4()),
amount=amount,
item_name=f'{plan.title()} Plan',
email_address=request.user.email,
custom_str1=plan,
)
Multiple Items Checkout
Handle cart checkout with multiple items:
def cart_checkout(request):
cart = get_cart(request)
# Calculate total
total = sum(item.price * item.quantity for item in cart.items.all())
# Create item description
items_desc = ', '.join([
f"{item.quantity}x {item.product.name}"
for item in cart.items.all()
])
payment = PayFastPayment.objects.create(
user=request.user,
m_payment_id=str(uuid.uuid4()),
amount=total,
item_name='Shopping Cart',
item_description=items_desc,
email_address=request.user.email,
custom_str1=f'cart_{cart.id}',
custom_int1=cart.items.count(), # Number of items
)
Refund Tracking
While dj-payfast doesn’t process refunds (done via PayFast dashboard), you can track them:
# Create a custom model for refunds
from django.db import models
class PayFastRefund(models.Model):
payment = models.ForeignKey(PayFastPayment, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=10, decimal_places=2)
reason = models.TextField()
refunded_at = models.DateTimeField(auto_now_add=True)
processed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
# Create refund record
refund = PayFastRefund.objects.create(
payment=payment,
amount=payment.amount,
reason='Customer request',
processed_by=request.user,
)
Best Practices
Always Use HTTPS in Production
PayFast requires HTTPS for live payments.
Generate Unique Payment IDs
Use UUIDs or combine timestamp + user ID:
import uuid m_payment_id = str(uuid.uuid4()) # Or from django.utils import timezone m_payment_id = f"{request.user.id}_{int(timezone.now().timestamp())}"
Store Original Request Data
Use custom fields to store important metadata:
payment.custom_str1 = f'order_{order.id}' payment.custom_int1 = request.user.id
Handle All Payment States
Don’t assume payments will always complete:
if payment.status == 'complete': grant_access() elif payment.status == 'pending': show_pending_message() else: show_error_message()
Use Absolute URLs for Callbacks
Always use
build_absolute_uri():notify_url = request.build_absolute_uri(reverse('payfast:notify'))
Monitor Webhook Failures
Regularly check
PayFastNotificationfor validation errors.Test Thoroughly
Always test in sandbox before going live. See Testing Guide.
Next Steps
Webhooks Guide - Deep dive into webhook handling
Testing Guide - Learn testing strategies
security - Security best practices
API Reference - Complete API reference