From c5d3b3fca67f92a154db1241fb9ed2d5c49dd8c6 Mon Sep 17 00:00:00 2001 From: mahmutKaya <33642821+mahmutkaya@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:46:03 +0200 Subject: [PATCH] =?UTF-8?q?feat(catalog):=20add=20online-payments=20at=20?= =?UTF-8?q?=E2=82=AC19,=20not=20yet=20sellable=20(S10,=203/4)=20(#115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(catalog): add online-payments at €19, not yet sellable Slice S10 of SOFRA-PAYMENTS-PLAN — the control-plane third of a four-way lockstep (backend ModuleIds.cs, frontend modules.ts, deploy provision-tenant.sh). €19/mo, priced on value rather than on UI surface. The catalog's usual rule (staff surfaces 12, feature modules 9) reads it as a 9, but Sofra takes NO commission and the restaurant pays Stripe's fees — so this module is the entire revenue from enabling payments, and it is the one add-on whose value scales with the tenant's turnover while its price does not. GloriaFood charges $29 for the same capability. The support burden lands here too: chargebacks, KYC onboarding, a 2FA step in the runbook. NOT SELLABLE YET, and that is the load-bearing part of this commit. The vocabulary and the price list are the SAME array, so adding an id to make provisioning accept it also puts it on the public signup page — selling a module with no endpoint, no enforcement and no Stripe onboarding flow. A `sellable` flag now gates both purchase surfaces (SignupConfigurator and the founder's ProvisionPicker), with tests pinning that it is known-but-unsellable and that no bundle smuggles it in. Delete the flag in S9, when the flow works end to end. Co-Authored-By: Claude Opus 5 * chore(deps): patch the js-yaml and nanoid high CVEs Lockfile only. These fail OSV-Scanner, Trivy and `npm audit` on EVERY sofra PR, not just this one — they are pre-existing on develop and were blocking the merge of a change that does not touch dependencies. Regenerated with `npx npm@10`, not the local npm 11: npm 11 writes a lockfile node-22 CI rejects, and CI pins node 22. lockfileVersion stays 3, `npm ci` resolves clean, 261/261 tests and tsc green. Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- components/SignupConfigurator.tsx | 2 +- components/control/ProvisionPicker.tsx | 2 +- lib/module-catalog.ts | 21 ++++++++++++++++++ package-lock.json | 30 +++++++++++++------------- tests/unit/module-catalog.test.ts | 18 ++++++++++++++++ 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/components/SignupConfigurator.tsx b/components/SignupConfigurator.tsx index d44751a..1877163 100644 --- a/components/SignupConfigurator.tsx +++ b/components/SignupConfigurator.tsx @@ -35,7 +35,7 @@ export default function SignupConfigurator() { const saving = quote.aLaCarteCents - quote.monthlyCents; const optionalModules = MODULES.filter( - (m) => m.id !== "core" && m.id !== "extra-languages", + (m) => m.id !== "core" && m.id !== "extra-languages" && m.sellable !== false, ); return ( diff --git a/components/control/ProvisionPicker.tsx b/components/control/ProvisionPicker.tsx index ee7fdef..48e8aa5 100644 --- a/components/control/ProvisionPicker.tsx +++ b/components/control/ProvisionPicker.tsx @@ -76,7 +76,7 @@ export default function ProvisionPicker({ {labels.modulesCore} · {eur(MODULES.find((m) => m.id === "core")!.priceCents)}

- {MODULES.filter((m) => m.id !== "core" && m.id !== "extra-languages").map((m) => ( + {MODULES.filter((m) => m.id !== "core" && m.id !== "extra-languages" && m.sellable !== false).map((m) => (