Checkout & Portal
Stripe Checkout session creation and customer portal integration.
Checkout Flow
1. Create Checkout Session
POST /api/subscriptions/checkout/{
"price_id": "price_1234567890",
"workspace_id": "ws-uuid"
}The server:
- Gets or creates a
StripeUserfor the authenticated user - Creates a Stripe Checkout Session with the price and customer
- Returns the session ID for client-side redirect
{
"session_id": "cs_live_..."
}2. Redirect to Stripe
The frontend redirects the user to Stripe's hosted checkout page:
const stripe = await loadStripe(publishableKey);
await stripe.redirectToCheckout({ sessionId: data.session_id });3. Post-Checkout
After payment, Stripe redirects to {FRONT_END_BASE_URL}/payment?session={CHECKOUT_SESSION_ID}.
Stripe fires webhook events that update the local database (see Webhooks).
Checkout Parameters
The checkout session is configured with:
{
"customer": customer_id,
"success_url": f"{FRONT_END_BASE_URL}/payment?session={{CHECKOUT_SESSION_ID}}",
"cancel_url": f"{FRONT_END_BASE_URL}/dashboard",
"payment_method_types": ["card"],
"mode": "subscription",
"line_items": [{"price": price_id, "quantity": 1}],
"subscription_data": {
"trial_end": trial_end_timestamp,
"metadata": {"workspace_id": workspace_id}
},
"metadata": {"workspace_id": workspace_id},
"allow_promotion_codes": True
}Free Trial
If NEW_USER_FREE_TRIAL_DAYS is set (default: 7), new subscriptions include a trial period:
trial_end = now() + timedelta(days=NEW_USER_FREE_TRIAL_DAYS + 1)The extra day accounts for timezone rounding. Stripe requires the trial end to be at least 48 hours in the future.
Set NEW_USER_FREE_TRIAL_DAYS = None to disable trials.
Customer Portal
The Stripe Customer Portal lets users manage their subscription without custom UI:
- View current subscription
- Change plan (upgrade/downgrade)
- Update payment method
- View billing history
- Cancel subscription
Get Portal URL
POST /api/subscriptions/customer-portal/{
"url": "https://billing.stripe.com/p/session/..."
}The frontend redirects the user to this URL. After they're done, Stripe redirects back to {FRONT_END_BASE_URL}/dashboard/.
Configuration
Configure the Customer Portal in the Stripe Dashboard:
- Enable/disable features (plan changes, cancellations)
- Set cancellation policy (immediate vs end of period)
- Customize branding
- Configure proration behavior
Available Prices Endpoint
GET /api/subscriptions/subscribable-product/Returns pricing information for the pricing page:
Anonymous users: All active prices across all products.
Authenticated users: Active prices excluding products the user is already subscribed to.
Response:
[
{
"price_id": "price_123",
"product_id": "prod_abc",
"name": "Pro Plan",
"price": 2900,
"freq": "month_1",
"currency": "usd",
"nickname": "Pro Monthly",
"services": [
{"feature_id": "chat"},
{"feature_id": "rag"},
{"feature_id": "export"}
]
}
]Configuration
| Setting | Default | Description |
|---|---|---|
STRIPE_API_SECRET | — | Stripe secret key |
STRIPE_PUBLISHABLE_KEY | — | Stripe publishable key (frontend) |
FRONT_END_BASE_URL | — | Frontend URL for redirects |
NEW_USER_FREE_TRIAL_DAYS | 7 | Trial period length |
DEFAULT_PAYMENT_METHOD_TYPES | ["card"] | Accepted payment methods |
DEFAULT_CHECKOUT_MODE | "subscription" | Checkout mode |
ALLOW_PROMOTION_CODES | true | Enable promo codes in checkout |
CHECKOUT_SUCCESS_URL_PATH | "payment" | Success redirect path |
CHECKOUT_CANCEL_URL_PATH | "dashboard" | Cancel redirect path |