STRIPE STARTER · THE STRIPE SDK HELPERS
The Stripe SDK helpers
src/lib/stripe.ts (server) and src/lib/stripe-client.ts (browser). Plus the helpers — getCustomerId, getResourceId, getStripeRedirectUrl, constructWebhookEvent — that save you from `as` type assertions every other line.
There are two Stripe SDKs: the server one (stripe from "stripe") and the browser one (@stripe/stripe-js). They have totally different surfaces. The server one talks directly to the Stripe API with your secret key. The browser one is only used for Elements or Embedded Checkout — for the hosted-redirect flow we default to, you don't need it.
Server SDK — src/lib/stripe.ts
import { stripe } from "@/lib/stripe";
// Use in Server Components, Server Actions, API routes
const session = await stripe.checkout.sessions.retrieve(sessionId, {
expand: ["line_items.data.price", "subscription"],
});NEVER import "@/lib/stripe" in a 'use client' component
It will fail at BUILD time. The instance is constructed with env.STRIPE_SECRET_KEY at import — a server-only var. The build error is clear, but it costs you a few minutes every time you reach for it on instinct. If you need Stripe in the browser, use stripe-client.ts.
Browser SDK — src/lib/stripe-client.ts (only if you need it)
"use client";
import { getStripeClient } from "@/lib/stripe-client";
const stripe = await getStripeClient();
const checkout = await stripe.initEmbeddedCheckout({ clientSecret });It's lazy — loadStripe is called at most once per session. If you stay on the hosted-redirect flow (the default), you never need this. We ship it pre-wired so Elements is a 5-minute swap if you ever need it.
Why getCustomerId and getResourceId exist
Stripe object fields like session.customer can be: a string ID, an expanded Customer object, an expanded DeletedCustomer object, or null. Doing session.customer as string is wrong in three of four cases. These helpers narrow safely.
import { getCustomerId, getResourceId } from "@/lib/stripe";
const customerId = getCustomerId(session.customer); // string | null
const subscriptionId = getResourceId(session.subscription); // string | null
const paymentIntentId = getResourceId(session.payment_intent); // string | nullconstructWebhookEvent — signature verification
import { constructWebhookEvent } from "@/lib/stripe";
const event = constructWebhookEvent(rawBody, signatureHeader);
// Throws Stripe.errors.StripeSignatureVerificationError if invalid.getStripeRedirectUrl — absolute URLs for callbacks
import { getStripeRedirectUrl } from "@/lib/stripe";
getStripeRedirectUrl("/checkout/success");
// → "https://yourdomain.com/checkout/success" in prod
// → "http://localhost:3000/checkout/success" in devDon't use `as string` on Stripe fields
The ESLint config bans `as` assertions everywhere except test files and the webhook route's dispatch switch (where the discriminated event type guarantees the shape). Reach for getCustomerId or getResourceId instead. If you need a new helper, add it to src/lib/stripe.ts — there are only two for a reason: those are the patterns we hit over and over.
We pin typescript: true on the SDK
new Stripe(secret, { typescript: true }) enables stricter response typings. It's the only option we set — Stripe's defaults are sensible enough.