Repository navigation
Hosted: billing routes, webhook, refunds and disputes - #1406
Merged
Merged
Conversation
GET /api/billing, POST checkout, confirm, and portal on the account Worker, behind the login gate, 4 KiB bodies, and 503 while billing is closed. Checkout takes a plan and a ref only: the ref is one of HOSTED_REFS, "other", or none, sent through pgstencil's checkout ref; the owner is the login, its email only once verified, else null so Stripe Checkout asks. A checkout open for another plan is expired. The webhook verifies the raw body (1 MiB cap) before any write and answers 2xx only after commit. In its transaction, each under its own savepoint, it counts checkout.completed from completedCheckout and subscription.canceled or .refunded from a purchase's end, so a redelivery counts nothing and a failed count rolls back alone, logged. A full refund or a lost dispute ends the subscription its charge paid for, found through the PaymentIntent's invoice payments, while it is the owner's current one; a charge for an earlier subscription or none ends nothing and is logged for the operator. It runs after the commit, retried on every delivery until it has. An open dispute, read from Stripe's current status, suspends through a new dormouse_billing_disputes row the entitlement checks; closing in the account's favor restores it. checkout.started counts best effort. The admin page lists only the counted labels of a long label list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hosted.md gains the billing routes table, the webhook, refund, and dispute rules, the dispute clause of the entitlement, and four metric rows, with the webhook's counts under their own savepoint. pricing.md promotes the refund and dispute rule above the fold and narrows the paid-launch dispute gate to the notice. security-hosted.md adds S283 (route inputs), S284 (webhook), S285 (refunds and disputes) and widens S282 and S110 to the disputes table. Privacy lists the dispute state, the checkout ref, and the new counts. The Hosted audit prompt asks whether billing can grant what Stripe did not sell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying mouseterm with
|
| Latest commit: |
df85b3d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7288a2db.mouseterm.pages.dev |
| Branch Preview URL: | https://hosted-billing-routes.mouseterm.pages.dev |
dormouse-bot
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Billing PR 2 of 4, stacked on #1405 (billing core). Billing stays closed: every billing route answers 503 "Checkout is not open yet." until the
STRIPE_*bindings are set.hosted/server/billing-routes.ts):GET /api/billing, andPOSTcheckout,confirmandportal. The login gate runs first, and bodies are capped at 4 KiB.ref(one ofHOSTED_REFS,other, or none), passed to pgstencil'scheckout(..., { ref }). A Price, customer, owner, quantity or return URL never comes from the request.null, and Checkout collects one.emailis added toAccountLogin.2026-08-26.dahlia) has nocharge.invoice, so the charge is resolved through invoice payments toinvoice.parent.subscription_details.subscription. The cancel happens in Stripe after the commit, retried on each delivery until it's gone. A charge that paid for an earlier subscription, or that can't be resolved, cancels nothing and logs a warning for the operator. A partial refund does nothing.dormouse_billing_disputestable (migration014), which the entitlement checks. Won, closed or prevented deletes the row. Inquiries (warning_*) don't suspend.checkout.started[plan:ref] is best effort.checkout.completed[plan:ref],subscription.canceledandsubscription.refunded[plan] are counted inside the webhook transaction, so a redelivery never counts twice.hosted.mdhas a routes table, the rules and the new metrics rows.pricing.mdmoves the refund and dispute rule above the fold, and the paid-launch dispute gate narrows to the customer notice, which PR 4 builds.security-hosted.mdgets FAIL IFS283(route inputs),S284(webhook, including that a failed count never fails the event) andS285(refunds and disputes), and widensS282andS110.StripeDev wiring in
dev.tscomes with PR 3, which adds the/billingpage that needs it.🤖 Generated with Claude Code