Complete PayNow payment gateway integration for Django applications. Accept payments from Zimbabwean customers using EcoCash, OneMoney, Visa/Mastercard, and more.
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.
- 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
- Python 3.8+
- Django 3.2+
- Django REST Framework 3.12+
- requests 2.25.0+
- PayNow Account (sign up at paynow.co.zw)
pip install dj-paynow# 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',
]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 productionEnvironment Variables (Recommended):
pip install dotzenfrom 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'# urls.py
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('paynow/', include('paynow.urls')),
]python manage.py migratefrom 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)import requests
# Create payment
response = requests.post('http://localhost:8000/paynow/payments/', json={
'amount': '100.00',
'description': 'Product Purchase',
'email': 'customer@example.com',
'phone': '+263771234567',
})
payment = response.json()
# Redirect user to payment['paynow_url']- 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
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.
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)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']}")Stores payment transaction data:
payment = PayNowPayment.objects.create(
user=request.user,
amount=99.99,
description='Premium Plan',
email='user@example.com',
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
Logs all status updates and webhook calls:
updates = payment.status_updates.all()
for update in updates:
print(f"{update.status} at {update.created_at}")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/
Optional REST API endpoints:
GET /paynow/payments/ - List payments
POST /paynow/payments/ - Create payment
GET /paynow/payments/{reference}/ - Get payment details
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
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
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}")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')- 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
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
- GitHub: github.com/carrington-dev/dj-paynow
- Issues: GitHub Issues
- Email: carrington.muleya@outlook.com
- PayNow Docs: paynow.co.zw/developers
Contributions welcome! Please see CONTRIBUTING.md
MIT License - See LICENSE file
Inspired by:
- Initial release
- PayNow integration
- Payment models
- Webhook handling
- Status polling
- REST API
- Django admin
Made with ❤️ for the Zimbabwean developer community