Skip to content

Stripe

Stripe with alchemy means your billing lives in the same TypeScript program as the app that does the billing. The Product and Price are declared on the Worker that sells them. The Worker calls Stripe through typed bindings that carry their own permissions. Webhooks are one function call that provisions the endpoint and hands your handler a typed event. bun alchemy deploy ships all of it; alchemy destroy takes it down without leaving orphans in the Dashboard.

New here? Set up credentials, then pick the guide that matches what you’re building.

  • A SaaS that charges monthlySell a subscription. Hosted Checkout, the Billing Portal, and an entitlement record kept current by webhooks. One Worker, one KV namespace.
  • A platform that pays other businessesOnboard merchants with Connect. Express accounts, hosted onboarding with link refresh, and account.updated driving a D1 merchant table.
  • Anything that needs to know what Stripe didReact to Stripe events. Which events, how consumeEvents works, when to declare the endpoint yourself.

Both guides walk a runnable example under examples/ end to end and end with the full source.

src/Api.ts
export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
// Catalog — deployed with the Worker.
const product = yield* Stripe.Product("Pro", { name: "Pro" });
const price = yield* Stripe.Price("ProMonthly", {
product,
currency: "usd",
unitAmount: 2000,
recurring: { interval: "month" },
});
const priceId = yield* price.id;
// Runtime calls — each adds its permission to the Worker's token.
const createCustomer = yield* Stripe.CreateCustomer();
const createCheckout = yield* Stripe.CreateCheckoutSession();
// Stripe → app.
yield* Stripe.consumeEvents(
"Events",
{ events: [Stripe.CustomerSubscriptionCreated] },
Effect.fn(function* (event) {
yield* Effect.log(`${event.object.customer} subscribed`);
}),
);
return {
fetch: Effect.gen(function* () {
const customer = yield* createCustomer({ email: "a@example.com" });
const session = yield* createCheckout({
mode: "subscription",
customer: customer.id,
line_items: [{ price: yield* priceId, quantity: 1 }],
success_url: "https://example.com/welcome",
cancel_url: "https://example.com/pricing",
});
return HttpServerResponse.redirect(session.url!);
}).pipe(Effect.orDie),
};
}).pipe(
Effect.provide([
Stripe.CreateCustomerHttp,
Stripe.CreateCheckoutSessionHttp,
Stripe.ConsumeEventsLive,
]),
),
) {}

Three ideas carry the whole integration:

  • Resources (Product, Price, Coupon, WebhookEndpoint, BillingPortalConfiguration, …) are Stack-managed. Alchemy creates, updates, and deletes them. Objects Stripe won’t hard-delete are deactivated instead.
  • Bindings (CreateCustomer, CreateCheckoutSession, RetrieveProduct, …) are what a Worker calls at runtime. Yielding one registers the permission it needs on a RestrictedApiKey and returns a callable. Provide the matching *Http layer once at the end.
  • Outputs like price.id are yielded at plan time to get an accessor, then yielded again inside a route. That’s how a resource id reaches runtime.

Product · Price · Coupon · Customer · BillingPortalConfiguration · WebhookEndpoint · RestrictedApiKey

The full list is under Resources in the sidebar.