Stripe

Accept one-time payments or subscriptions, open Stripe-hosted billing pages, and unlock the right features after verified webhook events.

Checkout — send users to pay

billing.checkout opens Stripe Checkout in a new tab. Use an existing priceId, or pass an amount and product for a one-off inline price. Use billing.createCheckout when your UI should decide how to open the returned URL.

import { billing } from "@/integrations/stripe";

// One-off payment
await billing.checkout({ mode: "payment", product: { name: "Pro" }, amount: 19900 });

// Subscription with trial
await billing.checkout({ mode: "subscription", priceId: "price_xxx", trialDays: 7 });

// Raw — get the URL, open it yourself
const { checkoutUrl } = await billing.createCheckout({ mode: "payment", amount: 19900 });

Local payments require ngrok

Before testing checkout locally, start the Supabase runtime and turn on ngrok in Functions. Stripe cannot call 127.0.0.1, so its webhook must point to the public ngrok URL ending in /functions/v1/stripe-webhook.

Keep ngrok running while you test. Without an active tunnel and a matching STRIPE_WEBHOOK_SECRET, Checkout may report a successful payment, but the local backend will not receive the signed event and the extension will not unlock access.

Portal — let users manage their subscription

Send customers to Stripe's hosted portal to update payment methods, cancel, or switch plans. Your extension does not need to recreate billing settings.

await billing.portal();
const { portalUrl } = await billing.createPortal(); // URL-only version

Entitlements — gate paid features

Use billing.hasAccess("pro") to render the right interface and billing.requireAccess("pro") before a paid action. Keep the authoritative check in a backend function; frontend checks only control presentation.

import { billing } from "@/integrations/stripe";
import { ChromeShipError } from "@/integrations/shared";

const hasPro = await billing.hasAccess("pro");

try {
  await billing.requireAccess("pro");
  // ...run the paid feature
} catch (error) {
  if (error instanceof ChromeShipError && error.code === "entitlement_required") {
    await billing.checkout({ mode: "payment", priceId: "price_xxx" });
  }
}

// Shortcut: run a callback only if entitled
await billing.withAccess("pro", async () => runPaidFeature());

// Listen for access changes across surfaces
billing.onAccessChange((cache) => { /* show/hide premium UI */ });

Catalog — render pricing from Stripe

Fetch active products and prices from Stripe so pricing stays in sync without hardcoded price IDs in the interface.

const plans = await billing.catalog();
// [{ priceId, productId, name, amount, currency, interval, ... }]

Next

Try the Stripe + Auth Starter for an end-to-end walkthrough. Deploy your Stripe Edge Functions. Set production webhook secrets when you're ready to ship.