| 1 | # π€ Pricing bands β global pricing that subsidises, never interrogates |
| 2 | |
| 3 | Design doc, 2026-07-14. Status: **designed, not built.** Companion docs: |
| 4 | `docs/free-pool.md` (what the Supporter band funds), `docs/demo-spaces.md`, |
| 5 | `docs/tier-economics.md` (the cost-anchored base prices this doc multiplies). |
| 6 | Decisions in this doc were made by Paul 2026-07-14; legal findings are |
| 7 | research summaries, not legal advice β a human legal review gates Phase 1. |
| 8 | |
| 9 | ## The design in one sentence |
| 10 | |
| 11 | > **Location suggests, never decides.** Every buyer sees the same four |
| 12 | > prices; the only thing geography ever does is choose which one is |
| 13 | > *pre-selected* β and stepping to any other band is one tap, no questions |
| 14 | > asked, in both directions, with a sentence explaining what the step means. |
| 15 | |
| 16 | This single choice is simultaneously the honor mechanism ("click a link, get |
| 17 | the lower price, instantly"), what makes VPN games pointless without any |
| 18 | policing, and the legal defence (Β§ Law). It is what separates this from |
| 19 | personalised/surveillance pricing: the countryβband table is public, the |
| 20 | suggestion can be computed client-side, and no IP is ever stored. |
| 21 | |
| 22 | ## The bands |
| 23 | |
| 24 | Multipliers on the existing EUR base prices, charm-rounded: |
| 25 | |
| 26 | | Tier | Supporter Γ1.4 | Base Γ1.0 | Reduced Γ0.5 | Solidarity Γ0.25 | |
| 27 | |---|---|---|---|---| |
| 28 | | Family (annual) | β¬7.70/y | β¬5.50/y | β¬2.75/y | β¬1.40/y | |
| 29 | | Community (annual, per member) | β¬0.15/member/y | β¬0.11/member/y | β¬0.055/member/y | β¬0.0275/member/y | |
| 30 | | Business (annual, per seat) | β¬15/person/y | β¬11/person/y | β¬5.50/person/y | β | |
| 31 | |
| 32 | Community's β¬11 minimum needs no row (and no Stripe Price) of its own: the |
| 33 | billed quantity floors at 100 members, so each band's minimum is just 100 Γ |
| 34 | its member rate β supporter β¬15, base β¬11, reduced β¬5.50, solidarity β¬2.75. |
| 35 | |
| 36 | - **Family and C&F prices are VAT-inclusive; Business prices are |
| 37 | VAT-exclusive** (decided 2026-07-14) β the standard B2C/B2B split: a |
| 38 | consumer's β¬5 means β¬5 with VAT inside; Business is quoted ex-VAT and tax |
| 39 | is added at checkout (or reverse-charged) β Β§ VAT below. |
| 40 | - **Solidarity is annual-only**: Stripe's fixed ~β¬0.25/charge makes β¬1/m |
| 41 | fee-hostile (~26% of gross), and annual matches how those buyers think |
| 42 | about β¬10. |
| 43 | - **Business went per-seat 2026-07-22** (β¬11/person/year, annual only β |
| 44 | `tier-economics.md Β§ Per-seat`): the band multipliers apply per seat, and |
| 45 | Solidarity is dropped for Business β a solidarity price aimed at |
| 46 | hardship-priced *businesses* never quite parsed, and Reduced at |
| 47 | β¬5.50/person covers nonprofits/education cleanly. |
| 48 | - **C&F went per-member 2026-07-27** (β¬0.11/member/year, β¬11 minimum with the |
| 49 | first 100 members included β the elevens ladder): monthly billing is gone |
| 50 | ladder-wide (the fixed fee would eat it), and the per-member bands ride |
| 51 | Stripe's `unit_amount_decimal` β fractional cents are exactly what a |
| 52 | β¬0.055/member Reduced rate needs. |
| 53 | - **All bands are always visible and any is selectable** β adjacent-only |
| 54 | stepping was considered and rejected; friction is the enemy of the story. |
| 55 | - No band is structurally loss-making while hibernation holds (even |
| 56 | Solidarity Family nets ~β¬1 against a mostly-asleep space's ~β¬0.2/y |
| 57 | storage+noise); the real exposure remains media hoarding, already bounded |
| 58 | by tier storage caps. |
| 59 | - Validation against revealed practice: Switzerland at 1.4Γ the euro base is |
| 60 | *conservative* (Netflix/Spotify charge CH β1.5β1.65Γ their German price); |
| 61 | Solidarity Γ0.25 matches Steam's post-2022 India/CIS recommendations and |
| 62 | goes a step past the parity movement's 50β60%-off norm β because the free |
| 63 | pool, not the price, is the real floor here. |
| 64 | |
| 65 | ## The index and the countryβband table |
| 66 | |
| 67 | **Primary index: the World Bank price level ratio** (`PA.NUS.PPPC.RF` β PPP |
| 68 | conversion factor Γ· market exchange rate; ~200 economies, annual, free bulk |
| 69 | data). Not an income index: the number needed is *"what multiplier makes β¬5 |
| 70 | feel like β¬5 there"*, which is literally what a price index measures. The two |
| 71 | diverge where it matters β the Gulf states and Singapore are rich by income |
| 72 | but mid-priced by price level; a price index honestly puts them at Base. |
| 73 | |
| 74 | - Published vs US = 1.00; **rebased to a GDP-weighted euro-area anchor |
| 75 | (~0.73)** so everything reads as a multiplier on the EUR base. |
| 76 | - Band edges: β₯1.15 β Supporter Β· 0.75β1.15 β Base Β· 0.45β0.75 β Reduced Β· |
| 77 | <0.45 β Solidarity. |
| 78 | - **Fallback** for countries with no price-level datum (Taiwan, Cuba; stale |
| 79 | series): World Bank income classification β high β Base, upper-middle β |
| 80 | Reduced, low/lower-middle β Solidarity; neither datum β Base. The honor |
| 81 | picker bounds the harm of any wrong default. |
| 82 | - **Decided placements (2026-07-14):** **United States β Supporter Γ1.4** |
| 83 | (the index says 1.37; the one-tap step down is the escape hatch β this is |
| 84 | the boldest call in the scheme and the band-mix telemetry below is how we |
| 85 | check it). Band-edge UK (~1.16) and Canada (~1.15) β **Base**; revisit at |
| 86 | the annual table regeneration. EU/EEA β Base *by law*, Switzerland β |
| 87 | suggestion-only Supporter (Β§ Law). |
| 88 | |
| 89 | **The index never touches runtime code.** A small committed tool |
| 90 | (`tools/pricing-bands.mjs`, human-legible) reads a vendored CSV of the index |
| 91 | and emits a committed, reviewable countryβband JSON table with the index name |
| 92 | and vintage in a header (`"index": "wb-pli-2024"`). Swapping the index = |
| 93 | regenerate from a different CSV, read the diff, commit. Regenerate annually; |
| 94 | band *reassignments* reach existing subscribers only through the annual |
| 95 | review (Β§ The 15% cap), which >15% jumps turn into hold+invite by |
| 96 | construction. The table is public information β serve it on the pricing page; |
| 97 | publishing it *is* the transparency story. |
| 98 | |
| 99 | ## Law (the part that shaped the design) |
| 100 | |
| 101 | - **EU Geo-blocking Regulation 2018/302** (electronically supplied services β |
| 102 | hosting is squarely in scope): a trader may not *apply* different prices by |
| 103 | reason of the customer's residence, but is free to *offer* different |
| 104 | country price lists that anyone can access. Therefore: **one uniform |
| 105 | EU/EEA default (Base)** β Denmark/Luxembourg/Norway/Iceland forgo their |
| 106 | index-justified premium; the voluntary step-up remains available to them. |
| 107 | - **Switzerland** has its own geo-price-discrimination ban (UCA Art. 3a, in |
| 108 | force 2022, from the Fair Price Initiative; civil enforcement only). A |
| 109 | *hard* Swiss surcharge would sit inside its text; a pre-selected suggestion |
| 110 | any Swiss customer can decline in one tap for the same product arguably |
| 111 | discriminates against no one. The Swiss premium ships **suggestion-only** |
| 112 | (as everywhere). |
| 113 | - The open-band design is the belt *and* braces: inside the EU/EEA the |
| 114 | question never arises (uniform default); everywhere else, every price being |
| 115 | openly offered to every customer with no eligibility test is the defence. |
| 116 | - Consumer transparency: EU rules require the total, VAT-inclusive price |
| 117 | before purchase β Β§ VAT below β and the one-line band explanations double |
| 118 | as the required transparency. |
| 119 | |
| 120 | **Sub-national pricing ("wealthier part of a country") β never as |
| 121 | detection** (decided): no precedent exists for residence-based sub-national |
| 122 | digital pricing; region-level IP geolocation is 55β80% accurate and exactly |
| 123 | the interrogation this product refuses. The honor picker already solves it |
| 124 | per-person, more accurately than any geo-database could β say so on the |
| 125 | pricing page. |
| 126 | |
| 127 | ## The picker (the honor mechanism, concretely) |
| 128 | |
| 129 | 1. The plan picker shows the tier as today, with a **band row** beneath: four |
| 130 | prices, one pre-selected. Pre-selection: geo-suggestion (Phase 1, CDN |
| 131 | country header, evaluated in the page, never stored) for non-EU/EEA |
| 132 | visitors; otherwise Base. |
| 133 | 2. One quiet line under the selection: *"Prices are suggested by country. |
| 134 | Pick the one that's right for you β no questions asked."* |
| 135 | 3. **Deep links** (`β¦/upgrade?tier=family&band=reduced`) pre-select a band β |
| 136 | Paul's "click a link, get the lower price, instantly": newsletters, NGO |
| 137 | wikis, a friend's message. The server honours whatever band arrives. No |
| 138 | check, ever. |
| 139 | 4. **Not Stripe promotion codes** (considered, rejected): percent-off coupons |
| 140 | persist across Billing Portal plan switches, hide the band from |
| 141 | `tierByPrice()` and the ledger, and frame Reduced as a deal off *your* |
| 142 | price when it is simply *the* price for that band. **Separate Price IDs |
| 143 | per band** are architecturally native β the price book already maps |
| 144 | priceβtier; it grows one dimension. |
| 145 | |
| 146 | ### Local-currency display before checkout (added 2026-07-14) |
| 147 | |
| 148 | Show every picker price with an approximate local figure β *"β¬7/year |
| 149 | (β $8.20)"* β **before** the buyer reaches Stripe: |
| 150 | |
| 151 | - **Display-only conversion.** Billing stays EUR (the tier-economics |
| 152 | EUR-settlement stance). The local figure is an estimate and is labelled: |
| 153 | *"billed in euros β β shown in your currency at today's rate."* |
| 154 | - **Rates:** ECB daily reference rates (~30 currencies, free, no key). The |
| 155 | home service fetches once daily and serves a tiny cached JSON alongside the |
| 156 | price book, with the last-good snapshot as fallback β the client never |
| 157 | talks to a third party (CSP stays pinned). |
| 158 | - **Currency choice comes from the browser locale** (`Intl.NumberFormat` / |
| 159 | `navigator.language`), not IP β privacy-consistent, works offline-ish, and |
| 160 | a small currency override sits next to the figure. Nothing is stored. |
| 161 | - **Later, optional (Phase 3):** true multi-currency *charging* via Stripe |
| 162 | `currency_options` (one amount per currency, charm-rounded, curated |
| 163 | annually) β only if EUR-billing measurably hurts non-EU conversion. Note |
| 164 | `currency_options` cannot express bands *inside* a currency β one amount |
| 165 | per currency per Price β which is exactly why bands are separate Prices |
| 166 | and currency presentment is an independent layer on top. Stripe's |
| 167 | Adaptive Pricing was evaluated and rejected: it is FX localisation with a |
| 168 | 2β4% customer-paid conversion fee, not PPP adjustment, and the hidden fee |
| 169 | is off-brand. |
| 170 | |
| 171 | ## The +15% annual review cap (Paul's pricing promise) |
| 172 | |
| 173 | **The rule:** every live price β monthly or annual billing alike β is |
| 174 | reviewed once a year (anchored to the subscription anniversary, so reviews |
| 175 | stagger naturally); silent renewal can carry at most **+15%**; anything |
| 176 | larger requires an explicit opt-in to a new subscription. |
| 177 | |
| 178 | The cap is **the sole propagation channel**: index updates, band |
| 179 | reassignments, FX drift, and base-price changes reach existing subscribers |
| 180 | only through reviews. New customers always pay the current table. Stripe |
| 181 | grandfathers by default (a subscription renews on *its* Price until updated), |
| 182 | so "current table" (the env price book) and "your price" (the subscription's |
| 183 | Price id) separate cleanly; the ledger gains a pending-review notice |
| 184 | (space id, from-price, to-price, effective date, consent-required flag). |
| 185 | |
| 186 | The review sweep, per subscription: |
| 187 | |
| 188 | - **Current β€ subscribed** β schedule the decrease at next renewal. |
| 189 | Unconditional, no ceremony, message it proudly. (The asymmetry β decreases |
| 190 | automatic, increases capped/consented β is deliberately public: German and |
| 191 | Italian courts voided Netflix's and Spotify's adjustment clauses partly for |
| 192 | being increase-only. Symmetry is the cure.) |
| 193 | - **Gap β€ 15%** β schedule the increase at next renewal with in-product |
| 194 | notice and an easy cancel/step-down path. |
| 195 | - **Gap > 15%** β **hold + invite** (decided): no silent change, no |
| 196 | compounding 15-15-15 ladder; the subscriber stays on the old price until |
| 197 | they affirmatively opt in to the current table (or a lower band). |
| 198 | - Discipline: the silent path only ever moves a subscription onto an |
| 199 | *existing catalog Price* (current or `"retired": true` vintages kept in the |
| 200 | price book) β never bespoke per-cohort amounts chasing "exactly +15%", |
| 201 | which would explode the book and break `tierByPrice()` legibility. |
| 202 | |
| 203 | **Honor-band subscribers track their own band's price** β Reduced is band C's |
| 204 | real price, not a discount off band A β so there is no mechanism by which an |
| 205 | honor price silently walks back up: a band reassignment is >15% by |
| 206 | construction, hence always hold+invite. The invite surface may gently show |
| 207 | the suggested band ("You're welcome on the reduced price as long as you need |
| 208 | it β the suggested price for your country is β¬5"), keeping the social |
| 209 | contract visible without enforcing it. |
| 210 | |
| 211 | **Notice without email** (accounts are anonymous passkeys; Checkout collects |
| 212 | no email β verify nothing downstream assumes one): notices land in-product β |
| 213 | (1) an inline row on the home page where Upgrade/Manage live ("Renews 12 Aug |
| 214 | at β¬5.75 β review / change / cancel"), (2) the space's Admin-app update |
| 215 | stream + admin push (the announce.go channel), (3) Eleven can explain any |
| 216 | price from the public band table. "Explicit opt-in" = an authenticated |
| 217 | affirmative act on the home page (same ownership gate as checkout), recorded |
| 218 | in the ledger, then executed as a Stripe subscription update (never |
| 219 | cancel+recreate β preserves the customer object, anniversary, and ledger |
| 220 | row). Mechanics: plain subscription update with `proration_behavior=none` |
| 221 | near renewal; `invoice.upcoming` webhook as the notice trigger. |
| 222 | |
| 223 | Regulatory shape-check: Ofcom's 2025 rule (in-contract rises must be |
| 224 | disclosed in pounds-and-pence at point of sale) says state the cap **in |
| 225 | euros on the picker** β "Family: never more than β¬0.75/yr more at renewal |
| 226 | without your say-so" β not as a bare percentage. The terms need a |
| 227 | transparent variation clause encoding cap + cadence + index reference + |
| 228 | symmetric downside + cancel-anytime, drafted by a human lawyer before |
| 229 | Phase 2 ships. |
| 230 | |
| 231 | ## Stripe implementation |
| 232 | |
| 233 | **11 Prices** (nominally 3 tiers Γ 4 bands, minus Business's solidarity β |
| 234 | the elevens ladder, annual-only throughout), minted by the committed tool |
| 235 | `tools/stripe-band-prices.mjs` β its `BAND_TABLE` is this doc's matrix as |
| 236 | code, reviewed like any diff. The operator runs it from their own terminal |
| 237 | (sandbox key first, then live; the key never transits a session): |
| 238 | |
| 239 | ```sh |
| 240 | STRIPE_SECRET_KEY=sk_β¦ node tools/stripe-band-prices.mjs --book current.json # dry-run |
| 241 | STRIPE_SECRET_KEY=sk_β¦ node tools/stripe-band-prices.mjs --book current.json --apply |
| 242 | ``` |
| 243 | |
| 244 | It reuses each tier's existing Stripe product where the current book has one |
| 245 | (subscriber history stays put), creates every missing ladder Price β base and |
| 246 | bands β idempotently (lookup keys `eleven11-<tier>-<band>`; re-runs create |
| 247 | nothing; the old `eleven-*` prices stay untouched for lapsing subscribers), |
| 248 | and prints the complete new price book β sealed into `/data/cloud.env` via |
| 249 | the operator env flow. Per-unit tiers ride `unit_amount_decimal`, and the |
| 250 | book carries the flags the cloud already understands (`per_seat`, |
| 251 | `min_seats`, `business`): |
| 252 | |
| 253 | ```json |
| 254 | [{"key":"family","name":"Family", |
| 255 | "label":"β¬5.50/year","price":"price_baseFam", |
| 256 | "eur":5.5,"net_eur_year":5.168, |
| 257 | "bands":{ |
| 258 | "supporter":{"label":"β¬7.70/year","price":"price_supFam","eur":7.7,"net_eur_year":7.335}, |
| 259 | "reduced":{"label":"β¬2.75/year","price":"price_redFam","eur":2.75,"net_eur_year":2.459}, |
| 260 | "solidarity":{"label":"β¬1.40/year","price":"price_solFam","eur":1.4,"net_eur_year":1.129}}}, |
| 261 | {"key":"community","name":"Community & Friends", |
| 262 | "label":"β¬0.11/member/year","price":"price_baseCom", |
| 263 | "eur":0.11,"net_eur_year":0.108,"per_seat":true,"min_seats":100, |
| 264 | "bands":{"β¦":"β¦"}}] |
| 265 | ``` |
| 266 | |
| 267 | (`net_eur_year` is the free pool's revenue input β see `docs/free-pool.md`.) |
| 268 | |
| 269 | Touch points: |
| 270 | |
| 271 | - `checkoutHandler`: optional `band` param (default `base`), resolve |
| 272 | `tier.bands[band].price`, stamp `metadata[band]`. Ownership gates, |
| 273 | no-customer-identity, webhook routing: unchanged. |
| 274 | - Webhook: `tierByPrice` β `tierBandByPrice` (still a flat scan over a few |
| 275 | dozen rows); the ledger records the band. |
| 276 | - Billing Portal: four per-band portal configurations (portals are minted |
| 277 | per-session, so the cloud picks the customer's band's config); band |
| 278 | *changes* go through the home surface, which can message them β never the |
| 279 | portal. |
| 280 | - Telemetry that respects the ethos: band mix by month from the ledger β no |
| 281 | per-user tracking β is how the US-at-Γ1.4 call gets checked. |
| 282 | |
| 283 | ### VAT β Stripe Tax (decided: enable; consumer-inclusive, Business-exclusive) |
| 284 | |
| 285 | Today nothing sets `automatic_tax`; the EUR figure is charged as-is and VAT |
| 286 | is implicit. With bands multiplying the price book, this gets fixed properly. |
| 287 | **The split (decided 2026-07-14): Family and C&F prices are VAT-inclusive; |
| 288 | Business prices are VAT-exclusive** β the standard B2C/B2B convention. |
| 289 | |
| 290 | - **Code (this repo):** set `automatic_tax[enabled]=true` on Checkout |
| 291 | Sessions. **Family/C&F Prices** are created with `tax_behavior=inclusive` |
| 292 | β labels stay the total price (β¬5 means β¬5, VAT inside, satisfying EU |
| 293 | price-indication rules) and net revenue varies by buyer country (VAT |
| 294 | 17β27% comes out of the sticker). **Business Prices** are created with |
| 295 | `tax_behavior=exclusive` β the sticker is ex-VAT, tax is added at |
| 296 | checkout, and Business checkout enables `tax_id_collection` so an EU |
| 297 | business with a valid VAT ID gets the **reverse charge** (no VAT |
| 298 | collected, they self-account). Picker labels for Business carry |
| 299 | "excl. VAT" wherever the price appears. Portal configs follow the same |
| 300 | behaviors. |
| 301 | - **Bookkeeping:** consumer-tier `net_eur_year` figures become net-of-VAT |
| 302 | approximations across buyer countries (the free pool tolerates that |
| 303 | comfortably); Business `net_eur_year` is *more* stable than today β |
| 304 | β¬11/seat is β¬11/seat regardless of buyer country (per-seat since |
| 305 | 2026-07-22, so the pool input scales it by the billed seats). |
| 306 | - **Caveat:** ex-VAT display is standard for business-directed offers |
| 307 | (Slack/Notion-style), but EU price-indication rules technically demand |
| 308 | consumer-facing totals be inclusive β so the Business tier should present |
| 309 | as business-directed (it already does), show "excl. VAT" adjacent to |
| 310 | every price, and Checkout shows the final total before payment. Include |
| 311 | in the Phase 1 legal review. |
| 312 | - **Dashboard (operator β Paul):** two places. |
| 313 | *Settings β Tax* (dashboard.stripe.com/settings/tax): origin address + |
| 314 | defaults β note the "Include tax in prices" default (Automatic/Yes/No) |
| 315 | is **superseded by our explicit per-Price `tax_behavior`**, so its value |
| 316 | doesn't matter once the 20 Prices exist; its Automatic mode |
| 317 | (currency-based: USD/CAD exclusive, EUR inclusive) is *not* our |
| 318 | inclusive/exclusive split, which is per-tier, not per-currency. |
| 319 | *Tax β Registrations* (dashboard.stripe.com/tax/registrations): **the |
| 320 | load-bearing step** β Stripe Tax only calculates where a registration is |
| 321 | added and merely monitors thresholds elsewhere. Needed eventually: the |
| 322 | home-country VAT registration + the **EU OSS (Union scheme)**, plus |
| 323 | others as the monitoring tab flags obligations. These are account-level |
| 324 | actions the code cannot and should not do β and until the code opts in |
| 325 | (`automatic_tax[enabled]=true` per session), a configured dashboard |
| 326 | still changes nothing at checkout. |
| 327 | **Registrations are deferred (Paul, 2026-07-14).** Phase 0 ships without |
| 328 | them and behaves gracefully: with zero registrations Stripe Tax |
| 329 | calculates β¬0 everywhere, so inclusive prices are unchanged for the |
| 330 | buyer and Business checkouts add nothing yet β the plumbing |
| 331 | (per-Price `tax_behavior`, `automatic_tax`, `tax_id_collection`) is all |
| 332 | in place, and adding a registration later activates it with no code |
| 333 | change. Stripe Tax's threshold-monitoring tab is the revisit trigger |
| 334 | (note: monitoring tracks configuration, not obligation β whether an |
| 335 | obligation already exists today is an accountant question, not a |
| 336 | dashboard one). |
| 337 | - Existing subscriptions keep their current Prices; the new tax behaviors |
| 338 | apply as new Prices roll through checkout and reviews. |
| 339 | |
| 340 | ## Messaging (drafts, not final copy β lock-and-key voice) |
| 341 | |
| 342 | The canonical cross-subsidy line (decided, aggregate form only): |
| 343 | |
| 344 | > **"Paying supports free spaces for those who can't."** |
| 345 | |
| 346 | - **Supporter:** "This is the supporter price for Switzerland. Paying |
| 347 | supports free spaces for those who can't β the difference doesn't go to |
| 348 | us, it goes to the free pool. If it's not right for you, pick the standard |
| 349 | price β no questions asked." |
| 350 | - **Base:** "The standard price. It covers your space's costs with margin |
| 351 | for resilience β the books are public." |
| 352 | - **Reduced / Solidarity:** "You're on the reduced price, carried by |
| 353 | supporters. It's yours as long as you need it." Never "discount", never |
| 354 | "eligible", never a country name on this side. |
| 355 | - **The cap, everywhere prices appear:** "Reviewed once a year. Never up |
| 356 | more than 15% β for Family, at most β¬0.75 β never onto a new plan without |
| 357 | your explicit yes. Down happens by itself." |
| 358 | - Never a per-space "your β¬7 funds N spaces" claim (decided): the aggregate |
| 359 | pool gauge ("paid spaces fund the free pool MΓ over") is the verifiable |
| 360 | form. Precedent says transparent cross-subsidy works with an anchor, a |
| 361 | chosen default, and visible reciprocity (Guardian, solidarity tickets) and |
| 362 | backfires when hidden or anchorless (Amazon 2000, Panera) β this design |
| 363 | keeps the standard-price tap in the same glance as the supporter ask. |
| 364 | |
| 365 | ## Phasing |
| 366 | |
| 367 | - **Phase 0 β bands, pure honor, no geolocation.** The 19 Prices; price-book |
| 368 | `bands` + `net_eur_year`; `tierBandByPrice`; checkout `band` param; picker |
| 369 | with all four bands, Base pre-selected for everyone; deep-link band param; |
| 370 | local-currency β display (ECB rates via home); Stripe Tax |
| 371 | (code + dashboard runbook above). Legally inert everywhere β it's four |
| 372 | prices β and already delivers "anyone, anywhere, one click to the lower |
| 373 | price" plus the supporter story. Measure band mix. |
| 374 | *Built (code side): the cloud rails (book/checkout/ledger/tax knob), the |
| 375 | home picker + deep links + β display, and the minting tool. What remains is |
| 376 | operational: run the tool (sandbox, then live), seal the new book into the |
| 377 | vault, and flip `LCHAT_STRIPE_AUTOMATIC_TAX=1` with it β everything renders |
| 378 | as today until the book carries bands.* |
| 379 | - **Phase 1 β geo-suggested defaults.** Commit `tools/pricing-bands.mjs` + |
| 380 | the countryβband table; pre-select by CDN country header (client-side, |
| 381 | never stored); EU/EEA pinned to Base; publish the table on the pricing |
| 382 | page. **Human legal review gates this phase.** |
| 383 | - **Phase 2 β the annual review machinery.** Ledger review/consent fields; |
| 384 | the sweep; home inline notices + admin announce; decreases automatic, |
| 385 | β€15% with notice, >15% hold+invite; terms variation clause (lawyer). |
| 386 | - **Phase 3 β optional refinements:** true multi-currency charging via |
| 387 | `currency_options` (only if EUR-billing measurably hurts conversion); |
| 388 | annual index refresh cadence (Phase 2 already absorbs band moves); |
| 389 | free-pool gauge coupling on the pricing page. |
| 390 | - **Never:** sub-national pricing as detection; promotion-code discounts; |
| 391 | Adaptive Pricing's customer-paid FX fee. |
| 392 | |