8bf3d646ab
Final cut of the multi-merchant-profile work. Adds the Merchant Profiles
admin UI section (list/create/edit/delete profiles + per-profile Connect
BTCPay / Connect Zaprite), bumps the version, and writes the comprehensive
release notes flagging the one-way migration and the master-operator
post-migration manual step (update the Zaprite webhook URL to the new
path-keyed form, or click Disconnect + Reconnect in the new UI to have
Keysat re-register at the right URL automatically).
web/index.html
New sidebar nav entry + ROUTE_META + routes['merchant-profiles']:
- Lists every profile with: default badge, support email, brand
color preview, post-purchase redirect URL summary, attached
payment-providers table (kind / label / served rails / disconnect),
and Connect BTCPay / Connect Zaprite buttons for whichever kinds
aren't already attached.
- Set-default button on non-default profiles.
- Delete button on non-default profiles (the backend refuses if any
product or active subscription is still attached).
- Create modal: name, support URL, support email, post-purchase
redirect URL (with {invoice_id} substitution), brand color picker.
- Edit modal: same fields, populated from the profile row.
- Connect BTCPay opens the OAuth authorize URL in a new tab with the
merchant_profile_id baked into the CSRF state token (so the callback
knows which profile to attach the new provider row to).
- Connect Zaprite shows a small modal for the API key (+ optional
base_url for sandbox orgs); on success surfaces the new
provider-keyed webhook URL the operator pastes into Zaprite's
dashboard.
What this UI does NOT cover (deferred follow-ups, called out in the
release notes):
- Buy-page rail picker (defaults to first available rail today).
- Product-edit-page merchant-profile picker (new products always
attach to the default profile until the picker ships).
- Per-profile SMTP override form (the schema fields are in place,
consumed by the keysat-smtp-emails plan when it lands).
- Rail-preference editing UI (only matters when 2 providers on the
same profile both serve the same rail — settable today via
`PUT /v1/admin/merchant-profiles/:id/rail-preferences/:rail`).
startos/versions/v0.2.0.ts
Bumps to 0.2.0:52 with a comprehensive release note describing the
one-way migration, the post-migration manual Zaprite-webhook-URL step
for the master operator (you), the new tier-cap (unlimited_merchant_
profiles entitlement), and the four UI follow-ups deferred to later
releases.
Build: cargo check passes. Two warnings remaining — both expected:
- recover.rs unused-import (pre-existing, unrelated)
- SETTING_ACTIVE_PROVIDER inside the deprecated shim's own pre-
migration fallback branch
The shipped feature set:
- Migrations 0020 + 0021 + 0022 (one-way data port + invoice→provider
link + BTCPay-authorize-state profile column).
- Merchant profile + payment provider data model + repo helpers.
- Rail enum + served_rails() trait method + build_provider factory.
- AppState resolution layer (per-product, per-rail provider lookup
with explicit-preference → unique-candidate → deterministic-earliest-
connected fallback).
- Every backend call site (purchase, subscriptions, reconcile,
upgrade, tipping, capture, auto-charge, boot loader) ported.
- BTCPay + Zaprite connect/disconnect/status rewritten for the new
model (per-profile attachment + path-keyed webhook URLs).
- Webhook router with path-keyed deliveries + legacy back-compat.
- Thank-you page provider-kind copy reads the invoice's recorded
provider.
- Merchant profile CRUD + rail preference CRUD admin endpoints.
- Tier-cap wiring (enforce_merchant_profile_cap).
- Admin UI Merchant Profiles section (this commit).
- Comprehensive :52 release notes.
Master Keysat self-license note: the new `unlimited_merchant_profiles`
entitlement needs to be added to the Pro and Patron policies on the
master keysat.xyz admin UI for Pro/Patron customers to be able to
create multiple profiles. Pure data action, no code change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>