WIP — merchant profile CRUD endpoints + tier-cap wire-up (part 4)
Backend is now feature-complete for :52. Admin UI still has to consume
these endpoints (part 5) but every operation the UI needs has a
working API surface behind it.
api/merchant_profiles.rs (new module)
Axum handlers wrapping the merchant_profiles::* business-logic helpers
and the rail-preference repo helpers. Each endpoint writes an audit
entry so the operator can see every profile/rail-preference change
in the audit log.
GET /v1/admin/merchant-profiles list + summarize
POST /v1/admin/merchant-profiles create (tier-gated)
GET /v1/admin/merchant-profiles/:id detail + providers + rail prefs + counts
PATCH /v1/admin/merchant-profiles/:id partial update
DELETE /v1/admin/merchant-profiles/:id refuses if attached
POST /v1/admin/merchant-profiles/:id/set-default transactional flip
PUT /v1/admin/merchant-profiles/:id/rail-preferences/:rail validates + persists
DELETE /v1/admin/merchant-profiles/:id/rail-preferences/:rail clears the override
set_rail_preference validates THREE things before persisting: rail
name is one of lightning/onchain/card; the provider exists; the
provider is attached to THIS profile; AND it serves this rail. So
the operator can't pin "Card" to a BTCPay row, and can't pin a
provider that belongs to a different profile.
list/get redact SMTP password (smtp_configured: bool is enough for
the UI to render "configured/not configured" status; the actual
password stays write-only). The edit form submits a new password
only when the operator explicitly rotates it.
api/tier.rs
New enforce_merchant_profile_cap helper. Refuses with HTTP 402
AppError::PaymentRequired when a Creator-tier operator already has
one profile (the default) and the self-license lacks the new
`unlimited_merchant_profiles` entitlement. Same shape as the
existing enforce_product_cap / enforce_policy_cap helpers — the
admin UI's existing tier-cap modal renders the upgrade CTA from
the upgrade_url field.
Note: master Keysat's Pro and Patron policies need
`unlimited_merchant_profiles` added to their entitlement JSON as a
separate admin action on the master keysat.xyz instance — purely
data, no code change. Master operator self-license must be re-
issued (or naturally renewed) to pick up the new entitlement.
merchant_profiles.rs
create() now calls enforce_merchant_profile_cap before INSERT.
Replaces the TODO comment from part 1.
api/mod.rs
Registers the merchant_profiles module and wires the routes above.
Build: cargo check passes. Two warnings remaining — both expected:
- recover.rs unused-import (pre-existing, unrelated)
- SETTING_ACTIVE_PROVIDER inside the shim's own pre-migration
fallback branch
Backend status: every multi-provider story (purchase routing,
subscription snapshot, webhook delivery, connect/disconnect, profile
CRUD, tier gating) is now wired to the new schema. Only the admin UI
+ a version bump remain.
What's left for :52:
- Admin UI in web/index.html — Merchant Profiles section, product
picker, buy-page brand block + rail picker. Roughly 600-1000 lines
of HTML/CSS/JS consuming the new endpoints. Largest single
remaining piece.
- Version bump to :52 + release notes flagging the one-way migration
+ the post-migration manual Zaprite-webhook-URL update.
- End-to-end sandbox test against two profiles + two Zaprite orgs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,7 @@ pub mod tier;
|
||||
pub mod validate;
|
||||
pub mod community;
|
||||
pub mod db_info;
|
||||
pub mod merchant_profiles;
|
||||
pub mod payment_provider;
|
||||
pub mod rates_admin;
|
||||
pub mod recover;
|
||||
@@ -395,11 +396,31 @@ pub fn router(state: AppState) -> Router {
|
||||
// model providers attach to profiles and products pick a profile
|
||||
// at resolution time; there's no singleton "active" preference to
|
||||
// flip. Multi-profile operators should use the new
|
||||
// /v1/admin/merchant-profiles endpoints instead.
|
||||
// /v1/admin/merchant-profiles endpoints below.
|
||||
.route(
|
||||
"/v1/admin/payment-provider/status",
|
||||
get(payment_provider::status),
|
||||
)
|
||||
// Merchant profile CRUD + rail preferences.
|
||||
.route(
|
||||
"/v1/admin/merchant-profiles",
|
||||
get(merchant_profiles::list).post(merchant_profiles::create),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/merchant-profiles/:id",
|
||||
get(merchant_profiles::get)
|
||||
.patch(merchant_profiles::update)
|
||||
.delete(merchant_profiles::delete),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/merchant-profiles/:id/set-default",
|
||||
post(merchant_profiles::set_default),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/merchant-profiles/:id/rail-preferences/:rail",
|
||||
axum::routing::put(merchant_profiles::set_rail_preference)
|
||||
.delete(merchant_profiles::clear_rail_preference),
|
||||
)
|
||||
// Zaprite webhook landing — operator points Zaprite's
|
||||
// webhook setting at this URL. Same handler as
|
||||
// /v1/btcpay/webhook because the underlying validate_webhook
|
||||
|
||||
Reference in New Issue
Block a user