Site

Guest Checkout (buying without an account)

Guest checkout lets a visitor pay for a one-time product without signing up. Nothing is stored until Stripe confirms the payment; the buyer then receives a receipt carrying a single-use link that attaches the purchase to a real account.

Disabled by default. Enable it with the server-only GUEST_CHECKOUT_ENABLED environment variable, or answer the prompt in pnpm run init. Turnstile keys are required, because the checkout endpoint is anonymous.

Scope is deliberately narrow

Guests may buy one-time products only — the entries in pricingConfig.products. Subscriptions are refused at the schema boundary: a recurring charge against someone with no login has no self-service cancellation, which is a consumer-law liability rather than a UX gap. Credit packs are excluded too, since purchased credits are unusable until the buyer claims the account.

Where the option appears

The pricing page is unchanged. A visitor picks a product exactly as before, and the selection is parked in the bsk_pending_checkout cookie. The alternative is offered at the moment the product asks them to create an account — on /login, as a “continue without an account” button below the sign-in form.

That button only renders when the feature is on and a one-time product is already parked. A visitor who came for a subscription never sees it, so the affordance can never lead to a refusal. The check runs server-side and ships as static markup, which means the flag itself never reaches the browser and the button cannot drift from what the API accepts.

The flow

/pricing  (unchanged)  →  product parked in the cookie
↓
/login    “Continue without an account”
↓
/checkout/guest        email + terms + withdrawal waiver + Turnstile
↓                POST /api/billing/guest-checkout   ← writes nothing
Stripe payment
↓                checkout.session.completed, paid
guest account + single-use claim token created by the webhook
↓                receipt email carries the claim link
/checkout/claim        → POST /api/billing/guest-claim

Why nothing is written before payment

The checkout endpoint is anonymous. Creating an account row there would let unauthenticated traffic create unbounded records, and every abandoned checkout would leave an orphan behind. Instead the endpoint only talks to Stripe, and the webhook creates the guest account once payment is confirmed. The account has no owner and no membership, so it is unreachable through the data API — only the claim path can adopt it.

Claiming a purchase

The claim link is single-use and expires after 72 hours. Only a digest of the token is stored, so the raw value exists nowhere except the email. Redeeming it requires signing in with the address that paid, then moves the licence, the payment record, and any included credits onto the account the buyer already uses.

The purchase is transferred, not adopted: adopting the guest account would leave the buyer with two personal accounts, and the second one would be invisible to the rest of the product.

Consumer law

The checkout form collects an explicit, separate waiver of the 14-day withdrawal right for immediate digital delivery (Directive 2011/83/EU, Art. 16(m)). It is deliberately not folded into the terms checkbox — a waiver bundled into a general acceptance is not “express”, and without it a refund is owed for 14 days whatever the terms page says. The authenticated one-time purchase path does not yet collect this waiver.

Housekeeping

When guest checkout is enabled, pnpm run init registers two internal jobs automatically. sync-guest-purchases runs every five minutes and continues large verified purchase histories in bounded batches. cleanup-guest-accounts runs daily, deletes guest accounts that hold neither a licence nor a payment, and never deletes an account that still holds a purchase — those rows cascade, so removing the account would destroy a paid entitlement with the money already taken.

The job also reports unclaimed_expired_purchases: buyers who paid and never claimed. A count that keeps growing is the signal that receipt emails are not being delivered.

Data protection

A claimed guest purchase appears in the account owner’s data export and is removed by the account deletion job. A buyer who never claimed is attached to no account, so no self-service path can reach them; handle those requests through support and say so in your privacy notice.