Skip to main content

Pluggable Django package for integrating multiple payment gateways like Paystack, Flutterwave, Interswitch, Stripe, etc.

Project description

Django Payment Gateways Package

A pluggable Django package for integrating multiple payment gateways including Paystack, Flutterwave, Interswitch, and Stripe, with an extensible architecture for adding more gateways.


✨ Features

  • 🔌 Plug-and-play integration
  • 🔐 Paystack, Flutterwave, Interswitch, and Stripe support
  • 📦 Dispatcher pattern for gateway switching
  • 🧱 Abstract BaseOrder model for customization
  • ✅ Built-in Payment Verification View (redirect-based gateways)
  • 🟦 Webhook-based verification for Stripe
  • 🧠 Smart unique order reference generation
  • 🧪 Built-in signal handling for order reference
  • 💡 Fully customizable frontend and views

Example Project

A sample Django project demonstrating how to use this package is available here:

👉 django_pg_test_project


📦 Installation

pip install django-pg

⚙️ Project Setup

  1. Add the app to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
    ...
    'django_pg',  # Your payment package
]
  1. Define required settings in your settings.py
# settings.py

# Models used for order
PAYMENT_ORDER_MODEL = 'yourapp.Order'

# It's recommended that you put the secret key 
# in a .env file and load it in your settings

# Paystack keys
PAYSTACK_PUBLIC_KEY = 'your-paystack-public-key'
PAYSTACK_SECRET_KEY = 'your-paystack-secret-key'

# Flutterwave keys
FLUTTERWAVE_PUBLIC_KEY = "your-flutterwave-public-key"
FLUTTERWAVE_SECRET_KEY = "your-flutterwave-secret-key"

# Interswitch keys
INTERSWITCH_MERCHANT_CODE = "your-interswitch-merchant-code"
INTERSWITCH_PAY_ITEM_ID = "your-interswitch-pay-item-id"

# Stripe keys
STRIPE_SECRET_KEY = "your_stripe_secret"
STRIPE_WEBHOOK_SECRET = "your_webhook_secret_if_using_webhooks"
DJANGO_PG_STRIPE_WEBHOOK_PATH = "webhooks/stripe/" #if using webhook
  1. Built-in Payment Verification View django-pg provides a built-in payment_verification view that handles verifying transactions for all the payment gateways out of the box.

🔌 URL Configuration

You can use the built-in view directly in your urls.py:

from django.urls import path
from django_pg.views import payment_verification  # Import from the package

urlpatterns = [
    path("verify/<int:order_id>/<str:payment_method>/", payment_verification, name="payment_verification"),
]

🌐 Redirect Behavior

After verifying a transaction, the view will redirect the user based on settings defined in your settings.py.

Option 1: Use named URL patterns

# settings.py
DJANGO_PG_SUCCESS_REDIRECT = 'yourapp:track_order'
DJANGO_PG_FAILURE_REDIRECT = 'yourapp:create_order'

Option 2: Use custom Python functions (advanced) You can also pass a function that takes the verification result dictionary and returns a HttpResponseRedirect.

# settings.py
DJANGO_PG_SUCCESS_REDIRECT = 'yourapp.utils.payment_success_redirect'
DJANGO_PG_FAILURE_REDIRECT = 'yourapp.utils.payment_failure_redirect'

If you go with option 2, you will need to add the functions in yourapp/utils.py:

from django.shortcuts import redirect

def payment_success_redirect(result):
    return redirect('yourapp:track_order', order_reference=result["order_reference"])

def payment_failure_redirect(result):
    return redirect('yourapp:create_order')
  1. Extend the BaseOrder abstract model In your own app, create your order model by extending gateways.models.BaseOrder:
# yourapp/models.py
from django.db import models
from django_pg.models import BaseOrder
from django.contrib.auth.models import User

class Order(BaseOrder):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    total_price = models.DecimalField(max_digits=10, decimal_places=2)
    # Add your fields here

Note: Users attempting to make a payment via Paystack and Flutterwave must have a valid email address. The Paystack and Flutterwave gateway requires this for transaction initiation. Make sure you enforce email submission when a user registers

5. Add JS to Your HTML Template

If you're using multiple payment methods (e.g. Paystack, Flutterwave and Interswitch), make sure your template checks for the selected payment_method. If you're only using one payment method, you can pass the preferred payment method in a hidden field when the order is created.

✅ Paystack Integration (HTML Template)

Check Paystack Documentation

{% if payment_method == 'paystack' %}
<script src="https://js.paystack.co/v2/inline.js"></script>
<script type="text/javascript">
    function payWithPaystack() {
        var handler = PaystackPop.setup({
            key: '{{ PAYSTACK_PUBLIC_KEY }}',
            email: '{{ request.user.email }}',
            amount: {{ order.total_price|multiply:100 }},
            currency: "NGN",
            ref: '' + Math.floor((Math.random() * 1000000000) + 1),
            callback: function(response) {
                window.location.href = "{% url 'yourapp:payment_verification' order.id payment_method %}?reference=" + response.reference;
            },
            onClose: function() {
                alert('Payment was not completed.');
            }
        });
        handler.openIframe();
    }

    window.onload = function() {
        payWithPaystack();
    };
</script>
{% endif %}

✅ Flutterwave Integration (HTML Template)

Check Flutterwave Documentation

{% if payment_method == 'flutterwave' %}
<script src="https://checkout.flutterwave.com/v3.js"></script>
<script>
  document.addEventListener("DOMContentLoaded", function () {
    FlutterwaveCheckout({
      public_key: "{{ FLUTTERWAVE_PUBLIC_KEY }}",
      tx_ref: "{{ order.order_reference }}",
      amount: {{order.total_price}},
      currency: "NGN",
      payment_options: "card, ussd, banktransfer",
      redirect_url: "{% url 'yourapp:payment_verification' order.id payment_method %}",
      customer: {
        email: "{{ request.user.email }}",
        name: "{{ request.user.get_full_name|default:request.user.username }}"
      },
      customizations: {
        title: "My Store",
        description: "Payment for order {{ order.order_reference }}"
      }
    });
  });
</script>
{% endif %}

✅ Interswitch Integration (HTML Template)

Check Interswitch Documentation

{% if payment_method == 'interswitch' %}
<script src="https://newwebpay.qa.interswitchng.com/inline-checkout.js"></script>
<script>
(function() {
    const redirectUrl = "{% url 'yourapp:payment_verification' order.id payment_method %}?reference={{ order.order_reference }}";
    const paymentAmount = {{ order.total_price|floatformat:0 }} * 100;

    function paymentCallback(response) {
        console.log("Interswitch Payment Response:", response);

        if (response?.resp === '00') {
            // Successful payment
            window.location.href = redirectUrl;
        } else {
            alert("Payment was not successful. Please try again.");
        }
    }

    const paymentRequest = {
        merchant_code: "{{ INTERSWITCH_MERCHANT_CODE }}",
        pay_item_id: "{{ INTERSWITCH_PAY_ITEM_ID }}",
        txn_ref: "{{ order.order_reference }}",
        site_redirect_url: redirectUrl,
        amount: paymentAmount,
        currency: 566,
        cust_email: "{{ request.user.email }}",
        cust_name: "{{ request.user.get_full_name|default:request.user.username }}",
        onComplete: paymentCallback,
        mode: "TEST"
    };

    window.webpayCheckout(paymentRequest);
})();
</script>
{% endif %}

🟦 Stripe Integration (Important)

Stripe payments are server-authoritative and rely on webhooks.

How Stripe works in this package:

  1. Backend creates a Stripe Checkout Session
  2. Backend returns stripe_checkout_url and stripe_session_id
  3. Frontend redirects user to Stripe Checkout
  4. Stripe sends a webhook (checkout.session.completed) to the backend
  5. Backend marks the order as paid
  6. Frontend redirect is UX only, not payment truth

⚠️ Do not rely solely on frontend redirects to confirm Stripe payments. ⚠️ Stripe Checkout requires a webhook in production to reliably confirm payments.

Creating a Stripe Checkout Session (Backend)

from django_pg.stripe.stripe_checkout import create_stripe_checkout_session

session = create_stripe_checkout_session(
    order=order,
    success_url="https://frontend.com/payment/ORDER_REF/stripe/?reference={CHECKOUT_SESSION_ID}",
    cancel_url="https://frontend.com/payment/ORDER_REF/stripe/?cancelled=true",
    customer_email=request.user.email,
)

order.stripe_checkout_session_id = session.id
order.save()

✅ Stripe Integration (HTML Template)

Check Stripe Documentation

{% if payment_method == 'stripe' %}
<script src="https://js.stripe.com/v3/"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
    const stripe = Stripe("{{ STRIPE_PUBLIC_KEY }}");
    const sessionId = "{{ STRIPE_SESSION_ID|default:'' }}";

    if (!sessionId) {
        alert("Unable to start Stripe checkout (missing session id).");
        return;
    }

    stripe.redirectToCheckout({ sessionId: sessionId });
});
</script>
{% endif %}

Add URLs to backend if using webhook

urlpatterns = [
    path("", include("django_pg.urls")),
]

🔁 Signals (Auto Order Reference)

You don’t need to register anything. The gateways app automatically registers a pre_save signal that generates a unique order_reference.

# gateways/signals.py
@receiver(pre_save, sender=Order)
def set_order_reference(sender, instance, **kwargs):
    if not instance.order_reference:
        instance.order_reference = generate_unique_order_reference()

🧠 Gateway Dispatcher (Behind the scenes)

The following function routes the verification based on the selected payment method:

# gateways/payment.py
def verify_payment(order_id, reference, user, payment_method):
    if payment_method == 'paystack':
        return verify_paystack_payment(order_id, reference, user)
    # elif payment_method == 'flutterwave': ...

You don't need to modify this — it's extendable internally.

🛡 License

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


🤝 Contributing

Pull requests are welcome! If you find a bug or have a feature request, feel free to open an issue.

See full Changelog.

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

django_pg-0.5.0.tar.gz (15.5 kB view details)

Uploaded Source

Built Distribution

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

django_pg-0.5.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file django_pg-0.5.0.tar.gz.

File metadata

  • Download URL: django_pg-0.5.0.tar.gz
  • Upload date:
  • Size: 15.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.0

File hashes

Hashes for django_pg-0.5.0.tar.gz
Algorithm Hash digest
SHA256 6d79b67881fcc4ebf5d7f5444720bdddde97de1a80dca43723d078827d166a32
MD5 61143b0f2a62ad55258d5eb94149077f
BLAKE2b-256 194afdc2b60a8bc4263f41db208e63dcf67a54dc304a70ad55a45628ce5d8ac6

See more details on using hashes here.

File details

Details for the file django_pg-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: django_pg-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.0

File hashes

Hashes for django_pg-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f942385678c869368fde8a0bbfe9ffcf1823712afeeb1358143395c36aecf09d
MD5 011fb9d20a97ffd68d9d341bbefd8fed
BLAKE2b-256 315f77c9600aea2130b53b16b228fd8282ebbc6b0ee43e1a1425f1f230adaeba

See more details on using hashes here.

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