VALIDATED CHECKOUT — IMPLEMENTATION AND ROLLOUT
2 October 2026

The /checkout flow validates name, email, date, known local time and an exact
private GeoNames place before creating a payment. Ambiguous places, DST folds
or gaps, unsupported latitude and unavailable data stop before payment.
The accepted input, place, report period, product version and price are frozen
in private checkout_requests. Browser retries reuse a random purchase key.

A signed completed/async_payment_succeeded webhook retrieves the Stripe
session, verifies the configured account, mode, bound order and paid amount,
checks refunds, and atomically confirms payment and creates the six report
jobs. The customer need not return to the site. Duplicate events cannot create
another version or another set of jobs. The existing scheduler/release/delivery
gates remain authoritative. TEST reports never send mail automatically.

An interrupted payment resumes the same session. A new unpaid purchase is
allowed only after Stripe proves the bound payment expired unpaid, followed by
a locked database recheck. Uncertain creation/settlement is preserved. A paid
customer may explicitly start another purchase without deleting the first.

DEPLOYMENT
Apply drizzle/migrations/0036_validated_checkout.sql after 0035. The new table
and five RPCs are service-role only. Deploy the matching source. This migration
does not activate sales or processing and does not modify old test orders.

Dedicated server settings (never browser VITE_* variables):
  CHECKOUT_MODE=test
  CHECKOUT_SANDBOX_ENABLED=true
  STRIPE_CHECKOUT_SECRET_KEY=<existing intended sandbox key, sk_test/rk_test>
  STRIPE_CHECKOUT_ACCOUNT_ID=<exact intended Stripe account id>
  STRIPE_CHECKOUT_WEBHOOK_SECRET=<secret for the endpoint below>
  CHECKOUT_APP_ORIGIN=<stable HTTPS application origin, no path/query>
Configure Stripe's signed webhook endpoint:
  /api/public/webhooks/checkout
Events: checkout.session.completed, checkout.session.async_payment_succeeded,
charge.refunded, refund.created, refund.updated, refund.failed,
charge.dispute.created, charge.dispute.updated, charge.dispute.closed,
charge.dispute.funds_withdrawn, charge.dispute.funds_reinstated.
Apply migrations 0037 (payment holds) and 0038 (bounded calculation queue wait).
Verified charge events hold the bound validated order, cancel future work,
stop delivery retries and revoke download links atomically. Partial refunds
require review. Delayed payment events cannot reopen a held order. Resolved
disputes do not automatically restart it. A send already in flight cannot be
recalled; its download link is revoked. No refund/dispute is initiated here.
New checkout uses Stripe API 2026-08-26.dahlia. The legacy /test/checkout client
and its separate webhook remain TEST-only, on their existing pinned version.

Missing/inconsistent settings close checkout. Never copy a key from another
Stripe account just to pass setup. New live sales additionally require ALL:
  CHECKOUT_MODE=live and matching dedicated live credentials/webhook
  CHECKOUT_LIVE_APPROVED=true
  CALCULATION_LIVE_APPROVED=true after production calculation is resolved
  SALES_ENABLED=true in product config
  automation_flags.live_processing=true AND scheduled_delivery=true
Closing new-sales flags must leave credentials/webhook processing intact for
already accepted payments. Configuration checks do not prove quota, balance,
webhook reachability or provider reliability.

VERIFICATION AND REMAINING ACCEPTANCE
Offline tests run the real server handlers and PostgreSQL migration/RPCs with
synthetic Stripe and GeoNames responses and outbound fetch blocked by default.
They cover payment without browser return, repeated/delayed events, signature,
mode, account/price/binding errors, atomic rollback/retry, input failures,
ownership, interrupted purchases and launch gates. Type-check and production
build also run. These are not real Stripe, report or capacity measurements.

Before enabling live sales: prove sandbox payment/webhook on the deployed
stable address; run the bounded AI/report pilot; prove automatic release and
delivery once under the approved QA scope; validate the commercial calculation
route, email quota and measured throughput/cost. Automatic refund/dispute handling is implemented and tested offline; include
these events in the deployed Stripe sandbox proof. Multiple product types
require their own approved content specification and quality pilot.

CALCULATION DECISION
Current repository policy allows astro.com swetest for synthetic/paid TEST
orders only. The public page calls it a test/debugging interface; no documented
production quota/SLA for 200 daily reports was found. Do not infer that buying
a software licence grants production use of that hosted test interface.
The existing services/astro engine remains a possible self-hosted route, not a
deployed service. Durable job handling, container configuration and the application selector are
implemented. It still needs a licence decision, hosting/image-build proof and
host load measurement before production (services/astro/OPERATIONS.txt). Its measured local run is
not a capacity guarantee. The official Swiss Ephemeris price page listed an
unlimited professional licence at CHF 700 on 2 October 2026; hosting is extra,
and no licence/hosting purchase has been made. AGPL applicability must be
resolved before choosing that alternative for this private commercial app.
Primary sources checked:
https://www.astro.com/swisseph/swetest.htm
https://www.astro.com/swisseph/swephprice_e.htm
https://www.astro.com/swisseph/sweph_g.htm
