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