Skip to content

get.traylinx.com: render pricing from the live API, not literals - #37

Merged
rschumann merged 3 commits into
mainfrom
feature/landing-live-pricing
Jul 26, 2026
Merged

get.traylinx.com: render pricing from the live API, not literals#37
rschumann merged 3 commits into
mainfrom
feature/landing-live-pricing

Conversation

@rschumann

Copy link
Copy Markdown
Contributor

The problem

get.traylinx.com was still selling a product we retired.

on the page reality
Explorer $39 / Creator $79 / Operator $149 those plans no longer exist
"Save 2 months" the retired USD ×10 badge
"7-day money-back guarantee" ×2 the terms of sale say annual is non-refundable
"All prices in USD" the ladder is EUR-only

Nothing had touched it since the storefront moved to Starter / Starter·Managed / Builder / Power, and nothing was watching, so it drifted silently.

The fix

The pricing section now renders from https://tytus.traylinx.com/public/pricing — the same source of truth the main storefront and the OpenClaw landing already use. A price change is now Stripe + catalog only, with zero edits to this file.

  • Monthly/Yearly toggle passes interval, so the annual ladder comes from the API too
  • Annual terms render only on the annual view and only when the API says annual is sellable, using the API's own consent_text — the same words Stripe Checkout shows above the pay button, so the promise can't drift between page and payment
  • Toggle badge names no number: the saving runs 0.79→1.91 months across the ladder, and a badge above all cards may only claim what the weakest card delivers
  • Free tier isn't sold through the API, so it stays static

Degradation

Unreachable / non-200 / malformed API → bundled snapshot of the current live values. The page never blanks and never renders a partial payload. API text is escaped.

Verified

web/pricing.test.mjs — 14 assertions, network-free (stubs fetch), wired into pr.yml as "Landing pricing contract". A red run means the page changed, never that production wobbled.

Mutation-checked, so the assertions aren't vacuous:

  • reintroduce "7-day money-back guarantee" → FAIL no refund promise anywhere
  • put "Save 2 months" back on the toggle → FAIL the annual toggle badge names no number of months

Rendered against the live API before shipping:

month:  Starter €24 · Starter·Managed €49 · Builder €89 · Power €179 · Droplet from €24 · Tytus AI from €25
year:   Starter €269/yr · Starter·Managed €538/yr · Builder €968/yr · Power €1968/yr · Droplet from €269/yr · Tytus AI from €269/yr

🤖 Generated with Claude Code

The landing page hardcoded a price ladder that had been retired: Explorer
$39, Creator $79, Operator $149 -- three plans that no longer exist -- plus
"Save 2 months", "All prices in USD", and a "7-day money-back guarantee"
that the terms of sale directly contradict (annual is explicitly
non-refundable). None of it had been touched since the storefront moved to
Starter / Starter-Managed / Builder / Power, so the page had been selling a
product we do not offer at prices we do not charge.

The pricing section now renders from
https://tytus.traylinx.com/public/pricing -- the same source of truth the
main storefront and the OpenClaw landing already use. A price change is now
Stripe + catalog only, with zero edits to this file. The Monthly/Yearly
toggle passes `interval`, so the annual ladder arrives from the API too.

Annual terms are shown ONLY on the annual view and ONLY when the API says
annual is sellable, using the API's own consent text -- the same words
Stripe Checkout puts above the pay button, so the promise cannot drift
between the page and the payment.

The toggle badge names no number. The saving is not uniform (0.79 to 1.91
months across the ladder), and a badge above ALL cards may only claim what
the WEAKEST card actually delivers.

The free tier is not sold through the pricing API, so it stays static.

Degradation: an unreachable, non-200 or malformed API falls back to a
bundled snapshot of the current live values rather than blanking the page
or rendering a partial payload. API-supplied text is escaped.

Adds web/pricing.test.mjs (14 assertions, network-free -- it stubs fetch, so
CI never depends on the pricing API being up) and a "Landing pricing
contract" job in pr.yml. Mutation-checked: reintroducing the money-back
promise or a numbered toggle badge each turn the suite red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 26, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
tytus-cli 869d4ed Jul 26 2026, 08:21 AM

rschumann and others added 2 commits July 26, 2026 10:13
)

Three real defects from the codex review, all the same shape: the page
could display a price we do not charge.

1. STALE RESPONSE. loadPricing() read the global billingMode again AFTER
   the await, so toggling month -> year while the month request was in
   flight let the late MONTHLY response paint itself under an active
   Yearly toggle. Requests now carry a monotonic id and only the newest
   one may render.

2. UNVALIDATED TERM. isValidPricing() checked the payload's shape but not
   its interval. A proxy that drops `interval` returns a structurally
   perfect MONTHLY payload for a YEARLY request -- exactly the bug that
   shipped in the pricing proxy earlier the same day, where nothing failed
   loudly because the payload was valid and only the term was wrong. The
   requested interval is now demanded back.

3. UNCONFIRMED ANNUAL LADDER. A failed annual load rendered the bundled
   annual snapshot, which cannot know whether annual is still sellable --
   so an outage would advertise a term we may have switched off. A failed
   annual load now drops to the SHORTER commitment and moves the toggle
   with it, so the label always matches the amounts. Monthly is always
   honourable; that asymmetry mirrors the pricing services, where display
   leniency falls toward the shorter term and never toward the longer one.

Three tests added for exactly these (codex point 5): a wrong-interval
payload, a superseded in-flight request landing late, and a failed annual
load moving the toggle. 17 assertions, still network-free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
web/ IS the website. Cloudflare Workers serves ./web directly
(wrangler.jsonc "assets.directory") and the Pages job does
`cp -rL web/. _site/`, so web/pricing.test.mjs would have been downloadable
at https://get.traylinx.com/pricing.test.mjs on both deploy paths -- and it
carries internal notes about retired prices and the money-back incident.
It also turned the Workers build red, which is how it surfaced: that check
is green on main and went red only on this branch.

Moved to tests/landing-pricing.test.mjs, outside the asset root, and the
pr.yml job follows it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rschumann
rschumann merged commit 6692243 into main Jul 26, 2026
10 of 13 checks passed
@rschumann
rschumann deleted the feature/landing-live-pricing branch July 26, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant