Django Seedkit

Blog · Billing

How to add Stripe Checkout to Django

Stripe hosts the payment page. No card number reaches the Django server, and the project needs no plan-switch interface. Django needs two views and one field on the user model.

Install the official Stripe library and read the keys from the environment.

uv add stripe
# config/settings/base.py
import stripe as _stripe

STRIPE_PUBLISHABLE_KEY = env("STRIPE_PUBLISHABLE_KEY", default="")
STRIPE_SECRET_KEY = env("STRIPE_SECRET_KEY", default="")
STRIPE_WEBHOOK_SECRET = env("STRIPE_WEBHOOK_SECRET", default="")

_stripe.api_key = STRIPE_SECRET_KEY

Store the customer ID

Add two fields to the user model. Use a CharField for stripe_customer_id, and a BooleanField for is_subscribed. Then run makemigrations. Create the Stripe customer one time, and use the same customer for every later session and portal visit.

def get_or_create_customer(user):
    if user.stripe_customer_id:
        return user.stripe_customer_id
    customer = stripe.Customer.create(
        email=user.email,
        idempotency_key=f"customer:user:{user.pk}",
    )
    user.stripe_customer_id = customer.id
    user.save(update_fields=["stripe_customer_id"])
    return customer.id

A double click on the upgrade button can create two Stripe customers. Both requests read an empty stripe_customer_id, and both call Customer.create. The subscription then belongs to one of the two customers. The idempotency key makes the second call return the first customer. Do not use a row lock instead. A row lock stays open across the network call to Stripe, and it blocks a worker when Stripe is slow.

The checkout session

# billing/views.py
@login_required
def create_checkout_session(request):
    session = stripe.checkout.Session.create(
        customer=get_or_create_customer(request.user),
        line_items=[{"price": request.POST["price_id"], "quantity": 1}],
        mode="subscription",
        success_url=request.build_absolute_uri("/billing/success/"),
        cancel_url=request.build_absolute_uri("/billing/cancel/"),
    )
    assert session.url
    return redirect(session.url)

A redirect to success_url is not proof of payment. The user can close the browser before Stripe completes the charge, and a card can fail later. Set is_subscribed from the webhook, not from the redirect.

Or give the task to an agent. It reads the same setup from a maintained reference file and applies it to the repository:

/django-seedkit add Stripe subscriptions

The reference holds the full setup, the webhook handler, and the dj-stripe alternative: billing.md · What Django Seedkit is