Plans come from the API and the page that shows them carries a little logic of its own, while subscribing runs on a Stripe Checkout Session the App renders itself. How the API stores plans and drives Stripe is covered in Plans & Subscriptions.
The plans page is a monthly and yearly toggle over a card per plan, rendered straight from what the API returns. Prices are the ones the API keeps in sync with Stripe, and each card lists the plan's own feature limits with null shown as unlimited. A new plan, or a new limit on one, needs nothing here beyond a translation key for the label.
The button on each card comes from one function, planAction(), which compares where the user stands against the plan on the card rather than leaving each card to decide for itself. It returns subscribe, upgrade, downgrade, switch interval, cancel, resume or current. A move down to the free tier reads as cancel, since that is what it is, and a cancelled subscription offers resume on its own plan and nothing on the rest until it is resumed.
When trials are on, the trial plan's card carries the trial wording, and a subscribed user partway through a trial sees where it stands in place of the cards. That is covered in Trials.
The subscription_mode setting comes from the API and decides whether a user starts on the free plan or with no plan at all. planAction() handles both, so the page needs no separate case for either.
The subscribe page runs on a Stripe Checkout Session in elements mode, which is neither Stripe's hosted page nor its embedded one. The App mounts the Billing Address Element and the Payment Element itself, styled with the App's own appearance object, and the session behind them carries the address, the card and the totals. It costs more code than hosted or embedded would, but it is what lets a customer with a card on file skip the payment form entirely, which the other two cannot do.
Opening the page creates a Checkout Session and nothing else. No subscription, no intent, no address written to the customer. A customer who walks away leaves a session that ages out, so there is nothing to clean up. The API expires any other open session the customer has whenever it creates one, which is what stops a stale tab on another device from subscribing them a second time.
The total and the line items are read off the session on every step. Nothing is priced in the App, which is why tax and discounts can be layered on without the page learning any arithmetic.
When the session carries a saved payment method there is nothing to collect. The page opens on the confirm step with a line of text showing the brand and last four, no elements are created, and the confirm runs against the saved card. If that card is refused, the page falls back to the address and card steps on the same session rather than opening a new one.
A challenge can take the customer off the page and back to a fresh load. The session secret is held in session storage for the length of the confirm, scoped to the user, so the return reopens the session that was being confirmed instead of creating a replacement. A replacement is what would subscribe them twice if the first had already completed while they were away.
The confirm creates the subscription and settles the first invoice at Stripe in one call. A sync request then writes the local rows from the session and refetches the user, and the webhook makes the same writes idempotently. A failed sync leaves a correct subscription behind, so the customer can submit again and the retry is safe.
Cancel, resume and plan changes act on the existing subscription and send no body, since the API resolves it from the user. The user is refetched after each, because subscription state is read off it in a lot of places.
Automatic tax, promotion codes and trials are layered onto this rather than being part of it. Each sits behind a flag, so with it off the page behaves exactly as described here and there is nothing to strip back out. See Automatic Tax, Promotion Codes and Trials.