Subscriptions and billing
Trak uses Stripe + dj-stripe for subscriptions. The core pieces are:
- Stripe billing data (products/prices)
- Local Stripe models synced via
dj-stripe - Trak metadata in
trak/apps/subscriptions/metadata.py
Setup
- Create products/prices in Stripe (test mode for development).
- Set Stripe keys in
.env: STRIPE_TEST_PUBLIC_KEYSTRIPE_TEST_SECRET_KEYSTRIPE_LIVE_PUBLIC_KEYSTRIPE_LIVE_SECRET_KEYSTRIPE_LIVE_MODE- Run bootstrap:
python manage.py bootstrap_subscriptions
If you are not using the embedded Stripe pricing table, update ACTIVE_PRODUCTS and ACTIVE_PLAN_INTERVALS in metadata.py with the output from the bootstrap command.
If you are using the embedded pricing table, set STRIPE_PRICING_TABLE_ID.
Pricing table
Embedded Stripe pricing table
- Configure products and pricing in Stripe.
- Set the confirmation URL to:
http://localhost:8000/subscriptions/confirm/?session_id={CHECKOUT_SESSION_ID}(dev)https://<yourdomain>/subscriptions/confirm/?session_id={CHECKOUT_SESSION_ID}(prod)
Make sure the confirmation URL is applied to every price.
In-app pricing table
Pricing is defined in metadata.py. Re-run bootstrap_subscriptions whenever Stripe products change.
Customer portal
Enable the Stripe Billing Customer Portal in Stripe and ensure webhooks are configured.
Webhooks
Development
Use the Stripe CLI to create a local webhook and forward events:
stripe listen --print-secret
./manage.py bootstrap_dev_webhooks --secret <your_secret>
Then run the stripe listen --forward-to ... command output by the bootstrap command.
Production
Create a webhook endpoint in Django admin (/admin/djstripe/webhookendpoint/add/) and enable at least:
checkout.session.completedcustomer.subscription.updatedcustomer.subscription.deleted
Dj-stripe will sync the endpoint to Stripe.
Per-seat billing
If you use per-seat billing, update the subscription quantity based on your business logic. Teams-based billing updates quantity automatically.
The periodic sync runs via Celery; you can also trigger it with:
python manage.py sync_subscriptions
Troubleshooting
- Wrong callback domain: update the Django
SiteandUSE_HTTPS_IN_ABSOLUTE_URLS. - Embedded pricing table not updating subscriptions: ensure confirmation URLs are set or webhooks are running.
- Webhook signature errors: verify Stripe keys and
DJSTRIPE_WEBHOOK_SECRET.