stripe
Installation
SKILL.md
Stripe
Use Stripe Checkout for new purchases and the Stripe Billing Portal for subscription management (upgrade, downgrade, cancel, switch monthly↔yearly). Do not build a custom billing UI.
Core rules, in priority order:
- One Stripe customer per
Org. Store the customer id inOrg.stripeCustomerIdand reuse it on every checkout/portal call. - Prefer
lookup_keyover hardcodedprice_xxxids. Fetch prices at runtime when possible. - Every Price uses
currency_optionsfor EUR on top of a USD base. Same integer value for both — see Multi-currency. - One active Subscription row per
Org. Before creating a checkout session, check the DB and redirect existing subscribers to the portal instead. - All Stripe-facing HTTP code lives inside spiceflow sub-apps (
website/src/lib/spiceflow-*.tsx). Not react-router actions. The webhook route is also a spiceflow route — spiceflow handlers receive a standardRequestobject, soawait request.text()gives the raw body needed for Stripe's signature verification. - Return errors as values, never throw. All Stripe/Drizzle calls are wrapped with
.catch()into tagged errore errors (StripeApiError,DbError,PriceNotFoundError, etc.).constructEventand other sync-throwing APIs go througherrore.try. Handlers checkinstanceof Error, early-return, and map errors to HTTP responses viaerrore.matchErrorat the HTTP boundary only. Always read the errore skill before writing or modifying error handling code in Stripe routes — it covers tagged errors,.catch()boundary rules, flat control flow, cause chains, and thematchErrorexhaustive handler.
CLI auth and multiple accounts
The Stripe CLI stores credentials in ~/.config/stripe/config.toml. By default, stripe login writes to a [default] section and all commands use it.
For multiple Stripe accounts (e.g. separate stores, test vs prod, client projects), use --project-name to namespace each one: