High level flow of recurring product
[ 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
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”
Concept | Purpose |
---|---|
Customer | Represents our Django user on Stripe |
Product + Price | Recurring monthly/annual subscription tier |
Checkout Session | Stripe-hosted payment page |
Subscription | Tied to a customer & price |
Webhook | Stripe notifies us of lifecycle events |
[ 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
[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 Event | What It Means | Action in Django App |
---|---|---|
invoice.payment_succeeded | Payment confirmed | Upgrade user to 'pro' |
invoice.payment_failed | Payment failed | Log failure, show warning |
customer.subscription.updated | Tier changed (e.g. plan downgrade) | Sync user’s tier |
customer.subscription.deleted | Subscription canceled (grace expired) | Downgrade to 'free' |
It is better not to mess up your users app so creating a payments app is neater
$ 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
Path | Purpose |
---|---|
/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