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:

  1. One Stripe customer per Org. Store the customer id in Org.stripeCustomerId and reuse it on every checkout/portal call.
  2. Prefer lookup_key over hardcoded price_xxx ids. Fetch prices at runtime when possible.
  3. Every Price uses currency_options for EUR on top of a USD base. Same integer value for both — see Multi-currency.
  4. One active Subscription row per Org. Before creating a checkout session, check the DB and redirect existing subscribers to the portal instead.
  5. 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 standard Request object, so await request.text() gives the raw body needed for Stripe's signature verification.
  6. Return errors as values, never throw. All Stripe/Drizzle calls are wrapped with .catch() into tagged errore errors (StripeApiError, DbError, PriceNotFoundError, etc.). constructEvent and other sync-throwing APIs go through errore.try. Handlers check instanceof Error, early-return, and map errors to HTTP responses via errore.matchError at 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 the matchError exhaustive 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:

Installs
1
GitHub Stars
42
First Seen
Jun 21, 2026
stripe — remorses/opencode-config