Sign in, pay, brief: the server is the only source of truth
A paid client portal on a static site: identity-keyed checkout, entitlements checked on the server, and briefs that are held, never refused, when something is missing.
The problem
Buyers sign in with one email and pay with another, and hosted payment links carry no customer. Paying clients also need to brief work without a product page, on a site that is entirely static.
The system
- Checkout links are minted server-side only after Google verifies the Firebase ID token; the user id, plan and product line travel with the payment.
- The payment webhook is signature-verified, idempotent under retries, and extends the paid period instead of overwriting it.
- Every brief submission re-reads the plan from Postgres. A user can edit their own profile document, so display may trust the browser but submission never does.
- An incomplete brief is held with the missing information named, and its due date starts when it becomes complete. Turnaround comes from one business-day module shared by Python and JavaScript.
Key decisions
One pricing path for single requests and mixes
WhyPriced separately, a mix would have been cheaper than its parts, and every client would learn to use it.
Trade-offA more complex pricing function with its own parity tests.
Hold, never refuse, an incomplete brief
WhyRequiring a product link blocked clients who had no product page at all.
Trade-offHeld briefs need a nudge and a place in the ops digest.
Refuse credentials in any free-text field
WhyPeople paste API keys into forms. Key-shaped strings are rejected in the browser and again on the server.
Trade-offOccasional false positives on long random strings.
Results
- 100+ API contract checks
- Python ↔ JavaScript parity tests for billing and business-day maths
- Access rules proven by probing the database from outside with the public key
What I’d do next
File uploads to object storage with signed URLs, and a nudge for briefs left incomplete.