Python Django Stripe Payment Integration

Written by

in

Integrating Stripe with a Django application is one of the most popular ways to add secure, fast, and flexible payment processing to your Python‑powered website. Whether you’re building a SaaS platform, an online store, or a donation portal, Stripe’s robust API combined with Django’s clean architecture lets you handle one‑time charges, subscriptions, and even complex marketplace payouts with minimal friction. In this guide we’ll walk through every step—from setting up a Stripe account to wiring up webhooks—so you can launch a production‑ready payment flow in minutes.

Why Choose Stripe for Django Projects?

  • Developer‑first API: Comprehensive documentation, client libraries for Python, and clear error messages.
  • PCI‑DSS compliance made easy: Use Stripe Elements or Checkout to keep sensitive card data off your servers.
  • Support for global payments: Credit cards, Apple Pay, Google Pay, SEPA, and more.
  • Built‑in subscription management: Handles recurring billing, trial periods, and proration automatically.
  • Scalable webhooks: Real‑time notifications for successful payments, disputes, and refunds.

Prerequisites

  1. Python 3.8+ and a virtual environment.
  2. Django 4.x installed (or the latest stable version).
  3. A Stripe account (sign up at stripe.com).
  4. Basic knowledge of Django views, models, and templates.

Step‑by‑Step Integration

1. Install the Stripe Python library

pip install stripe

After installation, add stripe to your INSTALLED_APPS if you plan to use any of Stripe’s Django‑specific utilities (optional).

2. Configure Stripe settings

Store your secret and publishable keys securely. The recommended approach is to use environment variables.

# settings.py
import os

STRIPE_PUBLIC_KEY = os.getenv('STRIPE_PUBLIC_KEY')
STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY')
STRIPE_WEBHOOK_SECRET = os.getenv('STRIPE_WEBHOOK_SECRET')

3. Create a simple Product model

For a one‑time purchase you can store the price in cents to avoid floating‑point issues.

# 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., $10.00 = 1000)")

    def __str__(self):
        return self.name

4. Build the checkout view

We’ll use Stripe Checkout for the quickest integration. It redirects users to a Stripe‑hosted payment page, keeping your PCI scope minimal.

# 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)

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

5. Add success and cancel pages

These simple templates confirm the transaction outcome to the user.

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

urlpatterns = [
    path('checkout//', views.create_checkout_session, name='checkout'),
    path('checkout/success/', views.checkout_success, name='checkout_success'),
    path('checkout/cancel/', views.checkout_cancel, name='checkout_cancel'),
]
# views.py (continued)
from django.http import HttpResponse

def checkout_success(request):
    return HttpResponse("

Payment successful! 🎉

") def checkout_cancel(request): return HttpResponse("

Payment cancelled. 😞

")

6. Render the payment button with Stripe.js

If you prefer a seamless inline form instead of a redirect, use Stripe Elements. Below is a minimal template that loads the publishable key and creates a token.

<!-- checkout.html -->
<script src="https://js.stripe.com/v3/"></script>
<form id="payment-form" method="post">
    {% csrf_token %}
    <div id="card-element"></div>
    <button type="submit">Pay {{ product.price_cents|floatformat:2 }} USD</button>
    <div id="card-errors" role="alert"></div>
</form>

<script>
    const stripe = Stripe("{{ STRIPE_PUBLIC_KEY }}");
    const elements = stripe.elements();
    const card = elements.create("card");
    card.mount("#card-element");

    const form = document.getElementById("payment-form");
    form.addEventListener("submit", async (e) => {
        e.preventDefault();
        const {error, token} = await stripe.createToken(card);
        if (error) {
            document.getElementById("card-errors").textContent = error.message;
        } else {
            // Append token to form and submit
            const hiddenInput = document.createElement("input");
            hiddenInput.setAttribute("type", "hidden");
            hiddenInput.setAttribute("name", "stripeToken");
            hiddenInput.setAttribute("value", token.id);
            form.appendChild(hiddenInput);
            form.submit();
        }
    });
</script>

7. Process the token on the server

When the form posts back, create a charge using the received token.

# views.py (token handling)
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render

@csrf_exempt
def charge(request, product_id):
    if request.method == "POST":
        token = request.POST.get("stripeToken")
        product = get_object_or_404(Product, pk=product_id)

        try:
            charge = stripe.Charge.create(
                amount=product.price_cents,
                currency="usd",
                source=token,
                description=f"Purchase of {product.name}",
            )
            return HttpResponse("

Payment succeeded!

") except stripe.error.CardError as e: return HttpResponse(f"

Card error: {e.user_message}

") return HttpResponse("

Invalid request

")

8. Set up Stripe webhooks

Webhooks keep your database in sync with Stripe events such as checkout.session.completed, invoice.payment_succeeded, or charge.refunded. Create an endpoint that validates the signature and processes the payload.

# urls.py (add webhook endpoint)
path('stripe/webhook/', views.stripe_webhook, name='stripe_webhook')
# views.py (webhook handler)
import json
from django.http import HttpResponseBadRequest, HttpResponse

def stripe_webhook(request):
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return HttpResponseBadRequest()

    # Handle the event
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        # Example: mark order as paid
        # order = Order.objects.get(stripe_session_id=session.id)
        # order.status = 'paid'
        # order.save()
    elif event['type'] == 'invoice.payment_failed':
        # Take action on failed subscription payment
        pass

    return HttpResponse(status=200)

Testing Your Integration

  • Use Stripe’s test mode: Replace your live keys with the test keys from the Dashboard.
  • Test cards: 4242 4242 4242 4242 works for successful payments; 4000 0000 0000 9995 simulates a declined card.
  • Run webhook tests: The Dashboard lets you send sample events to your local /stripe/webhook/ endpoint.

Best Practices for Production

  • Never expose secret keys: Keep STRIPE_SECRET_KEY out of version control and use a secrets manager.
  • Enable HTTPS: Stripe requires TLS for all live requests.
  • Store minimal card data: Rely on tokens or PaymentMethods; if you must store identifiers, encrypt them.
  • Handle idempotency: Pass an idempotency_key when creating charges to avoid duplicate transactions.
  • Log webhook failures: Persist the raw payload for debugging and implement retry logic.

Advanced Topics

Recurring Subscriptions

Stripe’s subscription API abstracts billing cycles, proration, and trial

Comments

Leave a Reply

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