dj-paynow - Django + PayNow Made Easy
Complete PayNow payment gateway integration for Django applications. Accept payments from Zimbabwean customers using EcoCash, OneMoney, Visa/Mastercard, and more.
Introduction
dj-paynow is a comprehensive Django library for integrating PayNow, Zimbabwe's leading payment gateway. It provides a simple, secure, and Pythonic way to accept online payments.
Features
- Easy Integration - Get started in under 10 minutes
- Multiple Payment Methods - EcoCash, OneMoney, TeleCash, Visa/Mastercard
- Secure - Hash verification and validation
- Complete - Models, forms, views, and webhooks included
- Database Tracking - Complete payment history
- Django Admin - Full admin integration
- REST API - Optional DRF support
- Status Polling - Real-time payment status updates
Requirements
- Python 3.8+
- Django 3.2+
- Django REST Framework 3.12+
- requests 2.25.0+
- PayNow Account (sign up at paynow.co.zw)
Installation
1. Install via pip
pip install dj-paynow
2. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps
'rest_framework', # Optional, for API support
'paynow', # Add this
# Your apps
'myapp',
]
3. Configure Settings
Add your PayNow credentials to settings.py:
# PayNow Configuration
PAYNOW_INTEGRATION_ID = 'your_integration_id'
PAYNOW_INTEGRATION_KEY = 'your_integration_key'
PAYNOW_TEST_MODE = True # False for production
Environment Variables (Recommended):
pip install dotzen
from dotzen import config
PAYNOW_INTEGRATION_ID = config('PAYNOW_INTEGRATION_ID')
PAYNOW_INTEGRATION_KEY = config('PAYNOW_INTEGRATION_KEY')
PAYNOW_TEST_MODE = config('PAYNOW_TEST_MODE', 'True') == 'True'
4. Configure URLs
# urls.py
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('paynow/', include('paynow.urls')),
]
5. Run Migrations
python manage.py migrate
Quick Start
Create a Payment
from django.shortcuts import redirect, reverse
from urllib.parse import urlencode
def buy_product(request):
"""Create payment and redirect to PayNow"""
params = urlencode({
'amount': '50.00',
'description': 'Premium Subscription',
'email': request.user.email,
'phone': '+263771234567',
})
url = f"{reverse('paynow:checkout')}?{params}"
return redirect(url)
Using the API
import requests
# Create payment
response = requests.post('http://localhost:8000/paynow/payments/', json={
'amount': '100.00',
'description': 'Product Purchase',
'email': '[email protected]',
'phone': '+263771234567',
})
payment = response.json()
# Redirect user to payment['paynow_url']
Payment Flow
- Create Payment - Create a payment record with amount and description
- Initialize with PayNow - Send request to PayNow to get payment URL
- Redirect User - Redirect customer to PayNow payment page
- Customer Pays - Customer completes payment via EcoCash/OneMoney/Card
- Receive Webhook - PayNow sends status update to your webhook
- Poll Status - Optionally poll for payment status
- Update Status - Payment status updated in database
Payment Methods
PayNow supports multiple payment methods:
- EcoCash - Mobile money (most popular in Zimbabwe)
- OneMoney - NetOne mobile money
- TeleCash - Telecel mobile money
- Visa/Mastercard - Credit and debit cards
Customers choose their preferred method on the PayNow payment page.
Webhook Handling
PayNow sends notifications to your result URL when payment status changes:
# Webhook endpoint: /paynow/result/
# This is handled automatically by dj-paynow
# You can add custom logic using Django signals:
from django.db.models.signals import post_save
from django.dispatch import receiver
from paynow.models import PayNowPayment
@receiver(post_save, sender=PayNowPayment)
def handle_payment_complete(sender, instance, **kwargs):
if instance.status == 'paid':
# Grant access to service
grant_premium_access(instance.user)
# Send confirmation email
send_confirmation_email(instance)
Status Polling
Check payment status programmatically:
from paynow.paynow_client import PayNowClient
client = PayNowClient()
payment = PayNowPayment.objects.get(reference='PN123456789')
if payment.poll_url:
status = client.check_transaction_status(payment.poll_url)
if status['success']:
print(f"Status: {status['status']}")
print(f"Reference: {status['paynow_reference']}")
Models
PayNowPayment
Stores payment transaction data:
payment = PayNowPayment.objects.create(
user=request.user,
amount=99.99,
description='Premium Plan',
email='[email protected]',
phone='+263771234567',
)
Fields:
reference- Unique payment referenceamount- Payment amount (decimal)description- Payment descriptionemail- Customer emailphone- Customer phone numberstatus- Payment status (pending, sent, paid, failed, etc.)poll_url- URL for status pollingbrowser_url- PayNow payment page URLpaynow_reference- PayNow internal reference
PayNowStatusUpdate
Logs all status updates and webhook calls:
updates = payment.status_updates.all()
for update in updates:
print(f"{update.status} at {update.created_at}")
Django Admin
Full admin interface included:
- View all payments
- Filter by status, date
- Search by reference, email
- View status update history
- Manual status updates
Access at: /admin/paynow/
REST API
Optional REST API endpoints:
GET /paynow/payments/ - List payments
POST /paynow/payments/ - Create payment
GET /paynow/payments/{reference}/ - Get payment details
Security
dj-paynow implements security best practices:
- Hash Verification - All responses verified with SHA512 hash
- Status Logging - All webhook calls logged for audit
- Environment Variables - Credentials stored securely
- HTTPS Required - Production requires HTTPS
Testing
PayNow provides a sandbox environment for testing:
- Sign up at paynow.co.zw
- Get sandbox credentials
- Set
PAYNOW_TEST_MODE = True - Test with EcoCash sandbox numbers
Examples
Subscription Payment
def subscribe_to_plan(request, plan_id):
plan = get_object_or_404(Plan, id=plan_id)
params = urlencode({
'amount': plan.price,
'description': f'{plan.name} Subscription',
'email': request.user.email,
})
return redirect(f"{reverse('paynow:checkout')}?{params}")
Donation Widget
def process_donation(request):
if request.method == 'POST':
amount = request.POST.get('amount')
email = request.POST.get('email')
params = urlencode({
'amount': amount,
'description': 'Donation',
'email': email,
})
return redirect(f"{reverse('paynow:checkout')}?{params}")
return render(request, 'donate.html')
Getting PayNow Credentials
- Visit paynow.co.zw
- Sign up for an account
- Navigate to Settings → Integrations
- Copy your Integration ID and Integration Key
- Add them to your environment variables
Troubleshooting
Payment not updating?
- Check webhook URL is publicly accessible
- Verify hash in status updates
- Check PayNow status update logs in admin
Hash verification failing?
- Ensure integration key is correct
- Check for extra spaces in credentials
Webhook not receiving calls?
- Use ngrok for local development
- Verify result URL is correct
- Check server logs for errors
Support
- GitHub: github.com/carrington-dev/dj-paynow
- Issues: GitHub Issues
- Email: [email protected]
- PayNow Docs: paynow.co.zw/developers
Contributing
Contributions welcome! Please see CONTRIBUTING.md
License
MIT License - See LICENSE file
Acknowledgments
Inspired by:
Changelog
0.1.0 (2025-12-15)
- Initial release
- PayNow integration
- Payment models
- Webhook handling
- Status polling
- REST API
- Django admin
Made with ❤️ for the Zimbabwean developer community