Stripe
Stripe billing for an Askr app: a customer per user, Checkout for subscriptions and one-off payments, the customer portal, and webhooks that are verified, recorded once and applied in one transaction.
Billing is not something every app needs, and Stripe moves on Stripe's schedule rather than Askr's. So it is not in the framework: it is the first plugin, and the one the plugin mechanism was designed against. ## What an app gets - **A customer per user.** The framework does not own your user model, so the Stripe customer is keyed by your user id as text — the id `Login` takes — the way API tokens are. - **Checkout, not a payment form.** Stripe's hosted Checkout takes the card, 3-D Secure and SCA, so none of that is Pascal an app has to trust. Subscriptions and one-off payments both start there. - **The customer portal** for changing a card, switching a plan or cancelling, on Stripe's own page. - **Webhooks that arrive once.** The signature is checked against the raw body with a time window; each event id is recorded, so a retried delivery has no second effect; and the event is applied in one transaction. If anything fails, it all rolls back and the answer is a 500 — and Stripe sends it again, for up to three days. Stripe's retries are the queue, so a new app needs none of its own. - **Order that does not matter.** Stripe does not promise it. An older event is still recorded and still dispatched, but it does not overwrite a newer subscription state. - **Questions an app asks:** is this user subscribed to that price, on a trial, in a grace period after cancelling. ## Money is minor units Stripe counts in øre and cents, as integers. They go into `Currency` by assignment, never by a cast: a cast reinterprets the bits instead of converting the value, and gives a different answer on x86_64 than on arm64. ## What the real account found The first run against a test-mode account found a bug no fake could: the customer was made with an idempotency key built from the user id. Stripe keeps a key for a day across everything that shares the account, so a reset database got a deleted customer back — and two apps on one account, a dev and a staging, would have shared customers. The key is random now, and a race between two requests is decided by the row. ## What it does not do Invoices as PDF, metered billing, Stripe Tax, Connect and card forms in your own pages are not in it. Each is a Stripe product with its own surface, and none of them is needed to take a subscription. Changing a plan is the portal's, or Stripe's API — the documentation shows both.