You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
**Production-ready Flask SaaS template**
4
4
5
-
Multi-tenant workspaces, Stripe billing, OAuth, and team collaboration out of the box. Build your product, not infrastructure.
5
+
Multi-tenant workspaces, subscription billing (Stripe or Chargebee), OAuth, and team collaboration out of the box. Build your product, not infrastructure.
Stripe integration for subscriptions and payments.
3
+
Subscription billing with Stripe or Chargebee.
4
4
5
5
## Overview
6
6
7
-
ReadyKit uses Stripe's hosted pages for billing - no custom checkout UI to build or maintain. Users upgrade via Stripe Checkout and manage subscriptions through the Stripe Customer Portal.
7
+
ReadyKit uses hosted payment pages - no custom checkout UI to build or maintain. Users upgrade via the provider's checkout page and manage subscriptions through their portal.
8
+
9
+
::: warning Choose Your Provider First
10
+
Select your billing provider before going to production. Switching providers after users have subscribed requires manual migration of customer data. Set `BILLING_PROVIDER` in your environment and stick with it.
11
+
:::
12
+
13
+
## Supported Providers
14
+
15
+
| Provider | Best For |
16
+
|----------|----------|
17
+
|**Stripe**| Most SaaS apps, US/EU focus, extensive API |
18
+
|**Chargebee**| Complex billing needs, subscription management, international |
8
19
9
20
## Plans
10
21
@@ -31,6 +42,8 @@ From [Stripe Dashboard](https://dashboard.stripe.com):
-**Authentication**: Basic Auth with your configured username/password
104
+
-**Events to listen for**:
105
+
-`subscription_cancelled`
106
+
-`payment_failed`
107
+
108
+
::: info Chargebee Webhook Security
109
+
Chargebee uses HTTP Basic Auth for webhook verification (not HMAC signatures like Stripe). Always configure `CHARGEBEE_WEBHOOK_USERNAME` and `CHARGEBEE_WEBHOOK_PASSWORD` in production. Unauthenticated webhooks are only allowed in debug mode for local testing.
110
+
:::
111
+
56
112
## How Billing Works
57
113
58
114
### Upgrade Flow
59
115
60
116
```
61
117
User clicks "Upgrade"
62
-
→ Create Stripe Checkout session
63
-
→ Redirect to Stripe
118
+
→ Create checkout session
119
+
→ Redirect to provider's hosted page
64
120
→ User completes payment
65
-
→ Stripe redirects to success URL
66
-
→ Validate session_id
121
+
→ Provider redirects to success URL
122
+
→ Validate session
67
123
→ Upgrade workspace to Pro
68
124
```
69
125
@@ -83,12 +139,13 @@ def upgrade(workspace_id):
83
139
84
140
### Success Callback
85
141
86
-
The success URL includes a `session_id` that's validated server-side:
142
+
The success URL includes a session ID that's validated server-side:
87
143
88
144
```python
89
145
@app.route("/billing/success")
90
146
defbilling_success():
91
-
session_id = request.args.get("session_id")
147
+
# Works with both Stripe (session_id) and Chargebee (id)
148
+
session_id = request.args.get("session_id") or request.args.get("id")
Webhooks update workspace status automatically when billing changes:
130
187
188
+
### Stripe Events
189
+
131
190
| Event | Action |
132
191
|-------|--------|
133
192
|`checkout.session.completed`| Upgrade workspace to Pro, save customer_id |
134
193
|`customer.subscription.deleted`| Downgrade workspace to Free |
135
194
|`invoice.payment_failed`| Downgrade workspace to Free |
136
195
196
+
### Chargebee Events
197
+
198
+
| Event | Action |
199
+
|-------|--------|
200
+
|`subscription_cancelled`| Downgrade workspace to Free |
201
+
|`payment_failed`| Downgrade workspace to Free |
202
+
203
+
::: tip Chargebee Upgrades
204
+
Chargebee upgrades are handled via the redirect flow only (not webhooks). This is intentional - the webhook would arrive after the redirect in most cases anyway.
205
+
:::
206
+
137
207
### Idempotency
138
208
139
-
Webhooks are idempotent - duplicate events are safely ignored using the `StripeEvent` model:
209
+
Webhooks are idempotent - duplicate events are safely ignored using the `BillingEvent` model:
140
210
141
211
```python
142
212
# Duplicate events are caught by unique constraint
0 commit comments