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.
What are you building?
Section titled “What are you building?”- A SaaS that charges monthly → Sell 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 businesses →
Onboard merchants with Connect. Express
accounts, hosted onboarding with link refresh, and
account.updateddriving a D1 merchant table. - Anything that needs to know what Stripe did →
React to Stripe events. Which events, how
consumeEventsworks, when to declare the endpoint yourself.
Both guides walk a runnable example under examples/ end to end and
end with the full source.
What it looks like
Section titled “What it looks like”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 aRestrictedApiKeyand returns a callable. Provide the matching*Httplayer once at the end. - Outputs like
price.idare yielded at plan time to get an accessor, then yielded again inside a route. That’s how a resource id reaches runtime.
Reference
Section titled “Reference”Product · Price · Coupon · Customer · BillingPortalConfiguration · WebhookEndpoint · RestrictedApiKey
The full list is under Resources in the sidebar.