release: sell online-payments, with the provisioning landmine disarmed (#117 + #118) - #119
Merged
Merged
Conversation
`sellable: false` kept online-payments out of the public SignupConfigurator and the founder's ProvisionPicker. Its original reason (an unbuilt surface) expired when S11 merged; what held it to the end was SOFRA-PAYMENTS-PLAN §7.6 — Connect unconfirmed on the LIVE Stripe platform, where `POST /v1/accounts` is provisioning's first call and refuses outright without it. Confirmed enabled on acct_1TpwTDCHzplJfkIy, 2026-08-10. Removing the flag broke two things outside the one-line diff: - The strings did not exist. The module had never rendered, so `signup.configurator.module.online-payments` and its hint were missing from all six locales and /signup showed the raw keys. The parity check diffs the other five AGAINST en.json, so a key absent from all six reads as "in parity"; SignupConfigurator builds both keys by template literal, so TypeScript cannot see them either. A unit test now asserts every sellable module has a label and a hint in en.json — parity carries the other five. Verified it fails when the hint is deleted. - The marketing site contradicted the price list. faq.items.payments and compare.table.rows.payments.sofra both said online payments were "on the roadmap — today you charge at the counter", in six locales, while the configurator offered the feature at EUR 19. The comparison page ran that against GloriaFood's "US$29/month" — a competitive claim wrong in our own disfavour. Rewritten; the FAQ now also states the Connect KYC precondition, which nothing had told a customer. The hint says "local methods like TWINT or iDEAL" rather than TWINT alone: methods are chosen dynamically per connected account, and TWINT does not exist for a Dutch tenant. The bundle-exclusion test above is now vacuous (no module carries the flag) and says so — it is kept for the next module that arrives id-first. Verified on a dev server: /signup lists the module at EUR 19 and prices a selection at EUR 38, /en#faq and /en/compare/gloriafood render the new copy, and /ar/signup renders it RTL with no raw keys. Refs SOFRA-PAYMENTS-PLAN §7.6
…118) A tenant who buys `online-payments` generated a registry entry that `provision-tenant.sh` refuses: the guard requires the module and a `stripe_account:` together, and it exits 1 BEFORE the database, the compose project and the image. So the customer did not get a restaurant without card payment — they got no restaurant at all, and the founder learned from the auto-opened deploy issue while a paid customer waited. The module and the account now ship as a pair or not at all: - Founder path — `/admin/provision` gains an optional Stripe account field. The runbook (§2b) already has them create the connected account BEFORE proposing, precisely because of this guard, so they arrive holding the `acct_` and the entry carries both halves in one shot. - Self-serve path — the buyer has no account and cannot be given one (only the restaurant can create it, through Stripe's hosted onboarding, which cannot be pre-filled). Their module is withheld, they go live on everything else, and the PR body names what was bought, why it is absent, and the exact two-field follow-up PR. `deferred` is returned from `openProvisioningPr` and recorded on both audit entries: a prose section in one PR is not a record anyone can query later, and a deferral means a customer is being billed for a module their tenant does not yet have. `buildProvisioningPrBody` moves to `lib/provisioning-pr-body.ts` — the pair outgrew the 200 LOC limit — and is added to the coverage `include` so the move does not drop already-covered code out of the floor's scope. The guard's own shell condition is evaluated by a real bash in the unit suite against both shapes, so the test pins the generator to the deploy repo's text rather than to a paraphrase of it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Release — the module goes on sale, and the thing that would have broken it is fixed
Two commits, and they must ship together:
sellable: falsefromonline-payments— it appears on the signup configurator and the founder's provision picker#117 alone re-arms a landmine. Before #118,
buildTenantRegistryEntryemittedmodulesverbatim and never emittedstripe_account, so a self-serve buyer ofonline-paymentsgenerated exactly the entryprovision-tenant.sh:94refuses:That
exit 1lands before the database, the compose project and the image — so the customer did not get a restaurant lacking card payment, they got no restaurant at all, while the founder found out from an auto-opened deploy issue. It stayed un-fired only because #117 was ondevelopand this site servesmain. Merging #117 without #118 would fire it on the first sale.After #118 the module and the account ship as a pair or not at all: the founder supplies the
acct_on/admin/provision(runbook §2b creates it first) and provisions in one shot; a self-serve buyer has none and cannot be given one, so the module is withheld, the tenant goes live on everything else, and the generated PR body states the exact two-field follow-up.deferredis recorded on both audit entries, so "who is owed a Stripe follow-up PR" is answerable later.What actually changes for a visitor
The
online-paymentsoption becomes selectable on the public configurator and in the provision picker, at €19/mo. Nothing else — no schema change, no migration, no billing change.What this does NOT unblock
Selling the module and being able to onboard a tenant onto Stripe are separate gates, and the second is still shut.
POST /v1/accountson the live platform returns400 "You must complete your platform profile to use Connect and create live connected accounts"— a dashboard questionnaire no API key of any scope gets past (SOFRA-PAYMENTS-PLAN §7.12, measured 2026-08-11). Until that is answered, anacct_cannot be created for anyone. #118 is what makes that a delay rather than a dead provision: a buyer still gets their restaurant.Verification after merge
Merging builds the image; rollout is manual and this release contains no schema change, so no migrate one-off is needed:
docker compose -f docker-compose.prod.yml pull sofra && docker compose -f docker-compose.prod.yml up -d sofraNote the box is currently two releases behind —
/api/healthreportsc3b6403(2026-07-31) whilemainis5977657(2026-08-10) — so this rollout also lands #116.After rolling:
/api/healthreports the new sha (notc3b6403)/admin/provisionshows the new optional Stripe account field🤖 Generated with Claude Code