Skip to main content

A comprehensive Django integration for Paystack Payment Gateway

Project description

paystack-django

A comprehensive Django integration for the Paystack Payment Gateway. This package provides a complete, production-ready solution for integrating Paystack payments into your Django applications.

PyPI version Django Versions Python Versions License: MIT

New in 2.0 — full coverage of the Paystack API (including Virtual Terminal, Direct Debit, Orders and Storefronts), memory-safe pagination with lazy iter_all() iterators, race-safe webhook deduplication, and a fail-closed webhook verification model. See the CHANGELOG for the full list, including breaking changes.

Features

  • Full Paystack API Coverage - Django-native clients for every Paystack API category
  • Django Models - Pre-built models for transactions, customers, plans, subscriptions, transfers, and webhook events
  • Webhook Support - Built-in webhook handling, HMAC-SHA512 signature verification (fails closed), and race-safe deduplication
  • Signal Support - Django signals for payment, transfer, refund, subscription, and dispute events
  • Memory-safe Pagination - Single-page list() plus lazy iter_all() iterators
  • Type Hints - Typed public interface with a shipped py.typed marker
  • Comprehensive Documentation - Detailed docs and examples

Supported Services

The package provides Django-native clients for the following Paystack APIs. "Full" means every endpoint in that category is implemented.

Category Status
Transactions Full
Transaction Splits Full
Customers (incl. authorization & direct-debit onboarding) Full
Plans / Subscriptions Full
Products Full
Payment Pages / Payment Requests Full
Transfers / Transfer Recipients / Transfer Control Full
Refunds Full
Disputes Full
Subaccounts Full
Dedicated Virtual Accounts Full
Terminal Full
Virtual Terminal Full
Direct Debit Full
Bulk Charges Full
Charge Full
Verification (Bank) Full
Settlements Full
Integration Full
Apple Pay Full
Orders Full
Storefronts Full
Miscellaneous (banks, countries, states) Full

Some list endpoints currently support page-based pagination; cursor-based pagination is on the roadmap. See the parity matrix for per-endpoint detail.

Installation

Install using pip:

pip install paystack-django

Or install from source:

git clone https://github.com/HummingByteDev/paystack-django.git
cd django-paystack
pip install -e .

Quick Start

1. Add to Django Settings

# settings.py

INSTALLED_APPS = [
    # ...
    'djpaystack',
]

PAYSTACK = {
    'SECRET_KEY': 'sk_live_your_secret_key_here',
    'PUBLIC_KEY': 'pk_live_your_public_key_here',
    'ENVIRONMENT': 'production',  # or 'test'
}

Paystack signs webhooks with your account secret key, so WEBHOOK_SECRET is optional and defaults to SECRET_KEY. Webhooks are rejected if no signing key can be resolved (fail closed).

2. Create PaystackClient Instance

from djpaystack import PaystackClient

client = PaystackClient()

# Initialize a transaction
response = client.transactions.initialize(
    email='customer@example.com',
    amount=50000,  # in kobo (500 NGN)
    reference='unique-reference-123'
)

authorization_url = response['data']['authorization_url']
print(f"Redirect user to: {authorization_url}")

3. Verify Transaction

# After user completes payment
verified = client.transactions.verify(reference='unique-reference-123')

if verified['data']['status'] == 'success':
    print("Payment successful!")
    # Update your database
else:
    print("Payment failed!")

4. Set Up Webhooks

# urls.py
from django.urls import include, path

urlpatterns = [
    # Exposes the webhook endpoint at /paystack/webhook/
    path('paystack/', include('djpaystack.webhooks.urls')),
]

Then set the webhook URL (e.g. https://yoursite.com/paystack/webhook/) in your Paystack dashboard.

Configuration

Complete configuration options available in PAYSTACK setting:

PAYSTACK = {
    # Required
    'SECRET_KEY': 'your-secret-key',  # Required
    'PUBLIC_KEY': 'your-public-key',  # Required

    # Optional
    'BASE_URL': 'https://api.paystack.co',  # API base URL
    # Paystack signs webhooks with your SECRET_KEY. Leave WEBHOOK_SECRET unset
    # to use SECRET_KEY automatically; only set it to override.
    'WEBHOOK_SECRET': None,
    'WEBHOOK_SIGNATURE_REQUIRED': True,  # reject unsigned webhooks (fail closed)
    'CALLBACK_URL': 'https://yoursite.com/callback/',  # Callback URL
    'ENVIRONMENT': 'production',  # 'production' or 'test'
    'TIMEOUT': 30,  # Request timeout in seconds
    'MAX_RETRIES': 3,  # Number of retries
    'VERIFY_SSL': True,  # Verify SSL certificates
    'CURRENCY': 'NGN',  # Default currency
    'AUTO_VERIFY_TRANSACTIONS': True,  # Auto-verify on webhook
    'CACHE_TIMEOUT': 300,  # Cache timeout in seconds
    'LOG_REQUESTS': False,  # Log API requests
    'LOG_RESPONSES': False,  # Log API responses
    'ENABLE_SIGNALS': True,  # Enable Django signals
    'ENABLE_MODELS': True,  # Enable Django models
    'ALLOWED_WEBHOOK_IPS': [],  # Allowed webhook IPs (empty = all)
}

Usage Examples

Transactions

from djpaystack import PaystackClient

client = PaystackClient()

# Initialize transaction
response = client.transactions.initialize(
    email='user@example.com',
    amount=100000,
    reference='unique-ref-001',
    metadata={'order_id': 123}
)

# Verify transaction
response = client.transactions.verify(reference='unique-ref-001')

# List transactions (one page; use iter_all() to stream everything)
response = client.transactions.list(page=1, per_page=10)

# Fetch transaction
response = client.transactions.fetch(id_or_reference=123456)

Customers

# Create customer
response = client.customers.create(
    email='customer@example.com',
    first_name='John',
    last_name='Doe',
    phone='1234567890'
)

# List customers
response = client.customers.list(page=1, per_page=50)

# Fetch customer
response = client.customers.fetch(email_or_code='CUS_xxxxx')

Subscriptions

# Create subscription
response = client.subscriptions.create(
    customer='CUS_xxxxx',
    plan='PLN_xxxxx',
    authorization='AUTH_xxxxx'
)

# Enable subscription
response = client.subscriptions.enable(
    code='SUB_xxxxx',
    token='tok_xxxxx'
)

# Disable subscription
response = client.subscriptions.disable(code='SUB_xxxxx')

Plans

# Create plan
response = client.plans.create(
    name='Monthly Plan',
    amount=500000,  # 5000 NGN
    interval='monthly',
    description='Premium monthly subscription'
)

# List plans
response = client.plans.list(page=1)

# Fetch plan
response = client.plans.fetch(id_or_code='PLN_xxxxx')

Transfers

# Create transfer recipient
response = client.transfer_recipients.create(
    type='nuban',
    name='John Doe',
    account_number='0000000000',
    bank_code='001'
)

# Initiate transfer
response = client.transfers.initiate(
    source='balance',
    amount=50000,
    recipient='RCP_xxxxx',
    reference='transfer-001'
)

# Finalize transfer
response = client.transfers.finalize(transfer_code='TRF_xxxxx', otp='123456')

Refunds

# Create refund
response = client.refunds.create(
    transaction='123456'
)

# List refunds
response = client.refunds.list(page=1)

# Fetch refund
response = client.refunds.fetch(reference='123456')

Orders, Storefronts & Virtual Terminal (new in 2.0)

# Create a virtual terminal
client.virtual_terminal.create(
    name='In-store till',
    destinations=[{'target': '+2348000000000', 'name': 'Sales'}],
)

# Create a storefront and publish it
sf = client.storefront.create(name='My Shop', slug='my-shop', currency='NGN')
client.storefront.publish(sf['data']['id'])

# Customer direct-debit onboarding
init = client.customers.initialize_authorization(
    email='customer@example.com', channel='direct_debit',
)
client.customers.verify_authorization(init['data']['reference'])

Database Models

The package includes Django models for persistence:

from djpaystack.models import (
    PaystackTransaction,
    PaystackCustomer,
    PaystackPlan,
    PaystackSubscription,
    PaystackTransfer,
    PaystackWebhookEvent,
)

# Query transactions
transactions = PaystackTransaction.objects.filter(status='success')

# Transactions by customer
customer_transactions = PaystackTransaction.objects.filter(
    customer_email='user@example.com'
)

# Inspect stored webhook events
events = PaystackWebhookEvent.objects.filter(event_type='charge.success')

Persistence is controlled by the ENABLE_MODELS setting (default True). Webhook handlers populate these models automatically.

Webhooks

Handle Paystack webhooks automatically:

# Webhook signals are dispatched automatically as events arrive
from django.dispatch import receiver

from djpaystack.signals import (
    paystack_payment_successful,
    paystack_payment_failed,
    paystack_transfer_successful,
    paystack_refund_processed,
    paystack_dispute_created,
)

@receiver(paystack_payment_successful)
def on_payment_success(sender, transaction_data, **kwargs):
    print(f"Payment successful: {transaction_data['reference']}")
    # Update your application

@receiver(paystack_payment_failed)
def on_payment_failed(sender, transaction_data, **kwargs):
    print(f"Payment failed: {transaction_data['reference']}")
    # Handle failed payment

Available signals: paystack_payment_successful, paystack_payment_failed, paystack_subscription_created, paystack_subscription_cancelled, paystack_transfer_successful, paystack_transfer_failed, paystack_refund_processed, paystack_dispute_created, paystack_dispute_resolved. Each receiver is called with a keyword argument carrying the event payload (e.g. transaction_data, transfer_data, refund_data, dispute_data).

Testing

Run the test suite:

pip install -e ".[dev]"
pytest

With coverage:

pytest --cov=djpaystack

Run tests across Python versions:

tox

Django Compatibility

The 2.x line is tested against the current and LTS Django releases:

Package Version Django 4.2 (LTS) Django 5.2 (LTS) Django 6.0
2.0.x

Python Compatibility

  • Python 3.8
  • Python 3.9
  • Python 3.10
  • Python 3.11
  • Python 3.12
  • Python 3.13

Environment Variables

You can also configure using environment variables:

PAYSTACK_SECRET_KEY=sk_live_xxx
PAYSTACK_PUBLIC_KEY=pk_live_xxx
# Webhooks are signed with your secret key; use the same sk_... value here.
PAYSTACK_WEBHOOK_SECRET=sk_live_xxx
PAYSTACK_ENVIRONMENT=production

Load them however you prefer — for example with the standard library:

import os

PAYSTACK = {
    'SECRET_KEY': os.environ['PAYSTACK_SECRET_KEY'],
    'PUBLIC_KEY': os.environ['PAYSTACK_PUBLIC_KEY'],
    'ENVIRONMENT': os.environ.get('PAYSTACK_ENVIRONMENT', 'test'),
}

python-decouple is not a dependency of this package. If you prefer decouple.config(...), install it in your own project.

Error Handling

The package provides specific exception classes:

from djpaystack.exceptions import (
    PaystackError,
    PaystackAPIError,
    PaystackValidationError,
    PaystackAuthenticationError,
    PaystackNetworkError,
)

try:
    client.transactions.verify(reference='ref-123')
except PaystackAuthenticationError:
    print("Invalid API credentials")
except PaystackNetworkError:
    print("Network error occurred")
except PaystackAPIError as e:
    print(f"API error: {e}")

Pagination

list() returns a single page (the first by default) and preserves Paystack's meta block, so you control how much you fetch:

response = client.transactions.list(
    page=1,
    per_page=50,
    from_date='2024-01-01',
    to_date='2024-12-31',
    status='success',
)

transactions = response['data']      # this page's records
meta = response['meta']              # {'page', 'pageCount', 'total', ...}

To stream every record across all pages without loading them all into memory, use the lazy iterator:

for txn in client.transactions.iter_all(status='success', from_date='2024-01-01'):
    process(txn)   # one record at a time; pages fetched on demand

Upgrading from 1.x? Previously list() eagerly fetched all pages. It now returns one page — switch full scans to iter_all(). See the CHANGELOG for the full list of breaking changes.

Logging

Enable logging to debug API interactions:

import logging

# In settings.py
PAYSTACK = {
    'LOG_REQUESTS': True,
    'LOG_RESPONSES': True,
}

# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('djpaystack')

Security

Environment Variables

Never hardcode secrets — load them from the environment:

import os

PAYSTACK = {
    'SECRET_KEY': os.environ['PAYSTACK_SECRET_KEY'],
    'PUBLIC_KEY': os.environ['PAYSTACK_PUBLIC_KEY'],
}

Webhook Verification

The built-in PaystackWebhookView verifies the HMAC-SHA512 signature on every request and rejects anything it cannot verify (fail closed), so you normally don't need to verify manually. If you handle webhooks yourself, use the helper:

from djpaystack.utils import verify_webhook_signature

is_valid = verify_webhook_signature(
    request.body,                                      # payload (bytes)
    request.headers.get('X-Paystack-Signature', ''),   # signature
    settings.PAYSTACK['SECRET_KEY'],                   # secret (the signing key)
)

if not is_valid:
    return JsonResponse({'status': 'invalid'}, status=403)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

Support

Changelog

See CHANGELOG.md for detailed release notes.

License

This project is licensed under the MIT License - see LICENSE file for details.

Acknowledgments

  • Paystack for the excellent payment gateway
  • Django community for the amazing framework
  • All contributors and users of this package

Disclaimer

This package is not affiliated with or endorsed by Paystack. It is maintained by Humming Byte as a community contribution.


Made with ❤️ by Humming Byte

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paystack_django-2.0.1.tar.gz (61.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paystack_django-2.0.1-py3-none-any.whl (78.0 kB view details)

Uploaded Python 3

File details

Details for the file paystack_django-2.0.1.tar.gz.

File metadata

  • Download URL: paystack_django-2.0.1.tar.gz
  • Upload date:
  • Size: 61.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for paystack_django-2.0.1.tar.gz
Algorithm Hash digest
SHA256 af3912625b3265cdfbde7b903f6cd8bb2f173c26bb25098521ba85ac1267d251
MD5 4c003744445c3abe5200db1c0db5ffbf
BLAKE2b-256 ae495272c06e6a926505ba81542cf117e8150356b894abaa820789d9bf7929ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for paystack_django-2.0.1.tar.gz:

Publisher: publish.yml on HummingByteDev/paystack-django

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file paystack_django-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: paystack_django-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 78.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for paystack_django-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e6abc4472f431bb9f2044ccec7f65e4b8430a6a0a1835350d27bf65e845b68f5
MD5 3115857ff21fc73650ab3bd36597ac64
BLAKE2b-256 53b88c1a25f493d3dfe29d4460d239d16b986ef59626c91f2749cb3387ebf154

See more details on using hashes here.

Provenance

The following attestation bundles were made for paystack_django-2.0.1-py3-none-any.whl:

Publisher: publish.yml on HummingByteDev/paystack-django

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page