Django Stripe integration tutorial – high level

High level flow of recurring product

Python
[ User ]               [ Django Backend ]              [ Stripe ]
   |                           |                          |
   |  Clicks Upgrade Button    |                          |
   |-------------------------->|                          |
   |                           |  Create Stripe Customer  |
   |                           |------------------------->|
   |                           |                          |
   |                           |  Create Checkout Session |
   |                           |------------------------->|
   |                           |                          |
   |                           |<------ Session URL ------|
   |<----- Redirect -----------|                          |
   |--------------------------> Stripe-hosted Checkout UI |
   |                           |                          |
   |          Success/Cancel Redirect to Django           |
   |<--------------------------|                          |
   |                           |                          |
   |                           | <-- Stripe Webhooks ---->|

Django entities vs Stripe entities

On the django side you’ll need a user column to store the tier of your user

Python
user.tier: 'free' | 'pro'
# Allow users to upgrade to 'pro'
# Automatically downgrade to 'free' if the subscription ends


State changes:
  [*] --> Free : User registers
  Free --> Pro : Successful subscription
  Pro --> Free : Subscription cancelled/failed
  Pro --> Pro : Successful renewal
  Free --> Free : Payment declined

With stripe, you interact based on their entities. Note: below you only own “Django user”

ConceptPurpose
CustomerRepresents our Django user on Stripe
Product + PriceRecurring monthly/annual subscription tier
Checkout SessionStripe-hosted payment page
SubscriptionTied to a customer & price
WebhookStripe notifies us of lifecycle events
Python
[ Product: Pro Tier ]
        |
[ Price: $9/month ]
        |
[ Subscription ] <-- linked to -- [ Customer ] <-- maps to --> [ Django User ]

Explicit Action vs Automated Webhooks

Upgrade Flow (Explicit Action)
[User clicks “Upgrade”] → [Django backend creates Stripe customer]
→ [Django backend creates checkout session]
→ [User is redirected to Stripe]
→ [User completes payment]
→ [User redirected back to success URL]
→ [Stripe triggers webhook: payment_succeeded]

Automated webhooks

Python
[Renewal Day]

[Payment Attempt]

[Success?] → Yes → OK
     ↓ No
[Retry in 2 days]

[Retry in 4 days]

[Grace Ends (7 days)] → [Subscription canceled] → Webhook → Downgrade

Core Webhook Events

Webhook EventWhat It MeansAction in Django App
invoice.payment_succeededPayment confirmedUpgrade user to 'pro'
invoice.payment_failedPayment failedLog failure, show warning
customer.subscription.updatedTier changed (e.g. plan downgrade)Sync user’s tier
customer.subscription.deletedSubscription canceled (grace expired)Downgrade to 'free'

It is better not to mess up your users app so creating a payments app is neater

Python
$ python manage.py startapp payments

vocal_notepad/
├── users/
│   └── views.py         ← Handles /upgrade/, /success/
├── payments/
│   ├── stripe_utils.py  ← Stripe setup, customer & checkout
│   ├── webhooks.py      ← Webhook handlers
│   └── urls.py          ← /stripe/webhook/

Key URLs

PathPurpose
/users/upgrade/Kicks off Stripe checkout
/users/upgrade/success/Landing page after success
/payments/stripe/webhook/Stripe sends payment lifecycle info

With this integration:

  • Users start as 'free'
  • Can upgrade via Stripe securely
  • Your system listens to Stripe’s webhook events to keep user tier in sync
  • Grace periods prevent sudden access loss, improving retention