Python Django Stripe Payment Integration

Written by

in

Integrating Stripe with a Python Django application is one of the most reliable ways to add secure, fast, and flexible payment processing to your web project. Whether you’re building a SaaS platform, an e‑commerce store, or a subscription‑based service, Stripe’s robust API combined with Django’s clean architecture makes the implementation both straightforward and scalable. In this guide we’ll walk through every step of a full‑stack Python Django Stripe payment integration, from setting up the Stripe account to handling webhooks and testing in a live environment. By the end of this article you’ll have a production‑ready checkout flow that follows best practices for security, error handling, and SEO‑friendly content.

Why Choose Stripe for Django Projects?

Before diving into the code, it’s worth understanding why Stripe is a top choice for Django developers:

  • Developer‑first API: Stripe’s documentation is clear, and the official stripe-python library mirrors the API closely.
  • PCI‑DSS compliance: By using Stripe Elements or Checkout, sensitive card data never touches your servers.
  • Extensive payment methods: Credit cards, Apple Pay, Google Pay, ACH, and even international wallets are supported out of the box.
  • Built‑in subscription management: Recurring billing, trial periods, and prorations are handled automatically.
  • Scalable webhooks: Real‑time event handling lets you update order status, send receipts, or trigger fulfillment workflows.

Prerequisites

Make sure you have the following before you start the integration:

  1. Python 3.9+ and Django 4.2+ installed.
  2. A Stripe account (you can sign up for a free test account).
  3. Basic knowledge of Django models, views, and templates.
  4. Git installed for version control (optional but recommended).

Step‑by‑Step Django Stripe Integration

1. Install Required Packages

First, add the Stripe Python library and Django‑CORS‑Headers (if you plan to use a JavaScript front‑end) to your project:

pip install stripe django-cors-headers

Then, add 'corsheaders' to INSTALLED_APPS and insert the middleware before Django’s common middleware:

# settings.py
INSTALLED_APPS = [
    # …
    'corsheaders',
    # …
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    # …
]

CORS_ALLOW_ALL_ORIGINS = True  # For development only; restrict in production

2. Configure Stripe Keys

Store your secret and publishable keys safely using Django’s settings.py and environment variables. Never hard‑code them in source code.

# .env (example)
STRIPE_PUBLISHABLE_KEY=pk_test_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_SECRET_KEY=sk_test_XXXXXXXXXXXXXXXXXXXXXXXX
# settings.py
import os
from pathlib import Path
from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(os.path.join(BASE_DIR, '.env'))

STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY')
STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY')

3. Create a Simple Product Model

For demonstration, let’s define a Product model that holds the price in cents (Stripe expects the smallest currency unit).

# shop/models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    price_cents = models.PositiveIntegerField(help_text="Price in cents, e.g., $9.99 = 999")
    
    def __str__(self):
        return self.name

    @property
    def price_display(self):
        return f"${self.price_cents / 100:.2f}"

4. Build the Checkout View

We’ll use Stripe Checkout, a pre‑built, PCI‑compliant payment page. The view creates a Checkout Session and redirects the user.

# shop/views.py
import stripe
from django.conf import settings
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from .models import Product

stripe.api_key = settings.STRIPE_SECRET_KEY

def create_checkout_session(request, product_id):
    product = get_object_or_404(Product, pk=product_id)

    domain = request.build_absolute_uri('/')[:-1]  # Remove trailing slash
    checkout_session = stripe.checkout.Session.create(
        payment_method_types=['card'],
        line_items=[{
            'price_data': {
                'currency': 'usd',
                'product_data': {
                    'name': product.name,
                },
                'unit_amount': product.price_cents,
            },
            'quantity': 1,
        }],
        mode='payment',
        success_url=domain + reverse('checkout_success') + '?session_id={CHECKOUT_SESSION_ID}',
        cancel_url=domain + reverse('checkout_cancel'),
    )
    return redirect(checkout_session.url, code=303)

5. Define Success and Cancel Views

These simple views render a template that thanks the user or informs them of a cancelled payment.

# shop/views.py (continued)
from django.shortcuts import render

def checkout_success(request):
    return render(request, 'shop/success.html')

def checkout_cancel(request):
    return render(request, 'shop/cancel.html')

6. Add URLs

# shop/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('checkout//', views.create_checkout_session, name='create_checkout'),
    path('success/', views.checkout_success, name='checkout_success'),
    path('cancel/', views.checkout_cancel, name='checkout_cancel'),
]

7. Create Front‑End Templates with Stripe.js

Even though Stripe Checkout handles most of the UI, you may want a custom button that loads the session via JavaScript. Below is a minimal template that lists products and triggers the checkout flow.

<!-- templates/shop/product_list.html -->
<h2>Our Products</h2>
<ul>
{% for product in products %}
    <li>
        <strong>{{ product.name }}</strong> – {{ product.price_display }}<br>
        {{ product.description|linebreaksbr }}<br>
        <button class="checkout-button" data-id="{{ product.id }}">Buy Now</button>
    </li>
{% endfor %}
</ul>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe("{{ stripe_publishable_key }}");
    document.querySelectorAll('.checkout-button').forEach(btn => {
        btn.addEventListener('click', () => {
            const productId = btn.dataset.id;
            fetch(`/checkout/${productId}/`, {
                method: 'GET',
                redirect: 'follow'
            })
            .then(response => {
                if (response.redirected) {
                    window.location.href = response.url;
                }
            })
            .catch(err => console.error('Checkout error:', err));
        });
    });
</script>

Don’t forget to pass stripe_publishable_key from the view context.

# shop/views.py (add product list view)
def product_list(request):
    products = Product.objects.all()
    return render(request, 'shop/product_list.html', {
        'products': products,
        'stripe_publishable_key': settings.STRIPE_PUBLISHABLE_KEY,
    })

8. Secure the Webhook Endpoint

Stripe sends asynchronous events (e.g., checkout.session.completed) to a webhook URL you configure in the Dashboard. Handling these events ensures your database stays in sync with payment status.

# shop/webhooks.py
import json, stripe
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import Product

stripe.api_key = settings.STRIPE_SECRET_KEY

@csrf_exempt
def stripe_webhook(request):
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    endpoint_secret = settings.STRIPE_WEBHOOK_SECRET  # Set in .env

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return HttpResponse(status=400)

    # Handle the event
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        # Example: mark order as paid, send email, etc.
        # You could create an Order model and link it here.
        print(f"Payment succeeded for session {session['id']}")
    # ... handle other event types ...

    return HttpResponse(status=200)

Add the webhook URL to urls.py and register the endpoint secret in your environment.

# shop/urls.py (add)
path('webhook/stripe/', views.stripe_webhook, name='stripe_webhook')

Testing the Integration

Stripe provides a set of test card numbers that you can use without real money. Follow these steps to verify everything works before going live:

  • Set STRIPE_SECRET_KEY and STRIPE_PUBLISH

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *