Checkout, a local copy of subscriptions in PostgreSQL, webhooks that survive duplicates and reordering, and the customer portal. This is the setup I use in production, with the parts that cost me time spelled out.
The shape of it
Stripe is the source of truth for who pays for what. The app keeps a local copy of the parts it needs (customers, subscriptions, handled events) so that a page can check a user's plan without an API call. Three things write to that copy:
Starting Checkout creates the Stripe customer and remembers it.
The success page , where Stripe sends the user after paying, syncs the new subscription at once so the user sees their plan immediately.
Webhooks keep everything in sync afterwards: renewals, failed payments, cancellations, plan changes made in the customer portal.
The stack: FastAPI, SQLAlchemy 2.0 in async mode with asyncpg, PostgreSQL and the official stripe package.β¦