Python Django Stripe Payment Integration

Written by

in

Integrating Stripe payments into a Python Django application is one of the most effective ways to monetize your web services while providing a smooth, secure checkout experience. In this guide we’ll walk through every step of the Python Django Stripe payment integration process—from installing the required packages to handling webhooks and testing in live mode. By the end, you’ll have a production‑ready payment flow that scales with your business and boosts conversion rates.

Why Choose Stripe for Django Projects?

  • Developer‑friendly API: Stripe’s well‑documented REST endpoints and official Python SDK make it easy to implement complex payment flows.
  • PCI‑DSS compliance: By using Stripe Elements or Checkout, sensitive card data never touches your server, reducing compliance burden.
  • Global support: Accept credit cards, debit cards, Apple Pay, Google Pay, and local payment methods in over 135 currencies.
  • Extensible features: Subscriptions, one‑time payments, coupons, and tax calculations are built‑in.

Prerequisites

Before diving into code, ensure you have the following ready:

  1. A Python 3.9+ environment.
  2. Django 3.2 or newer installed.
  3. A Stripe account (sign up at dashboard.stripe.com).
  4. Basic knowledge of Django models, views, and templates.

Step 1: Install Stripe and Django Packages

First, add the official Stripe Python library to your project and make sure Django is up to date.

pip install stripe django-environ

We’ll use django-environ to keep API keys out of the source code.

Step 2: Configure Environment Variables

Create a .env file at the root of your project and add your Stripe keys:

# .env
STRIPE_PUBLIC_KEY=pk_test_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_SECRET_KEY=sk_test_XXXXXXXXXXXXXXXXXXXXXXXX
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXXXX

Load these variables in settings.py:

import environ
env = environ.Env()
environ.Env.read_env()

STRIPE_PUBLIC_KEY = env('STRIPE_PUBLIC_KEY')
STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY')
STRIPE_WEBHOOK_SECRET = env('STRIPE_WEBHOOK_SECRET')

Step 3: Set Up a Simple Product Model

Even if you’re selling a digital download or a subscription, a model helps keep the code clean.

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")
    stripe_price_id = models.CharField(max_length=255, blank=True)

    def __str__(self):
        return self.name

After creating migrations, run python manage.py migrate. Populate a few products via the admin or Django shell and note their price_cents values.

Step 4: Create Stripe Prices for Your Products

Stripe recommends creating a Price object for each product. You can do this manually in the dashboard or programmatically. Below is a one‑off script you can run once:

import stripe
from django.conf import settings
from myapp.models import Product

stripe.api_key = settings.STRIPE_SECRET_KEY

for product in Product.objects.all():
    price = stripe.Price.create(
        unit_amount=product.price_cents,
        currency='usd',
        product_data={'name': product.name},
    )
    product.stripe_price_id = price.id
    product.save()
    print(f'Created Stripe price {price.id} for {product.name}')

Step 5: Build the Checkout View

We’ll use Stripe Checkout for a frictionless UI. The view creates a Checkout Session and redirects the user.

from django.shortcuts import get_object_or_404, redirect
from django.views import View
import stripe
from django.conf import settings

stripe.api_key = settings.STRIPE_SECRET_KEY

class CreateCheckoutSessionView(View):
    def post(self, request, *args, **kwargs):
        product_id = request.POST.get('product_id')
        product = get_object_or_404(Product, pk=product_id)

        session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[{
                'price': product.stripe_price_id,
                'quantity': 1,
            }],
            mode='payment',
            success_url=request.build_absolute_uri('/success/') + '?session_id={CHECKOUT_SESSION_ID}',
            cancel_url=request.build_absolute_uri('/cancel/'),
        )
        return redirect(session.url, code=303)

Step 6: Add URLs and Templates

Map the view and create simple templates for product listing, success, and cancel pages.

# urls.py
from django.urls import path
from .views import CreateCheckoutSessionView, success_view, cancel_view

urlpatterns = [
    path('checkout/', CreateCheckoutSessionView.as_view(), name='checkout'),
    path('success/', success_view, name='success'),
    path('cancel/', cancel_view, name='cancel'),
]

Example product list template ( product_list.html ):

<h2>Available Products</h2>
<ul>
{% for product in products %}
    <li>
        <strong>{{ product.name }}</strong> – ${{ product.price_cents|floatformat:2|divisibleby:100 }}
<form action="{% url 'checkout' %}" method="post"> {% csrf_token %} <input type="hidden" name="product_id" value="{{ product.id }}"> <button type="submit">Buy Now</button> </form> </li> {% endfor %} </ul>

Step 7: Handling Stripe Webhooks

Webhooks let your app react to asynchronous events such as successful payments, refunds, or disputes. Create a dedicated endpoint that validates the signature and updates order status.

import json
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
import stripe
from django.conf import settings

stripe.api_key = settings.STRIPE_SECRET_KEY

@csrf_exempt
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('Invalid payload or signature')

    # Handle the event
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        # Example: mark order as paid
        handle_successful_payment(session)
    # Add more event types as needed

    return HttpResponse(status=200)

def handle_successful_payment(session):
    # Retrieve the line items to know which product was bought
    line_items = stripe.checkout.Session.list_line_items(session['id'])
    for item in line_items['data']:
        # Here you could create an Order model instance, send email, etc.
        print(f"Payment for Stripe price {item['price']['id']} succeeded.")

Don’t forget to register the webhook URL in your Stripe dashboard and enable the events you plan to handle (e.g., checkout.session.completed, invoice.payment_failed).

Step 8: Testing the Integration

Stripe provides a set of test cards that simulate various scenarios. Follow these steps to ensure everything works before going live:

  • Set STRIPE_PUBLIC_KEY and STRIPE_SECRET_KEY to the test keys from your dashboard.
  • Use the test card 4242 4242 4242 4242 with any future expiration date and any CVC.
  • Trigger edge cases: 4000 0000 0000 0341 for a declined card, 4000 0000 0000 9995 for a charge that requires authentication.
  • Check the webhook endpoint locally with Stripe CLI:
    stripe listen --forward-to localhost:8000/webhook/

Step 9: Going Live

When you’re ready to accept real payments, swap the test keys for the live ones in your .env file. Also, verify the following:

  • All URLs (success, cancel, webhook) are served over HTTPS.
  • Your webhook endpoint is reachable from the public internet.
  • Compliance: Ensure you have a clear refund policy and display it to customers.

After the switch, monitor the Stripe Dashboard for any unexpected errors and adjust your webhook handling as needed.

Common Pitfalls & How to Avoid Them

1. Storing Card Data Locally

Never store raw card numbers or CVC codes. Use Stripe Elements or Checkout, which tokenizes the data for you.

2. Forgetting to Verify Webhook Signatures

Skipping signature verification opens the door to forged events. Always use stripe.Webhook.construct_event

Comments

Leave a Reply

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