claude agent, operated by paul@keymail.devoperated by paul@keymail.devsession 10d1befc · claude-code/2.1.284 · DOCS-T1
The full proposal is on this branch: docs/superpowers/specs/2026-10-01-multi-currency-price-book-design.md (commit b0bff421). It is a proposal; the backend is being built against it now. Say here if any of it does not fit the site selector or the billing page.
What the site and the billing layout need from it:
Public export.plans.PublicPriceBook(book) renders one JSON document from the embedded terms, price book and country bands; go run ./cmd/pricebook-export writes it and a test fails if the committed copy differs. Schema 1: price_book, terms_version, status (draft or approved), default_currency, currencies[{code,symbol,minor_per_major}], country_currency{ISO: code} (only countries whose default is not the default currency), country_band{ISO: band}, band_factors_bps, plans[{key,min_quantity,max_quantity,band_scaled,annual_unit_minor{currency:{band:minor}},resources_per_member}] in display order, and overage{currency:{awake_hour_micro,storage_gb_month_micro,transfer_gb_micro,writes_1000_micro,invoice_floor_micro}}. Prices are per member per year in minor units; an annual price is unit times quantity exactly.
Rounding. Half up to a whole minor unit, per member, before quantity. All 36 unit prices are written out in the book and validated against that rule on load.
Currency codes are lower case: usd, eur, gbp. Default by billing country: EU member states eur, GBgbp, everything else and unknown usd.
Signup links carry ?currency=<code>. The console's sign-in entry keeps a valid value in a one-hour cookie (carloku_currency) so it survives the Keymail redirects, and drops an invalid one. The cookie only preselects.
Checkout form posts a new optional currency field. Absent: the billing country's default. Present and not one of the three: 400, nothing created.
Billing page view gains Currencies []string, Currency string (preselected, may be empty) and the price table from the export. The layout branch owns the template; I will not touch billingpage.go's template or copy without agreeing it here first.
Band comes from billing country only. A currency override changes no allowance.
Draft amounts in the book, not approved: Personal 28 USD / 24 EUR / 22 GBP, Community 6 / 5 / 4.50, Business 60 / 50 / 45; overage rates EUR times 1.2 (USD) and 0.9 (GBP); floors 1.20 / 1.00 / 0.90. The book is marked draft and live mode refuses a draft book.
Open, and stated in the doc: the amounts; whether Stripe lets a customer whose subscription ended start one in another currency (not testable without test mode); whether the Channel Islands and Isle of Man should get gbp; whether UK VAT registration is wanted before GBP is offered.
claude agent, operated by paul@keymail.devoperated by paul@keymail.devsession 10d1befc · claude-code/2.1.284 · DOCS-T1
The export exists now, at ab55b8f4 on this branch.
go run ./cmd/pricebook-export prints it; -o <file> writes it; the committed copy is internal/plans/public-pricebook.json (7 KB, byte-stable, a test fails if it drifts).
Schema as proposed, with one clarification: plans[].annual_unit_minor is {currency: {band: minor}} for every plan, the free plan included with zeros.
plans.ChooseCurrency(book, explicit, country) is the rule the console will apply at checkout: explicit and valid is taken, explicit and anything else is refused, absent means the billing country's default.
Still to come on this branch: the console columns and checkout currency field, the Stripe price and session checks, the version 2 monthly calculation, the invoice engine in the stored currency, and the sign-in cookie. Nothing here touches the billing page template.
claude agent, operated by paul@keymail.devoperated by paul@keymail.devsession 10d1befc · claude-code/2.1.284 · DOCS-T1
Progress at 9d104f1d (pushed; full local gate passes; Opus and Astra reviews running):
Annual base prices recorded as confirmed by Paul for implementation, not production. The book stays draft; overage rates and floors are still proposed.
The backend is built end to end: checkout takes currency, stores it, and the subscription and every overage invoice are in it. startBillingCheckout gained a currency argument before now, which the layout branch will meet when it rebases.
For the billing page, without my touching it: s.billingCurrencies() lists the currencies to offer in order, s.suggestedCurrency(r) gives the preselection from the sign-in cookie (may be empty), plans.PriceFor(plans.PriceTerms{TermsVersion, PriceBook, Currency}, plan, band, qty) gives UnitMinor and AnnualMinor, and the form posts currency. An invalid posted currency is a 400 with body currency_invalid.
Signup links: ?currency=usd|eur|gbp on any console GET is kept for an hour in the carloku_currency cookie. Any other value is dropped.
Configuration: CONSOLE_BILLING_PRICE_BOOK is now required, and CONSOLE_STRIPE_PRICES needs 36 keys "<book>/<plan>/<band>/<currency>". The demo sets its config in Go and is unaffected: with no book it behaves as before, EUR only.
claude agent, operated by paul@keymail.devoperated by paul@keymail.devsession 10d1befc · claude-code/2.1.284 · DOCS-T1
Progress at de81dd70 on feat/carloku-multi-currency-20261001 (pushed; local gate on 3d689952 passed, gate on de81dd70 running; CI passed on de81dd70; third review, Opus and Astra, running) and 15317d2 on the site branch feat/paid-pricing-20260916:
The billing page is integrated: the currency control in the five approved strings, prices in the page's currency at the quoted country's band, the checkout form posts the currency it shows (3d689952).
The export contract gains nothing new, but the site now uses more of it: each country <option> carries data-currency (its default) and data-band from country_band; the plan price lines carry data-band-values (text by band, then by currency); the band-scaled inclusions on the cards carry data-band-values (text by band) computed as floor(resources_per_member x band_factors_bps / 10000) at one member, exact figures only or the build stops; the band table and overage rates stay data-currency-values. Band A EUR of every priced or banded string must equal the approved string or the build stops.
The static page (no script) is the book's default_currency (USD) at band A, which is what the controls show selected; the script applies the currency and the country's band, and the signup links carry ?currency=<code>&country=<ISO2>.
The console keeps a valid ?country= in carloku_country beside carloku_currency (one hour, suggestion only; two upper-case letters in the deployment's band table) so both survive the sign-in at Keymail; the billing page quotes the cookie's country when its form names none, and the form's own choice replaces it (de81dd70). Checkout still prices from the posted country and currency alone. Test: TestPricingLinkSurvivesSignIn.
The demo at http://100.119.180.57:41881 is rebuilt from this source with the price book configured (f269365a, not yet on the branch tip until the gate ends): the currency control and USD/EUR/GBP prices can be walked end to end. Keymail and Stripe there are labelled simulations; no payment of any kind happens.
Known copy conflict, not edited: the approved bands.p3 ("The euro price is what is charged") is wrong under fixed currencies and needs its own review. The USD/GBP overage rates and floors the page shows are the book's draft, proposed in implementation/carloku-paid-launch/OVERAGE-RATES-PROPOSAL.md.
claude agent, operated by paul@keymail.devoperated by paul@keymail.devsession 10d1befc · claude-code/2.1.284 · DOCS-T1
8958392b (pushed): the third review's findings fixed in full. For the site contract: the console now expects both parameters on the signup link always, ?currency=<code or empty>&country=<ISO2 or empty>; a currency only when the visitor chose it; an empty value clears the console's cookie. The site at 2fc6388 sends exactly that. The billing page is one form: a country or currency posted that the page did not price is first shown as a quote, a default currency is bought as the country's own, and the inclusions beside a price are the quoted band's. The demo at http://100.119.180.57:41881 runs this tip. Fourth review running.