HyperSaaS
BackendSubscriptions

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:

  1. Gets or creates a StripeUser for the authenticated user
  2. Creates a Stripe Checkout Session with the price and customer
  3. 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

SettingDefaultDescription
STRIPE_API_SECRETStripe secret key
STRIPE_PUBLISHABLE_KEYStripe publishable key (frontend)
FRONT_END_BASE_URLFrontend URL for redirects
NEW_USER_FREE_TRIAL_DAYS7Trial period length
DEFAULT_PAYMENT_METHOD_TYPES["card"]Accepted payment methods
DEFAULT_CHECKOUT_MODE"subscription"Checkout mode
ALLOW_PROMOTION_CODEStrueEnable promo codes in checkout
CHECKOUT_SUCCESS_URL_PATH"payment"Success redirect path
CHECKOUT_CANCEL_URL_PATH"dashboard"Cancel redirect path

On this page