Skip to content

feat(payments): the registry entry carries the tenant payments link, finished (E7a) - #233

Merged
mahmutkaya merged 2 commits into
developfrom
feature/express-payments-link-registry-field
Sep 5, 2026
Merged

mahmutkaya merged 2 commits into
developfrom
feature/express-payments-link-registry-field

Conversation

@mahmutkaya

Copy link
Copy Markdown
Contributor

Summary

  • The sofra half of payments_link_url: — the registry entry now carries the tenant's own onboarding page as one finished, opaque URL, emitted beside stripe_account:.
  • This is the field provision-tenant.sh will copy into Stripe:PaymentsLinkUrl, which is what the restaurant's own Payments tab links to (frontend #726 / backend #493 already ship the reader).
  • The deploy-repo half is not in this PR, by agreement.

Issue / plan link

Type

  • feat

The shape, and why it is one opaque field

The obvious alternative was to put the raw token in the registry and let provision-tenant.sh concatenate a base URL. This does it here instead:

  • The deploy repo is the thing that syncs to boxes, so anything it concatenates it can get subtly wrong per environment — and a wrong onboarding origin is a link that works and points at the wrong site. The failure would look like success.
  • The box never learns that part of the string is a credential; it copies a value.
  • The origin can change (staging, a partner zone) without a deploy-repo change.

Base URL comes from siteUrl() (lib/email.ts) — the one seam every link we send already goes through, runtime NEXTAUTH_URL first — so staging cannot mint links that point at production.

No locale prefix, verified rather than assumed

The stored URL is https://…/onboarding/payments/<token> with no /en/. middleware.ts redirects an unprefixed path to the visitor's own language, so one stored URL serves a Swiss restaurant whose staff read French and whose owner reads German — and baking a locale in would choose for them permanently, in a value copied into a box .env.

Measured against a running next start, with a positive control:

GET /onboarding/payments/tok123                        -> 307  location: /en/onboarding/payments/tok123
GET /legal                       (positive control)    -> 307  location: /en/legal
GET /onboarding/… Accept-Language: fr-CH               -> 307  location: /fr/onboarding/payments/tok123
GET /onboarding/… Accept-Language: de-CH               -> 307  location: /de/onboarding/payments/tok123

Pairing, and the unknown-key question

  • payments_link_url: is emitted only alongside stripe_account:, never on its own: with no account there is no onboarding page to point at, and a button that 404s is worse than no button. Same discipline the module and the commission rate already follow.
  • provision-tenant.sh extracts registry fields from a fixed tuple (:71-79), so an unknown key is ignored rather than rejected — which is exactly how payments_commission_bps existed in entries before anything read it. I looked for a strict schema check in the deploy repo (git grep -nE "unknown key|additionalProperties|allowed_keys|schema" origin/develop) and found none; the 10 matches for schema are unrelated, which is the positive control that the grep works.
  • An entry that never asked for a link is byte-identical to what this generator emitted before the field existed — asserted in the tests, the same contract base_domain and partner_name carry.

For your deploy PR

# beside the existing STRIPE_* lines (~:806-818)
set_env_line STRIPE_PAYMENTS_LINK_URL "$REG_PAYMENTS_LINK_URL"

plus payments_link_url in the extraction tuple at :71-79. The value is already absolute and locale-free; nothing needs concatenating, and nothing needs to know what a token is.

NFR triage

  • D1 security: the token now also lands in tenants/registry.yml in the deploy repo — a private repo whose read access is the same set of people who can already read a box .env. Worth stating rather than leaving implicit; it is the same blast radius (one restaurant's onboarding) in one more place. The rotation story is in this slice's BACKLOG entry.
  • D9 testing: 4 new unit tests; two mutants below. Both link and pairing decisions are pure and in the coverage floor.
  • D10 conventions: the entry-generator keeps its "absence is the contract" property; the pure/impure split is unchanged.
  • Rest: n/a because no schema change, no user-facing string, no money arithmetic.

Test plan

  • typecheck · lint · test:coverage (1133) · build · check-single-file --all
  • Redirect behaviour verified live with a positive control (above)
  • Mutation, each LANDED (grep -F) and COMPILED (tsc 0) before any result was read:
    1. link emitted without requiring an account → 1 RED
    2. /en/ baked into the URL and the trailing-slash trim removed → 2 RED

Deploy notes

  • Migration one-off required: no. New env vars: none on the sofra side.
  • Rollout order: this is safe to roll before the deploy PR — an extra registry key is ignored by the current provision-tenant.sh.

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

@mahmutkaya
mahmutkaya merged commit 7558099 into develop Sep 5, 2026
18 checks passed
@mahmutkaya
mahmutkaya deleted the feature/express-payments-link-registry-field branch September 5, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant