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 webone 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 siteplans/2026-08-21-badges-web-checkout.mdeditable SVGIn the appiOS idiom shown; Android and desktop differ only in chromeFor the operatorthe CLI behind the codesGetting a badge, and what you getThe store builds, and when it goes wrongSupport SimpleXSimpleX is built by people who believe privatemessaging should not depend on advertising.A badge helps pay for it.iWhy SimpleX is built this wayGet the codeRedeem the codeCodes are bought in a browser. Nothing ischarged in the app.A1. Where it startsTwo actions, and no wizard behind either. The app does not ask whichlevel or how many months: the site asks that, and asking twice wouldmean two catalogs, two price renderers and two places for them todisagree. 'Get the code' opens badges.simplex.chat with noparameters, so the site starts at its own first screen.On iOS and Play there is nothing to link out to.Support SimpleXSimpleX is built by people who believe privatemessaging should not depend on advertising.A badge helps pay for it.iWhy SimpleX is built this wayRedeem the codeno purchase button on this buildA1b. On iOS and PlaySame screen, one action. A button that opens a browser to buy adigital good is exactly what Apple and Google reject, so the storebuilds do not offer one and say nothing about where a code comesfrom. Until store evidence is verified, a code bought elsewhere isthe only route to a badge on these platforms - which is the acceptedcost of decision 6, not an oversight.Redeem the code‹SupportRedeem codePaste the code from your receipt.SXB-9K2M4-7QRT1-XZ5WPasteRedeemFormats as you type and folds I and L to 1,O to 0 - the same normalisation the servicedoes, so a code read off a screen cannot failon ambiguity alone.A2. RedeemingTwenty Crockford characters in five groups. The check character isverified before anything is sent, so a mistyped code never reachesthe service and never costs a throttle token.a valid code‹SettingsSupporter perksLegendshown on your profileENDS24 August 2027Prepaid months have no billing date. The badgeis reissued each month from the balance youalready paid for, and ends when it runs out.Add more monthsA3. After redeeming'Ends', never 'renews' - nothing recurs and the copy must not implyit does. The date is the paid-through date from the ledger balance,not the credential's expiry, which is a week-aligned internal theuser never sees.on the profile‹ChatsAliceAAliceLegendlast seen recentlyIN GROUPSAAliceLegendBBobSupporterCCarolThe badge travels with the profile, so it showswherever a profile does - in chats, in a memberlist, on a contact card.A4. How everyone else sees itThe point of the whole flow. The badge is a signed credentialcarried in the profile and verified by the recipient's own clientagainst the issuer key it already ships - not a flag the serverasserts, and not something a client can set for itself.Any error the service returns: code_invalid, code_used, code_expiredor rate_limited.‹SupportRedeem codeThis code isn't validcode_invalidThis code has already been usedcode_usedThis code has expiredcode_expiredToo many attempts. Try again in 4 minutes.rate_limited - retryAfter 240Could not verify the credentialinternalOne message per error the service can return.Anything else, including a credential that failsto verify locally, shows the service's own textrather than a blank screen.A2b. Every redemption errorA revoked code is deliberately indistinguishable from an unknown one- both say 'isn't valid', so a guesser learns nothing from arevocation. Support tells them apart with codes status; the wiredoes not.The last paid month has been issued and the balance is zero.‹SettingsSupporter perksLegendended 24 August 2027Your badge has endedThe months you paid for have all been issued.The badge stopped showing on your profile;nothing was cancelled and nothing is owed.Add more monthsA3b. The balance runs outAn exhausted balance is not an error: the service returns nocredential and a zero-balance statement, and the app says soplainly. 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-24SXB-9K2M4-7QRT1-XZ5WB-3NHD8SXB-2W7XP-4LMQ8-9DKR3-6TVZ5SXB-8HYNC-1FGJ6-5PBW2-7QSXD3 codes issued. Printed once; only hashes stored.OP1. Minting codesCompensation and promotional codes have no orderbehind them, so they are random rather thanderived. Printed once to stdout and never again -the database holds only SHA-256 hashes, which iswhat makes a stolen copy useless.support$simplex-badge-service codes status \--ref K7M2Qorder 8f3a...c21e paid 24 Augbadge legend, 12 monthscode redeemed 2 Sep--reveal refused: code already redeemedOP2. Resolving a referenceThe customer quotes the five-character referencefrom their receipt - never the order id, never thecode. --reveal is refused once a code is redeemedor revoked, on the same reasoning that stops theweb page disclosing it.On the webbadges.simplex.chat - one centred column, max-width 560, the logo the only imageBuying, start to finishThe other ways it endsRefused at checkoutbadges.simplex.chatSupport SimpleXSimpleX has no ads, no user accounts and nothing to sell.A supporter badge helps pay for the people who build it.Choose your levelAlready bought a code?Redeem it in the app: Settings, Supporter perks.The badge shows on your profile. Nothing renews byitself, and no account is created.simplex.chat/contactB1. The landing pageNot in the plan, and now load-bearing. D3 starts the site at the tier questionon the assumption that the app has already asked it; the app no longer asksanything, 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 levelbadges.simplex.chat/#/tierChoose your levelBigger files, and longer for people to collect them.Supporter$7 / month2 GB files7 days storageLegend$70 / month5 GB files21 days storageContinuesimplex.chat/contactB2. Question one: the levelThe wizard proper starts here. One centred column at max-width 560, generouswhitespace, accent #0053D0 - deliberately not the app's chrome, because it isnot the app. One question per screen, and a tier with no active price rendersdisabled rather than hidden.Continuebadges.simplex.chat/#/monthsHow long?Prepaid months. Nothing renews by itself.1 month$703 months$140save 33%12 months$420save 50%Continuesimplex.chat/contactB3. How longEvery figure comes from GET /api/catalog, the same totals the RPC catalog givesthe app. The browser formats minor units and computes the comparison only - itnever multiplies a price by a month count.Continuebadges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00 with MoneroCard is handled by Stripe. Bitcoin and Monero areon-chain, through BTCPay.simplex.chat/contactB4. Check your order, and payTwo screens in the plan, one here. The summary and the method are the samedecision - what am I buying and how do I hand over the money - and splittingthem made the buyer confirm a choice they had not made yet. POST /api/checkoutstill carries priceId, offerId and method and nothing else: badge type andmonths are derived server-side from the ids, so a tampered request cannot buy aLegend badge at a Supporter price. Nothing is prefilled: with the app's wizardgone there is nothing to prefill from, so D5's ?tier= and ?months= parametershave no producer in this milestone and every buyer walks B1 to B4. The threemarks are a choice, not three actions: D7 has one Pay button, it carries thetotal, and it is the last thing anyone presses - so the amount is on screen atthe moment of paying and the site keeps the choose-then-continue rhythm of B2and B3.Pay with Monerobadges.simplex.chat/?order=8f3a...c21eSend 1.482 XMR$420.00 - this rate is held for 58:12MONERO ADDRESS48HqK2...9fRtWcCopyWaiting for the payment to confirmREFERENCEK7M2QBookmark this page - the address and the countdownboth live on this URL.simplex.chat/contactB5. Paying in cryptoPolls GET /api/order every 2s for the first minute then every 10s, and stopswhile the tab is hidden. A partial payment stays 'pending' - partials are thenormal first event of a multi-transaction payment, not a failure.it confirmsbadges.simplex.chat/?order=8f3a...c21e✓Paid. Here is your code.SXB-9K2M4-7QRT1-XZ5WB-3NHD8Copy codescan to carry itto your phoneREDEEM IT IN THE APPSettings -> Supporter perks -> Redeem codeThe code is shown here only.This page works until 23 September, andstops the moment the code is redeemed.The link is the code - treat it so.simplex.chat/contactB6. The code, onceDerived from the order id rather than stored, so a reload recomputes it whileonly its hash is ever at rest. Disclosure stops at redemption, at revocation, or30 days after settlement - whichever comes first.Redeemed, revoked, or thirty days after settlement.badges.simplex.chat/?order=8f3a...c21eThis code has been redeemedThe code is no longer shown hereIt was redeemed on 2 September. If that was not you,get in touch.Legend, 12 monthspaid 24 AugustThe order stays; the capability does not.simplex.chat/contactB6b. The page outlives the codeDisclosure ends at redemption, at revocation, or 30 days after settlement. Theorder is still here because support resolves a reference against it - but theURL stops being equivalent to the code, which is the whole point.The provider's section is missing from badge_service.ini.badges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00Monero is temporarily unavailableTry another method, or come back later.PAY WITHBitcoinMonerounavailableCardPay $420.00 with Bitcoinsimplex.chat/contactB4b. A provider is not configured503 provider_unavailable from /api/checkout. The method is shown and disabledrather than omitted, so an operator who forgot a section sees it immediatelyinstead of wondering why nobody pays in Monero.Pay by card - Stripe returns herebadges.simplex.chat/?order=51c7...9d02Payment receivedWaiting for the card network to confirm.Still processingThis usually takes a few seconds. The page updates itself.If nothing happens for 15 minutes, we stop waitingand show you what to do next.simplex.chat/contactB5b. Coming back from the card flowStripe's success_url returns here with ?order=. The return is NOT proof ofpayment - only the webhook settles an order, so this screen polls rather thancelebrating. 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...c21eThis invoice expired0.734 XMR arrived, which is not the full amountThe rate window has closed, so the shortfall is no longermeaningful. Quote the reference below and we will sort it out.REFERENCEK7M2QStart a new invoiceNever shows the code, and never the ?order= URL.simplex.chat/contactB5c. Expired, and underpaidThe one state that needs a human. An expired-but-paid invoice still settleslater - late on-chain settlement is routine and moves the order to paid - but anunderpaid one stops here with the reference support resolves by.The price was disabled while the buyer was deciding.badges.simplex.chat/#/checkoutThese prices have changedStart again with the current pricesThe badge you chose was repriced while you were deciding.Nothing was charged.Start againprice_disabled / offer_disabled / offer_mismatchsimplex.chat/contactB4c. The catalog moved underneathPrices are checked at checkout and only there. A deprecated price is stillhonoured for someone mid-flow; a disabled one is refused. Repricing appends anew price rather than editing the old, so this is rare but reachable.Five checkout requests inside one minute, from one IP.badges.simplex.chat/#/checkoutToo many attemptsTry again in 46 secondsThe Pay button is disabled until then.Pay $420.00 with Monero429, with Retry-After. The polling loop backs off tothat value rather than treating it as a failure.simplex.chat/contactB4d. Rate limitedFive checkout requests a minute per IP, because each one reaches a paymentprovider. Order polling gets sixty. The redemption path has no IP at all - itruns over SimpleX RPC and is throttled per signer instead.On the web, on a phonethe same site at 390 px - what stays in columns, and what has to stackThe same six screens at 390 pxbadges.simplex.chatSupport SimpleXNo ads, no accounts, nothing to sell.A badge pays the people who build it.Choose your levelAlready bought a code?Redeem it in the app:Settings, Supporter perks.simplex.chat/contactM1. Nothing to reflowThe landing page was already a single column ofone thing after another, so the phone gets thesame page with a smaller hero. This is what themax-width 560 rule buys: on a phone the columnsimply becomes the viewport. The lede is shorterthan the desktop's, because 320 px leaves 280 pxof column and a lede that wraps to three lines ona phone is a lede that is too long.Choose your levelbadges.simplex.chat/#/tierChoose your levelBigger files, and longer to collect them.Supporter$7 / mo2 GB files7 days storageLegend$70 / mo5 GB files21 days storageContinuesimplex.chat/contactM2. Two tiers still fit side by side169 px each at 390, and 134 at 320. Two things abuyer is comparing must stay comparable: stackingthem would put the second below the fold and turna comparison into a scroll. The badge art shrinks;nothing else changes.Continuebadges.simplex.chat/#/monthsHow long?Prepaid months. Nothing renews.1 month$703 months$140save 33%12 months$420save 50%Continuesimplex.chat/contactM3. Three durations still fit across110 px each at 390, 87 at 320. They are threevalues of one number, so they belong on one linewhere the prices can be read against each other;the saving pill sits under its price rather thanbeside it.Continuebadges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00 with MoneroCard by Stripe. Bitcoin and Moneroon-chain, through BTCPay.simplex.chat/contactM4. The widest thing on the siteThe summary and the three ways to pay are thetightest fit in the document: 110 px per button at390 and 87 at 320, which still holds a 24 px markabove a 47 px word. This is the screen D2's 320 pxcheck is really about, and the reason the markscarry no text beside them.Pay with Monerobadges.simplex.chat/?order=8f3a...c21eSend 1.482 XMR$420.00 - rate held for 58:12MONERO ADDRESS48HqK2...9fRtWcCopyWaiting for the paymentREFERENCEK7M2Qsimplex.chat/contactM5. Here the columns have to stackThe QR and the payment details cannot sit side byside at this width, so they stack - and the QRgoes first, because a phone paying from a walletapp on the same device needs the address, while aphone being scanned by another device needs thecode. Both are one thumb apart.it confirmsbadges.simplex.chat/?order=8f3a...c21e✓Paid. Here is your code.SXB-9K2M4-7QRT1-XZ5WB-3NHD8Copy codescan to carry it to another deviceREDEEM IT IN THE APPSettings -> Supporter perks -> Redeem codeThe code is shown here only.This page works until 23 September, andstops the moment the code is redeemed.simplex.chat/contactM6. The code stays the heroEverything stacks, in the order it is needed: thecode, the button that copies it, the QR thatcarries it to another device, then where to putit. The warning stays last and stays red - it isthe only place the code is shown, and on a phoneit is the easiest to lose. Its body is thetightest text in the band at 320 px, with 16 px ofmargin, which is why that copy is two short linesrather than the desktop's three. Two things heremust be allowed to wrap rather than set on oneline: the code, at its hyphens, and the redeempath, at its arrows. Measured, both overflow a 320px column already at the default text size.The reader has set a larger text size, so the rowno longer fits.badges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00simplex.chat/contactM7. When the text is scaled upThe same screen with the system text size at 130per cent, which iOS and Android both apply to webcontent and which a lot of people run. A 24 pxmark beside a 17 px word needs 96 px and the rowonly has 100, so the three ways to pay stop beinga row and become a stack, and the Pay button dropsthe method it can no longer fit. Measured, notguessed: at 130 per cent the row fails on everyphone up to 412 px.Get the codeopens badges.simplex.chat with no parameters - the site asks everythingthe code is pasted into A2the only thing that crosses from the site back to the appDesign 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 webone 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 siteplans/badges-codes/2026-08-21-badges-web-checkout.mdeditable SVGIn the appiOS idiom shown; Android and desktop differ only in chromeFor the operatorthe CLI behind the codesGetting a badge, and what you getThe store builds, and when it goes wrongSupport SimpleXSimpleX is built by people who believe privatemessaging should not depend on advertising.A badge helps pay for it.iWhy SimpleX is built this wayGet the codeRedeem the codeCodes are bought in a browser. Nothing ischarged in the app.A1. Where it startsTwo actions, and no wizard behind either. The app does not ask whichlevel or how many months: the site asks that, and asking twice wouldmean two catalogs, two price renderers and two places for them todisagree. 'Get the code' opens badges.simplex.chat with noparameters, so the site starts at its own first screen.On iOS and Play there is nothing to link out to.Support SimpleXSimpleX is built by people who believe privatemessaging should not depend on advertising.A badge helps pay for it.iWhy SimpleX is built this wayRedeem the codeno purchase button on this buildA1b. On iOS and PlaySame screen, one action. A button that opens a browser to buy adigital good is exactly what Apple and Google reject, so the storebuilds do not offer one and say nothing about where a code comesfrom. Until store evidence is verified, a code bought elsewhere isthe only route to a badge on these platforms - which is the acceptedcost of decision 6, not an oversight.Redeem the code‹SupportRedeem codePaste the code from your receipt.SXB-9K2M4-7QRT1-XZ5WPasteRedeemFormats as you type and folds I and L to 1,O to 0 - the same normalisation the servicedoes, so a code read off a screen cannot failon ambiguity alone.A2. RedeemingTwenty Crockford characters in five groups. The check character isverified before anything is sent, so a mistyped code never reachesthe service and never costs a throttle token.a valid code‹SettingsSupporter perksLegendshown on your profileENDS24 August 2027Prepaid months have no billing date. The badgeis reissued each month from the balance youalready paid for, and ends when it runs out.Add more monthsA3. After redeeming'Ends', never 'renews' - nothing recurs and the copy must not implyit does. The date is the paid-through date from the ledger balance,not the credential's expiry, which is a week-aligned internal theuser never sees.on the profile‹ChatsAliceAAliceLegendlast seen recentlyIN GROUPSAAliceLegendBBobSupporterCCarolThe badge travels with the profile, so it showswherever a profile does - in chats, in a memberlist, on a contact card.A4. How everyone else sees itThe point of the whole flow. The badge is a signed credentialcarried in the profile and verified by the recipient's own clientagainst the issuer key it already ships - not a flag the serverasserts, and not something a client can set for itself.Any error the service returns: code_invalid, code_used, code_expiredor rate_limited.‹SupportRedeem codeThis code isn't validcode_invalidThis code has already been usedcode_usedThis code has expiredcode_expiredToo many attempts. Try again in 4 minutes.rate_limited - retryAfter 240Could not verify the credentialinternalOne message per error the service can return.Anything else, including a credential that failsto verify locally, shows the service's own textrather than a blank screen.A2b. Every redemption errorA revoked code is deliberately indistinguishable from an unknown one- both say 'isn't valid', so a guesser learns nothing from arevocation. Support tells them apart with codes status; the wiredoes not.The last paid month has been issued and the balance is zero.‹SettingsSupporter perksLegendended 24 August 2027Your badge has endedThe months you paid for have all been issued.The badge stopped showing on your profile;nothing was cancelled and nothing is owed.Add more monthsA3b. The balance runs outAn exhausted balance is not an error: the service returns nocredential and a zero-balance statement, and the app says soplainly. 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-24SXB-9K2M4-7QRT1-XZ5WB-3NHD8SXB-2W7XP-4LMQ8-9DKR3-6TVZ5SXB-8HYNC-1FGJ6-5PBW2-7QSXD3 codes issued. Printed once; only hashes stored.OP1. Minting codesCompensation and promotional codes have no orderbehind them, so they are random rather thanderived. Printed once to stdout and never again -the database holds only SHA-256 hashes, which iswhat makes a stolen copy useless.support$simplex-badge-service codes status \--ref K7M2Qorder 8f3a...c21e paid 24 Augbadge legend, 12 monthscode redeemed 2 Sep--reveal refused: code already redeemedOP2. Resolving a referenceThe customer quotes the five-character referencefrom their receipt - never the order id, never thecode. --reveal is refused once a code is redeemedor revoked, on the same reasoning that stops theweb page disclosing it.On the webbadges.simplex.chat - one centred column, max-width 560, the logo the only imageBuying, start to finishThe other ways it endsRefused at checkoutbadges.simplex.chatSupport SimpleXSimpleX has no ads, no user accounts and nothing to sell.A supporter badge helps pay for the people who build it.Choose your levelAlready bought a code?Redeem it in the app: Settings, Supporter perks.The badge shows on your profile. Nothing renews byitself, and no account is created.simplex.chat/contactB1. The landing pageNot in the plan, and now load-bearing. D3 starts the site at the tier questionon the assumption that the app has already asked it; the app no longer asksanything, 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 levelbadges.simplex.chat/#/tierChoose your levelBigger files, and longer for people to collect them.Supporter$7 / month2 GB files7 days storageLegend$70 / month5 GB files21 days storageContinuesimplex.chat/contactB2. Question one: the levelThe wizard proper starts here. One centred column at max-width 560, generouswhitespace, accent #0053D0 - deliberately not the app's chrome, because it isnot the app. One question per screen, and a tier with no active price rendersdisabled rather than hidden.Continuebadges.simplex.chat/#/monthsHow long?Prepaid months. Nothing renews by itself.1 month$703 months$140save 33%12 months$420save 50%Continuesimplex.chat/contactB3. How longEvery figure comes from GET /api/catalog, the same totals the RPC catalog givesthe app. The browser formats minor units and computes the comparison only - itnever multiplies a price by a month count.Continuebadges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00 with MoneroCard is handled by Stripe. Bitcoin and Monero areon-chain, through BTCPay.simplex.chat/contactB4. Check your order, and payTwo screens in the plan, one here. The summary and the method are the samedecision - what am I buying and how do I hand over the money - and splittingthem made the buyer confirm a choice they had not made yet. POST /api/checkoutstill carries priceId, offerId and method and nothing else: badge type andmonths are derived server-side from the ids, so a tampered request cannot buy aLegend badge at a Supporter price. Nothing is prefilled: with the app's wizardgone there is nothing to prefill from, so D5's ?tier= and ?months= parametershave no producer in this milestone and every buyer walks B1 to B4. The threemarks are a choice, not three actions: D7 has one Pay button, it carries thetotal, and it is the last thing anyone presses - so the amount is on screen atthe moment of paying and the site keeps the choose-then-continue rhythm of B2and B3.Pay with Monerobadges.simplex.chat/?order=8f3a...c21eSend 1.482 XMR$420.00 - this rate is held for 58:12MONERO ADDRESS48HqK2...9fRtWcCopyWaiting for the payment to confirmREFERENCEK7M2QBookmark this page - the address and the countdownboth live on this URL.simplex.chat/contactB5. Paying in cryptoPolls GET /api/order every 2s for the first minute then every 10s, and stopswhile the tab is hidden. A partial payment stays 'pending' - partials are thenormal first event of a multi-transaction payment, not a failure.it confirmsbadges.simplex.chat/?order=8f3a...c21e✓Paid. Here is your code.SXB-9K2M4-7QRT1-XZ5WB-3NHD8Copy codescan to carry itto your phoneREDEEM IT IN THE APPSettings -> Supporter perks -> Redeem codeThe code is shown here only.This page works until 23 September, andstops the moment the code is redeemed.The link is the code - treat it so.simplex.chat/contactB6. The code, onceDerived from the order id rather than stored, so a reload recomputes it whileonly its hash is ever at rest. Disclosure stops at redemption, at revocation, or30 days after settlement - whichever comes first.Redeemed, revoked, or thirty days after settlement.badges.simplex.chat/?order=8f3a...c21eThis code has been redeemedThe code is no longer shown hereIt was redeemed on 2 September. If that was not you,get in touch.Legend, 12 monthspaid 24 AugustThe order stays; the capability does not.simplex.chat/contactB6b. The page outlives the codeDisclosure ends at redemption, at revocation, or 30 days after settlement. Theorder is still here because support resolves a reference against it - but theURL stops being equivalent to the code, which is the whole point.The provider's section is missing from badge_service.ini.badges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00Monero is temporarily unavailableTry another method, or come back later.PAY WITHBitcoinMonerounavailableCardPay $420.00 with Bitcoinsimplex.chat/contactB4b. A provider is not configured503 provider_unavailable from /api/checkout. The method is shown and disabledrather than omitted, so an operator who forgot a section sees it immediatelyinstead of wondering why nobody pays in Monero.Pay by card - Stripe returns herebadges.simplex.chat/?order=51c7...9d02Payment receivedWaiting for the card network to confirm.Still processingThis usually takes a few seconds. The page updates itself.If nothing happens for 15 minutes, we stop waitingand show you what to do next.simplex.chat/contactB5b. Coming back from the card flowStripe's success_url returns here with ?order=. The return is NOT proof ofpayment - only the webhook settles an order, so this screen polls rather thancelebrating. 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...c21eThis invoice expired0.734 XMR arrived, which is not the full amountThe rate window has closed, so the shortfall is no longermeaningful. Quote the reference below and we will sort it out.REFERENCEK7M2QStart a new invoiceNever shows the code, and never the ?order= URL.simplex.chat/contactB5c. Expired, and underpaidThe one state that needs a human. An expired-but-paid invoice still settleslater - late on-chain settlement is routine and moves the order to paid - but anunderpaid one stops here with the reference support resolves by.The price was disabled while the buyer was deciding.badges.simplex.chat/#/checkoutThese prices have changedStart again with the current pricesThe badge you chose was repriced while you were deciding.Nothing was charged.Start againprice_disabled / offer_disabled / offer_mismatchsimplex.chat/contactB4c. The catalog moved underneathPrices are checked at checkout and only there. A deprecated price is stillhonoured for someone mid-flow; a disabled one is refused. Repricing appends anew price rather than editing the old, so this is rare but reachable.Five checkout requests inside one minute, from one IP.badges.simplex.chat/#/checkoutToo many attemptsTry again in 46 secondsThe Pay button is disabled until then.Pay $420.00 with Monero429, with Retry-After. The polling loop backs off tothat value rather than treating it as a failure.simplex.chat/contactB4d. Rate limitedFive checkout requests a minute per IP, because each one reaches a paymentprovider. Order polling gets sixty. The redemption path has no IP at all - itruns over SimpleX RPC and is throttled per signer instead.On the web, on a phonethe same site at 390 px - what stays in columns, and what has to stackThe same six screens at 390 pxbadges.simplex.chatSupport SimpleXNo ads, no accounts, nothing to sell.A badge pays the people who build it.Choose your levelAlready bought a code?Redeem it in the app:Settings, Supporter perks.simplex.chat/contactM1. Nothing to reflowThe landing page was already a single column ofone thing after another, so the phone gets thesame page with a smaller hero. This is what themax-width 560 rule buys: on a phone the columnsimply becomes the viewport. The lede is shorterthan the desktop's, because 320 px leaves 280 pxof column and a lede that wraps to three lines ona phone is a lede that is too long.Choose your levelbadges.simplex.chat/#/tierChoose your levelBigger files, and longer to collect them.Supporter$7 / mo2 GB files7 days storageLegend$70 / mo5 GB files21 days storageContinuesimplex.chat/contactM2. Two tiers still fit side by side169 px each at 390, and 134 at 320. Two things abuyer is comparing must stay comparable: stackingthem would put the second below the fold and turna comparison into a scroll. The badge art shrinks;nothing else changes.Continuebadges.simplex.chat/#/monthsHow long?Prepaid months. Nothing renews.1 month$703 months$140save 33%12 months$420save 50%Continuesimplex.chat/contactM3. Three durations still fit across110 px each at 390, 87 at 320. They are threevalues of one number, so they belong on one linewhere the prices can be read against each other;the saving pill sits under its price rather thanbeside it.Continuebadges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00 with MoneroCard by Stripe. Bitcoin and Moneroon-chain, through BTCPay.simplex.chat/contactM4. The widest thing on the siteThe summary and the three ways to pay are thetightest fit in the document: 110 px per button at390 and 87 at 320, which still holds a 24 px markabove a 47 px word. This is the screen D2's 320 pxcheck is really about, and the reason the markscarry no text beside them.Pay with Monerobadges.simplex.chat/?order=8f3a...c21eSend 1.482 XMR$420.00 - rate held for 58:12MONERO ADDRESS48HqK2...9fRtWcCopyWaiting for the paymentREFERENCEK7M2Qsimplex.chat/contactM5. Here the columns have to stackThe QR and the payment details cannot sit side byside at this width, so they stack - and the QRgoes first, because a phone paying from a walletapp on the same device needs the address, while aphone being scanned by another device needs thecode. Both are one thumb apart.it confirmsbadges.simplex.chat/?order=8f3a...c21e✓Paid. Here is your code.SXB-9K2M4-7QRT1-XZ5WB-3NHD8Copy codescan to carry it to another deviceREDEEM IT IN THE APPSettings -> Supporter perks -> Redeem codeThe code is shown here only.This page works until 23 September, andstops the moment the code is redeemed.simplex.chat/contactM6. The code stays the heroEverything stacks, in the order it is needed: thecode, the button that copies it, the QR thatcarries it to another device, then where to putit. The warning stays last and stays red - it isthe only place the code is shown, and on a phoneit is the easiest to lose. Its body is thetightest text in the band at 320 px, with 16 px ofmargin, which is why that copy is two short linesrather than the desktop's three. Two things heremust be allowed to wrap rather than set on oneline: the code, at its hyphens, and the redeempath, at its arrows. Measured, both overflow a 320px column already at the default text size.The reader has set a larger text size, so the rowno longer fits.badges.simplex.chat/#/checkoutCheck your orderLevelLegendDuration12 monthsTotal$420.00PAY WITHBitcoinMoneroCardPay $420.00simplex.chat/contactM7. When the text is scaled upThe same screen with the system text size at 130per cent, which iOS and Android both apply to webcontent and which a lot of people run. A 24 pxmark beside a 17 px word needs 96 px and the rowonly has 100, so the three ways to pay stop beinga row and become a stack, and the Pay button dropsthe method it can no longer fit. Measured, notguessed: at 130 per cent the row fails on everyphone up to 412 px.Get the codeopens badges.simplex.chat with no parameters - the site asks everythingthe code is pasted into A2the only thing that crosses from the site back to the appDesign 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