diff --git a/badges-flow-mvp.svg b/badges-flow-mvp.svg
deleted file mode 100644
index 711366f0c5..0000000000
--- a/badges-flow-mvp.svg
+++ /dev/null
@@ -1 +0,0 @@
-Supporter badges - every screen, in the app and on the web one tree per surface, left to right - blue is the normal path, orange a variant, grey a platform difference, and the dashed pair is everything that crosses between the app and the site plans/2026-08-21-badges-web-checkout.md editable SVG In the app iOS idiom shown; Android and desktop differ only in chrome For the operator the CLI behind the codes Getting a badge, and what you get The store builds, and when it goes wrong Support SimpleX SimpleX is built by people who believe private messaging should not depend on advertising. A badge helps pay for it. i Why SimpleX is built this way Get the code Redeem the code Codes are bought in a browser. Nothing is charged in the app. A1. Where it starts Two actions, and no wizard behind either. The app does not ask which level or how many months: the site asks that, and asking twice would mean two catalogs, two price renderers and two places for them to disagree. 'Get the code' opens badges.simplex.chat with no parameters, so the site starts at its own first screen. On iOS and Play there is nothing to link out to. Support SimpleX SimpleX is built by people who believe private messaging should not depend on advertising. A badge helps pay for it. i Why SimpleX is built this way Redeem the code no purchase button on this build A1b. On iOS and Play Same screen, one action. A button that opens a browser to buy a digital good is exactly what Apple and Google reject, so the store builds do not offer one and say nothing about where a code comes from. Until store evidence is verified, a code bought elsewhere is the only route to a badge on these platforms - which is the accepted cost of decision 6, not an oversight. Redeem the code ‹ Support Redeem code Paste the code from your receipt. SXB-9K2M4-7QRT1-XZ5W Paste Redeem Formats as you type and folds I and L to 1, O to 0 - the same normalisation the service does, so a code read off a screen cannot fail on ambiguity alone. A2. Redeeming Twenty Crockford characters in five groups. The check character is verified before anything is sent, so a mistyped code never reaches the service and never costs a throttle token. a valid code ‹ Settings Supporter perks Legend shown on your profile ENDS 24 August 2027 Prepaid months have no billing date. The badge is reissued each month from the balance you already paid for, and ends when it runs out. Add more months A3. After redeeming 'Ends', never 'renews' - nothing recurs and the copy must not imply it does. The date is the paid-through date from the ledger balance, not the credential's expiry, which is a week-aligned internal the user never sees. on the profile ‹ Chats Alice A Alice Legend last seen recently IN GROUPS A Alice Legend B Bob Supporter C Carol The badge travels with the profile, so it shows wherever a profile does - in chats, in a member list, on a contact card. A4. How everyone else sees it The point of the whole flow. The badge is a signed credential carried in the profile and verified by the recipient's own client against the issuer key it already ships - not a flag the server asserts, and not something a client can set for itself. Any error the service returns: code_invalid, code_used, code_expired or rate_limited. ‹ Support Redeem code This code isn't valid code_invalid This code has already been used code_used This code has expired code_expired Too many attempts. Try again in 4 minutes. rate_limited - retryAfter 240 Could not verify the credential internal One message per error the service can return. Anything else, including a credential that fails to verify locally, shows the service's own text rather than a blank screen. A2b. Every redemption error A revoked code is deliberately indistinguishable from an unknown one - both say 'isn't valid', so a guesser learns nothing from a revocation. Support tells them apart with codes status; the wire does not. The last paid month has been issued and the balance is zero. ‹ Settings Supporter perks Legend ended 24 August 2027 Your badge has ended The months you paid for have all been issued. The badge stopped showing on your profile; nothing was cancelled and nothing is owed. Add more months A3b. The balance runs out An exhausted balance is not an error: the service returns no credential and a zero-balance statement, and the app says so plainly. Prepaid means it simply stops. simplex-badge-service $ simplex-badge-service codes issue \ --type legend --months 12 --count 3 \ --batch conf-2026 --expires 2027-08-24 SXB-9K2M4-7QRT1-XZ5WB-3NHD8 SXB-2W7XP-4LMQ8-9DKR3-6TVZ5 SXB-8HYNC-1FGJ6-5PBW2-7QSXD 3 codes issued. Printed once; only hashes stored. OP1. Minting codes Compensation and promotional codes have no order behind them, so they are random rather than derived. Printed once to stdout and never again - the database holds only SHA-256 hashes, which is what makes a stolen copy useless. support $ simplex-badge-service codes status \ --ref K7M2Q order 8f3a...c21e paid 24 Aug badge legend, 12 months code redeemed 2 Sep --reveal refused: code already redeemed OP2. Resolving a reference The customer quotes the five-character reference from their receipt - never the order id, never the code. --reveal is refused once a code is redeemed or revoked, on the same reasoning that stops the web page disclosing it. On the web badges.simplex.chat - one centred column, max-width 560, the logo the only image Buying, start to finish The other ways it ends Refused at checkout badges.simplex.chat Support SimpleX SimpleX has no ads, no user accounts and nothing to sell. A supporter badge helps pay for the people who build it. Choose your level Already bought a code? Redeem it in the app: Settings, Supporter perks. The badge shows on your profile. Nothing renews by itself, and no account is created. simplex.chat/contact B1. The landing page Not in the plan, and now load-bearing. D3 starts the site at the tier question on the assumption that the app has already asked it; the app no longer asks anything, so this is where everyone begins - from a link, from a search, or from 'Get the code' in the app. It does not price the tiers: that is B2's question, and answering it here would make this page a worse version of that one. Choose your level badges.simplex.chat/#/tier Choose your level Bigger files, and longer for people to collect them. Supporter $7 / month 2 GB files 7 days storage Legend $70 / month 5 GB files 21 days storage Continue simplex.chat/contact B2. Question one: the level The wizard proper starts here. One centred column at max-width 560, generous whitespace, accent #0053D0 - deliberately not the app's chrome, because it is not the app. One question per screen, and a tier with no active price renders disabled rather than hidden. Continue badges.simplex.chat/#/months How long? Prepaid months. Nothing renews by itself. 1 month $70 3 months $140 save 33% 12 months $420 save 50% Continue simplex.chat/contact B3. How long Every figure comes from GET /api/catalog, the same totals the RPC catalog gives the app. The browser formats minor units and computes the comparison only - it never multiplies a price by a month count. Continue badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 with Monero Card is handled by Stripe. Bitcoin and Monero are on-chain, through BTCPay. simplex.chat/contact B4. Check your order, and pay Two screens in the plan, one here. The summary and the method are the same decision - what am I buying and how do I hand over the money - and splitting them made the buyer confirm a choice they had not made yet. POST /api/checkout still carries priceId, offerId and method and nothing else: badge type and months are derived server-side from the ids, so a tampered request cannot buy a Legend badge at a Supporter price. Nothing is prefilled: with the app's wizard gone there is nothing to prefill from, so D5's ?tier= and ?months= parameters have no producer in this milestone and every buyer walks B1 to B4. The three marks are a choice, not three actions: D7 has one Pay button, it carries the total, and it is the last thing anyone presses - so the amount is on screen at the moment of paying and the site keeps the choose-then-continue rhythm of B2 and B3. Pay with Monero badges.simplex.chat/?order=8f3a...c21e Send 1.482 XMR $420.00 - this rate is held for 58:12 MONERO ADDRESS 48HqK2...9fRtWc Copy Waiting for the payment to confirm REFERENCE K7M2Q Bookmark this page - the address and the countdown both live on this URL. simplex.chat/contact B5. Paying in crypto Polls GET /api/order every 2s for the first minute then every 10s, and stops while the tab is hidden. A partial payment stays 'pending' - partials are the normal first event of a multi-transaction payment, not a failure. it confirms badges.simplex.chat/?order=8f3a...c21e ✓ Paid. Here is your code. SXB-9K2M4-7QRT1-XZ5WB-3NHD8 Copy code scan to carry it to your phone REDEEM IT IN THE APP Settings -> Supporter perks -> Redeem code The code is shown here only. This page works until 23 September, and stops the moment the code is redeemed. The link is the code - treat it so. simplex.chat/contact B6. The code, once Derived from the order id rather than stored, so a reload recomputes it while only its hash is ever at rest. Disclosure stops at redemption, at revocation, or 30 days after settlement - whichever comes first. Redeemed, revoked, or thirty days after settlement. badges.simplex.chat/?order=8f3a...c21e This code has been redeemed The code is no longer shown here It was redeemed on 2 September. If that was not you, get in touch. Legend, 12 months paid 24 August The order stays; the capability does not. simplex.chat/contact B6b. The page outlives the code Disclosure ends at redemption, at revocation, or 30 days after settlement. The order is still here because support resolves a reference against it - but the URL stops being equivalent to the code, which is the whole point. The provider's section is missing from badge_service.ini. badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 Monero is temporarily unavailable Try another method, or come back later. PAY WITH Bitcoin Monero unavailable Card Pay $420.00 with Bitcoin simplex.chat/contact B4b. A provider is not configured 503 provider_unavailable from /api/checkout. The method is shown and disabled rather than omitted, so an operator who forgot a section sees it immediately instead of wondering why nobody pays in Monero. Pay by card - Stripe returns here badges.simplex.chat/?order=51c7...9d02 Payment received Waiting for the card network to confirm. Still processing This usually takes a few seconds. The page updates itself. If nothing happens for 15 minutes, we stop waiting and show you what to do next. simplex.chat/contact B5b. Coming back from the card flow Stripe's success_url returns here with ?order=. The return is NOT proof of payment - only the webhook settles an order, so this screen polls rather than celebrating. The cancel URL deliberately carries no order reference at all. The invoice expired with less than the full amount received. badges.simplex.chat/?order=8f3a...c21e This invoice expired 0.734 XMR arrived, which is not the full amount The rate window has closed, so the shortfall is no longer meaningful. Quote the reference below and we will sort it out. REFERENCE K7M2Q Start a new invoice Never shows the code, and never the ?order= URL. simplex.chat/contact B5c. Expired, and underpaid The one state that needs a human. An expired-but-paid invoice still settles later - late on-chain settlement is routine and moves the order to paid - but an underpaid one stops here with the reference support resolves by. The price was disabled while the buyer was deciding. badges.simplex.chat/#/checkout These prices have changed Start again with the current prices The badge you chose was repriced while you were deciding. Nothing was charged. Start again price_disabled / offer_disabled / offer_mismatch simplex.chat/contact B4c. The catalog moved underneath Prices are checked at checkout and only there. A deprecated price is still honoured for someone mid-flow; a disabled one is refused. Repricing appends a new price rather than editing the old, so this is rare but reachable. Five checkout requests inside one minute, from one IP. badges.simplex.chat/#/checkout Too many attempts Try again in 46 seconds The Pay button is disabled until then. Pay $420.00 with Monero 429, with Retry-After. The polling loop backs off to that value rather than treating it as a failure. simplex.chat/contact B4d. Rate limited Five checkout requests a minute per IP, because each one reaches a payment provider. Order polling gets sixty. The redemption path has no IP at all - it runs over SimpleX RPC and is throttled per signer instead. On the web, on a phone the same site at 390 px - what stays in columns, and what has to stack The same six screens at 390 px badges.simplex.chat Support SimpleX No ads, no accounts, nothing to sell. A badge pays the people who build it. Choose your level Already bought a code? Redeem it in the app: Settings, Supporter perks. simplex.chat/contact M1. Nothing to reflow The landing page was already a single column of one thing after another, so the phone gets the same page with a smaller hero. This is what the max-width 560 rule buys: on a phone the column simply becomes the viewport. The lede is shorter than the desktop's, because 320 px leaves 280 px of column and a lede that wraps to three lines on a phone is a lede that is too long. Choose your level badges.simplex.chat/#/tier Choose your level Bigger files, and longer to collect them. Supporter $7 / mo 2 GB files 7 days storage Legend $70 / mo 5 GB files 21 days storage Continue simplex.chat/contact M2. Two tiers still fit side by side 169 px each at 390, and 134 at 320. Two things a buyer is comparing must stay comparable: stacking them would put the second below the fold and turn a comparison into a scroll. The badge art shrinks; nothing else changes. Continue badges.simplex.chat/#/months How long? Prepaid months. Nothing renews. 1 month $70 3 months $140 save 33% 12 months $420 save 50% Continue simplex.chat/contact M3. Three durations still fit across 110 px each at 390, 87 at 320. They are three values of one number, so they belong on one line where the prices can be read against each other; the saving pill sits under its price rather than beside it. Continue badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 with Monero Card by Stripe. Bitcoin and Monero on-chain, through BTCPay. simplex.chat/contact M4. The widest thing on the site The summary and the three ways to pay are the tightest fit in the document: 110 px per button at 390 and 87 at 320, which still holds a 24 px mark above a 47 px word. This is the screen D2's 320 px check is really about, and the reason the marks carry no text beside them. Pay with Monero badges.simplex.chat/?order=8f3a...c21e Send 1.482 XMR $420.00 - rate held for 58:12 MONERO ADDRESS 48HqK2...9fRtWc Copy Waiting for the payment REFERENCE K7M2Q simplex.chat/contact M5. Here the columns have to stack The QR and the payment details cannot sit side by side at this width, so they stack - and the QR goes first, because a phone paying from a wallet app on the same device needs the address, while a phone being scanned by another device needs the code. Both are one thumb apart. it confirms badges.simplex.chat/?order=8f3a...c21e ✓ Paid. Here is your code. SXB-9K2M4-7QRT1-XZ5WB-3NHD8 Copy code scan to carry it to another device REDEEM IT IN THE APP Settings -> Supporter perks -> Redeem code The code is shown here only. This page works until 23 September, and stops the moment the code is redeemed. simplex.chat/contact M6. The code stays the hero Everything stacks, in the order it is needed: the code, the button that copies it, the QR that carries it to another device, then where to put it. The warning stays last and stays red - it is the only place the code is shown, and on a phone it is the easiest to lose. Its body is the tightest text in the band at 320 px, with 16 px of margin, which is why that copy is two short lines rather than the desktop's three. Two things here must be allowed to wrap rather than set on one line: the code, at its hyphens, and the redeem path, at its arrows. Measured, both overflow a 320 px column already at the default text size. The reader has set a larger text size, so the row no longer fits. badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 simplex.chat/contact M7. When the text is scaled up The same screen with the system text size at 130 per cent, which iOS and Android both apply to web content and which a lot of people run. A 24 px mark beside a 17 px word needs 96 px and the row only has 100, so the three ways to pay stop being a row and become a stack, and the Pay button drops the method it can no longer fit. Measured, not guessed: at 130 per cent the row fails on every phone up to 412 px. Get the code opens badges.simplex.chat with no parameters - the site asks everything the code is pasted into A2 the only thing that crosses from the site back to the app Design document. The app and the site are each one tree read left to right, and the third band is the same site at phone width. In a tree: a screen is always to the left of everything it leads to, each lane is labelled for what its screens have in common, and the cause of a branch is written above the screen it produces, in the colour of the line that reaches it. App screens are drawn in the iOS idiom; Android and desktop differ only in chrome. The site is drawn in its own idiom - one centred column at max-width 560, accent #0053D0 from website/tailwind.config.js - and is deliberately not a copy of the app's. Badge artwork is the shipped art, verbatim from MR/images/badge_{supporter,legend}.svg, and the hero on A1 and B1 is phone-supporter.png from simplex-chat-art, the same asset PhoneSupporterHero draws on that screen today. D2 asks for the logo alone, and the tier cards carrying the badges are the one deliberate deviation. The Bitcoin and Monero marks are the official ones from simple-icons (CC0-1.0) in their registered brand colours; the card glyph is Lucide's credit-card (ISC). Built today: the service schema, the pricing catalog, the ledger, code derivation and classification, credential signing, the RPC dispatcher and the operator CLI. Design, not yet built: every screen above, the web listener and its endpoints, both payment providers, and the client redeem path. Store purchase is removed on iOS and Play for the duration of this plan, and the app asks no purchase questions at all: the tier, the duration and the method are the site's, so G1, G4 and G5 lose most of their scope and APIGetBadgeCatalog loses its only client-side caller.
\ No newline at end of file
diff --git a/plans/2026-08-21-badges-web-checkout.md b/plans/badges-codes/2026-08-21-badges-web-checkout.md
similarity index 99%
rename from plans/2026-08-21-badges-web-checkout.md
rename to plans/badges-codes/2026-08-21-badges-web-checkout.md
index edbabf1528..f008af84f9 100644
--- a/plans/2026-08-21-badges-web-checkout.md
+++ b/plans/badges-codes/2026-08-21-badges-web-checkout.md
@@ -4,6 +4,7 @@
**Branch:** `sh/badges-codes` (off `badges`)
**Status:** approved, multi-session. This file is the source of truth; agents update the progress tracker in it.
**Supersedes:** `plans/2026-08-04-badges-mvp-scope.md` milestone 2, the in-app invoice flow. Milestone 3 (store purchase) is unaffected.
+**Design:** `plans/badges-codes/badges-flow-mvp.svg` — every screen in the app and on the site, the states between them, and the site at phone width. It records five deviations from this plan, each argued in its own caption: a landing page D3 does not specify, B4 merging D3's method and checkout screens, no in-app wizard at all, the badge art on the tier cards against D2's logo-only rule, and a storage perk that has no implementation anywhere yet.
**Citation keys:** `plans/2026-07-30-supporter-badges-v3-ux.md` = **UX §n**; `plans/2026-07-31-badges-core-implementation.md` = **core §n**; `docs/protocol/badges-rpc.md` = **RPC §n**; `plans/2026-08-04-badges-mvp-scope.md` = **MVP §n** (superseded, but still the source for the provider-event rules).
---
diff --git a/plans/badges-codes/badges-flow-mvp.svg b/plans/badges-codes/badges-flow-mvp.svg
new file mode 100644
index 0000000000..71e1753dba
--- /dev/null
+++ b/plans/badges-codes/badges-flow-mvp.svg
@@ -0,0 +1 @@
+Supporter badges - every screen, in the app and on the web one tree per surface, left to right - blue is the normal path, orange a variant, grey a platform difference, and the dashed pair is everything that crosses between the app and the site plans/badges-codes/2026-08-21-badges-web-checkout.md editable SVG In the app iOS idiom shown; Android and desktop differ only in chrome For the operator the CLI behind the codes Getting a badge, and what you get The store builds, and when it goes wrong Support SimpleX SimpleX is built by people who believe private messaging should not depend on advertising. A badge helps pay for it. i Why SimpleX is built this way Get the code Redeem the code Codes are bought in a browser. Nothing is charged in the app. A1. Where it starts Two actions, and no wizard behind either. The app does not ask which level or how many months: the site asks that, and asking twice would mean two catalogs, two price renderers and two places for them to disagree. 'Get the code' opens badges.simplex.chat with no parameters, so the site starts at its own first screen. On iOS and Play there is nothing to link out to. Support SimpleX SimpleX is built by people who believe private messaging should not depend on advertising. A badge helps pay for it. i Why SimpleX is built this way Redeem the code no purchase button on this build A1b. On iOS and Play Same screen, one action. A button that opens a browser to buy a digital good is exactly what Apple and Google reject, so the store builds do not offer one and say nothing about where a code comes from. Until store evidence is verified, a code bought elsewhere is the only route to a badge on these platforms - which is the accepted cost of decision 6, not an oversight. Redeem the code ‹ Support Redeem code Paste the code from your receipt. SXB-9K2M4-7QRT1-XZ5W Paste Redeem Formats as you type and folds I and L to 1, O to 0 - the same normalisation the service does, so a code read off a screen cannot fail on ambiguity alone. A2. Redeeming Twenty Crockford characters in five groups. The check character is verified before anything is sent, so a mistyped code never reaches the service and never costs a throttle token. a valid code ‹ Settings Supporter perks Legend shown on your profile ENDS 24 August 2027 Prepaid months have no billing date. The badge is reissued each month from the balance you already paid for, and ends when it runs out. Add more months A3. After redeeming 'Ends', never 'renews' - nothing recurs and the copy must not imply it does. The date is the paid-through date from the ledger balance, not the credential's expiry, which is a week-aligned internal the user never sees. on the profile ‹ Chats Alice A Alice Legend last seen recently IN GROUPS A Alice Legend B Bob Supporter C Carol The badge travels with the profile, so it shows wherever a profile does - in chats, in a member list, on a contact card. A4. How everyone else sees it The point of the whole flow. The badge is a signed credential carried in the profile and verified by the recipient's own client against the issuer key it already ships - not a flag the server asserts, and not something a client can set for itself. Any error the service returns: code_invalid, code_used, code_expired or rate_limited. ‹ Support Redeem code This code isn't valid code_invalid This code has already been used code_used This code has expired code_expired Too many attempts. Try again in 4 minutes. rate_limited - retryAfter 240 Could not verify the credential internal One message per error the service can return. Anything else, including a credential that fails to verify locally, shows the service's own text rather than a blank screen. A2b. Every redemption error A revoked code is deliberately indistinguishable from an unknown one - both say 'isn't valid', so a guesser learns nothing from a revocation. Support tells them apart with codes status; the wire does not. The last paid month has been issued and the balance is zero. ‹ Settings Supporter perks Legend ended 24 August 2027 Your badge has ended The months you paid for have all been issued. The badge stopped showing on your profile; nothing was cancelled and nothing is owed. Add more months A3b. The balance runs out An exhausted balance is not an error: the service returns no credential and a zero-balance statement, and the app says so plainly. Prepaid means it simply stops. simplex-badge-service $ simplex-badge-service codes issue \ --type legend --months 12 --count 3 \ --batch conf-2026 --expires 2027-08-24 SXB-9K2M4-7QRT1-XZ5WB-3NHD8 SXB-2W7XP-4LMQ8-9DKR3-6TVZ5 SXB-8HYNC-1FGJ6-5PBW2-7QSXD 3 codes issued. Printed once; only hashes stored. OP1. Minting codes Compensation and promotional codes have no order behind them, so they are random rather than derived. Printed once to stdout and never again - the database holds only SHA-256 hashes, which is what makes a stolen copy useless. support $ simplex-badge-service codes status \ --ref K7M2Q order 8f3a...c21e paid 24 Aug badge legend, 12 months code redeemed 2 Sep --reveal refused: code already redeemed OP2. Resolving a reference The customer quotes the five-character reference from their receipt - never the order id, never the code. --reveal is refused once a code is redeemed or revoked, on the same reasoning that stops the web page disclosing it. On the web badges.simplex.chat - one centred column, max-width 560, the logo the only image Buying, start to finish The other ways it ends Refused at checkout badges.simplex.chat Support SimpleX SimpleX has no ads, no user accounts and nothing to sell. A supporter badge helps pay for the people who build it. Choose your level Already bought a code? Redeem it in the app: Settings, Supporter perks. The badge shows on your profile. Nothing renews by itself, and no account is created. simplex.chat/contact B1. The landing page Not in the plan, and now load-bearing. D3 starts the site at the tier question on the assumption that the app has already asked it; the app no longer asks anything, so this is where everyone begins - from a link, from a search, or from 'Get the code' in the app. It does not price the tiers: that is B2's question, and answering it here would make this page a worse version of that one. Choose your level badges.simplex.chat/#/tier Choose your level Bigger files, and longer for people to collect them. Supporter $7 / month 2 GB files 7 days storage Legend $70 / month 5 GB files 21 days storage Continue simplex.chat/contact B2. Question one: the level The wizard proper starts here. One centred column at max-width 560, generous whitespace, accent #0053D0 - deliberately not the app's chrome, because it is not the app. One question per screen, and a tier with no active price renders disabled rather than hidden. Continue badges.simplex.chat/#/months How long? Prepaid months. Nothing renews by itself. 1 month $70 3 months $140 save 33% 12 months $420 save 50% Continue simplex.chat/contact B3. How long Every figure comes from GET /api/catalog, the same totals the RPC catalog gives the app. The browser formats minor units and computes the comparison only - it never multiplies a price by a month count. Continue badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 with Monero Card is handled by Stripe. Bitcoin and Monero are on-chain, through BTCPay. simplex.chat/contact B4. Check your order, and pay Two screens in the plan, one here. The summary and the method are the same decision - what am I buying and how do I hand over the money - and splitting them made the buyer confirm a choice they had not made yet. POST /api/checkout still carries priceId, offerId and method and nothing else: badge type and months are derived server-side from the ids, so a tampered request cannot buy a Legend badge at a Supporter price. Nothing is prefilled: with the app's wizard gone there is nothing to prefill from, so D5's ?tier= and ?months= parameters have no producer in this milestone and every buyer walks B1 to B4. The three marks are a choice, not three actions: D7 has one Pay button, it carries the total, and it is the last thing anyone presses - so the amount is on screen at the moment of paying and the site keeps the choose-then-continue rhythm of B2 and B3. Pay with Monero badges.simplex.chat/?order=8f3a...c21e Send 1.482 XMR $420.00 - this rate is held for 58:12 MONERO ADDRESS 48HqK2...9fRtWc Copy Waiting for the payment to confirm REFERENCE K7M2Q Bookmark this page - the address and the countdown both live on this URL. simplex.chat/contact B5. Paying in crypto Polls GET /api/order every 2s for the first minute then every 10s, and stops while the tab is hidden. A partial payment stays 'pending' - partials are the normal first event of a multi-transaction payment, not a failure. it confirms badges.simplex.chat/?order=8f3a...c21e ✓ Paid. Here is your code. SXB-9K2M4-7QRT1-XZ5WB-3NHD8 Copy code scan to carry it to your phone REDEEM IT IN THE APP Settings -> Supporter perks -> Redeem code The code is shown here only. This page works until 23 September, and stops the moment the code is redeemed. The link is the code - treat it so. simplex.chat/contact B6. The code, once Derived from the order id rather than stored, so a reload recomputes it while only its hash is ever at rest. Disclosure stops at redemption, at revocation, or 30 days after settlement - whichever comes first. Redeemed, revoked, or thirty days after settlement. badges.simplex.chat/?order=8f3a...c21e This code has been redeemed The code is no longer shown here It was redeemed on 2 September. If that was not you, get in touch. Legend, 12 months paid 24 August The order stays; the capability does not. simplex.chat/contact B6b. The page outlives the code Disclosure ends at redemption, at revocation, or 30 days after settlement. The order is still here because support resolves a reference against it - but the URL stops being equivalent to the code, which is the whole point. The provider's section is missing from badge_service.ini. badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 Monero is temporarily unavailable Try another method, or come back later. PAY WITH Bitcoin Monero unavailable Card Pay $420.00 with Bitcoin simplex.chat/contact B4b. A provider is not configured 503 provider_unavailable from /api/checkout. The method is shown and disabled rather than omitted, so an operator who forgot a section sees it immediately instead of wondering why nobody pays in Monero. Pay by card - Stripe returns here badges.simplex.chat/?order=51c7...9d02 Payment received Waiting for the card network to confirm. Still processing This usually takes a few seconds. The page updates itself. If nothing happens for 15 minutes, we stop waiting and show you what to do next. simplex.chat/contact B5b. Coming back from the card flow Stripe's success_url returns here with ?order=. The return is NOT proof of payment - only the webhook settles an order, so this screen polls rather than celebrating. The cancel URL deliberately carries no order reference at all. The invoice expired with less than the full amount received. badges.simplex.chat/?order=8f3a...c21e This invoice expired 0.734 XMR arrived, which is not the full amount The rate window has closed, so the shortfall is no longer meaningful. Quote the reference below and we will sort it out. REFERENCE K7M2Q Start a new invoice Never shows the code, and never the ?order= URL. simplex.chat/contact B5c. Expired, and underpaid The one state that needs a human. An expired-but-paid invoice still settles later - late on-chain settlement is routine and moves the order to paid - but an underpaid one stops here with the reference support resolves by. The price was disabled while the buyer was deciding. badges.simplex.chat/#/checkout These prices have changed Start again with the current prices The badge you chose was repriced while you were deciding. Nothing was charged. Start again price_disabled / offer_disabled / offer_mismatch simplex.chat/contact B4c. The catalog moved underneath Prices are checked at checkout and only there. A deprecated price is still honoured for someone mid-flow; a disabled one is refused. Repricing appends a new price rather than editing the old, so this is rare but reachable. Five checkout requests inside one minute, from one IP. badges.simplex.chat/#/checkout Too many attempts Try again in 46 seconds The Pay button is disabled until then. Pay $420.00 with Monero 429, with Retry-After. The polling loop backs off to that value rather than treating it as a failure. simplex.chat/contact B4d. Rate limited Five checkout requests a minute per IP, because each one reaches a payment provider. Order polling gets sixty. The redemption path has no IP at all - it runs over SimpleX RPC and is throttled per signer instead. On the web, on a phone the same site at 390 px - what stays in columns, and what has to stack The same six screens at 390 px badges.simplex.chat Support SimpleX No ads, no accounts, nothing to sell. A badge pays the people who build it. Choose your level Already bought a code? Redeem it in the app: Settings, Supporter perks. simplex.chat/contact M1. Nothing to reflow The landing page was already a single column of one thing after another, so the phone gets the same page with a smaller hero. This is what the max-width 560 rule buys: on a phone the column simply becomes the viewport. The lede is shorter than the desktop's, because 320 px leaves 280 px of column and a lede that wraps to three lines on a phone is a lede that is too long. Choose your level badges.simplex.chat/#/tier Choose your level Bigger files, and longer to collect them. Supporter $7 / mo 2 GB files 7 days storage Legend $70 / mo 5 GB files 21 days storage Continue simplex.chat/contact M2. Two tiers still fit side by side 169 px each at 390, and 134 at 320. Two things a buyer is comparing must stay comparable: stacking them would put the second below the fold and turn a comparison into a scroll. The badge art shrinks; nothing else changes. Continue badges.simplex.chat/#/months How long? Prepaid months. Nothing renews. 1 month $70 3 months $140 save 33% 12 months $420 save 50% Continue simplex.chat/contact M3. Three durations still fit across 110 px each at 390, 87 at 320. They are three values of one number, so they belong on one line where the prices can be read against each other; the saving pill sits under its price rather than beside it. Continue badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 with Monero Card by Stripe. Bitcoin and Monero on-chain, through BTCPay. simplex.chat/contact M4. The widest thing on the site The summary and the three ways to pay are the tightest fit in the document: 110 px per button at 390 and 87 at 320, which still holds a 24 px mark above a 47 px word. This is the screen D2's 320 px check is really about, and the reason the marks carry no text beside them. Pay with Monero badges.simplex.chat/?order=8f3a...c21e Send 1.482 XMR $420.00 - rate held for 58:12 MONERO ADDRESS 48HqK2...9fRtWc Copy Waiting for the payment REFERENCE K7M2Q simplex.chat/contact M5. Here the columns have to stack The QR and the payment details cannot sit side by side at this width, so they stack - and the QR goes first, because a phone paying from a wallet app on the same device needs the address, while a phone being scanned by another device needs the code. Both are one thumb apart. it confirms badges.simplex.chat/?order=8f3a...c21e ✓ Paid. Here is your code. SXB-9K2M4-7QRT1-XZ5WB-3NHD8 Copy code scan to carry it to another device REDEEM IT IN THE APP Settings -> Supporter perks -> Redeem code The code is shown here only. This page works until 23 September, and stops the moment the code is redeemed. simplex.chat/contact M6. The code stays the hero Everything stacks, in the order it is needed: the code, the button that copies it, the QR that carries it to another device, then where to put it. The warning stays last and stays red - it is the only place the code is shown, and on a phone it is the easiest to lose. Its body is the tightest text in the band at 320 px, with 16 px of margin, which is why that copy is two short lines rather than the desktop's three. Two things here must be allowed to wrap rather than set on one line: the code, at its hyphens, and the redeem path, at its arrows. Measured, both overflow a 320 px column already at the default text size. The reader has set a larger text size, so the row no longer fits. badges.simplex.chat/#/checkout Check your order Level Legend Duration 12 months Total $420.00 PAY WITH Bitcoin Monero Card Pay $420.00 simplex.chat/contact M7. When the text is scaled up The same screen with the system text size at 130 per cent, which iOS and Android both apply to web content and which a lot of people run. A 24 px mark beside a 17 px word needs 96 px and the row only has 100, so the three ways to pay stop being a row and become a stack, and the Pay button drops the method it can no longer fit. Measured, not guessed: at 130 per cent the row fails on every phone up to 412 px. Get the code opens badges.simplex.chat with no parameters - the site asks everything the code is pasted into A2 the only thing that crosses from the site back to the app Design document. The app and the site are each one tree read left to right, and the third band is the same site at phone width. In a tree: a screen is always to the left of everything it leads to, each lane is labelled for what its screens have in common, and the cause of a branch is written above the screen it produces, in the colour of the line that reaches it. App screens are drawn in the iOS idiom; Android and desktop differ only in chrome. The site is drawn in its own idiom - one centred column at max-width 560, accent #0053D0 from website/tailwind.config.js - and is deliberately not a copy of the app's. Badge artwork is the shipped art, verbatim from MR/images/badge_{supporter,legend}.svg, and the hero on A1 and B1 is phone-supporter.png from simplex-chat-art, the same asset PhoneSupporterHero draws on that screen today. D2 asks for the logo alone, and the tier cards carrying the badges are the one deliberate deviation. The Bitcoin and Monero marks are the official ones from simple-icons (CC0-1.0) in their registered brand colours; the card glyph is Lucide's credit-card (ISC). Built today: the service schema, the pricing catalog, the ledger, code derivation and classification, credential signing, the RPC dispatcher and the operator CLI. Design, not yet built: every screen above, the web listener and its endpoints, both payment providers, and the client redeem path. Store purchase is removed on iOS and Play for the duration of this plan, and the app asks no purchase questions at all: the tier, the duration and the method are the site's, so G1, G4 and G5 lose most of their scope and APIGetBadgeCatalog loses its only client-side caller.
\ No newline at end of file