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:
- A Python 3.9+ environment.
- Django 3.2 or newer installed.
- A Stripe account (sign up at dashboard.stripe.com).
- 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_KEYandSTRIPE_SECRET_KEYto 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 0341for a declined card,4000 0000 0000 9995for 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
Leave a Reply