Stripe or Creem
Both rails are wired. What actually differs, and how to switch.
Both providers are already implemented. The webhook routes verify, deduplicate
and translate either payload into one internal PaymentEvent; your features
handle the event and never import a provider client. So this is a choice you
can defer, and change later without stranding anyone: PAYMENT_PROVIDER
decides who takes new checkouts, while an existing subscription keeps
renewing wherever it started, read off the row.
What differs
| Stripe | Creem | |
|---|---|---|
| Role | Payment processor — you are the merchant of record | Merchant of record — they sell, you get paid |
| Sales tax and VAT | Yours to handle, or add Stripe Tax | Collected and remitted for you |
| Plan changes in the app | Upgrade now for the difference, downgrade at period end | Through the provider's own portal |
| A refund of an upgrade | Undoes it — price back, top-up clawed back | — |
| Local webhook test | bun run stripe:listen, through the Stripe CLI | A signed payload posted by the /creem skill |
| Catalog ids | stripePriceId, one per tier and interval | creemProductId, one per tier and interval |
| The switch | PAYMENT_PROVIDER=stripe | PAYMENT_PROVIDER=creem |
The one asymmetry worth planning around is plan changes. In-app upgrades
and downgrades are Stripe-only (src/features/billing/plan-change.ts); a Creem
subscriber who wants a different tier goes through Creem's portal. Everything
else — checkout, renewals, refunds, cancellation, the credit grants that hang
off each event — works identically on both.
Switching
PAYMENT_PROVIDER is read at boot, so set it in .dev.vars (local) and
.prod.vars (production) and restart. If it is unset the template falls back
to whichever key exists, preferring Stripe.
Nothing can be bought until the active provider has its key, and every purchase button reads that rather than showing a checkout that would fail — the product ships before the payment rail does, and a merchant account takes weeks.
Run the /stripe or /creem skill to build the catalog, write the ids into
src/config/plans.ts, and prove the webhook end to end before you trust it.
ShipKit