Some checks failed
Architecture Governance / architecture (push) Has been cancelled
A partner integration request landed for programmatic merchant-hierarchy management (Company/Project/Store/PaymentPoint). Built the answer generically: partner-specific behaviour is a PartnerProfile config row, and no partner name appears in any entity, field, endpoint or status value. New: - docs/backend/PARTNER-PROVISIONING-API-CONTRACT.md - hierarchy, idempotency, node-scoped public-key credentials, TEST/LIVE partition, routing context - docs/context/adrs/ADR-0003-generic-partner-provisioning-api.md Amended, because the schema impact must land before Phase 1 is implemented: - Phase 1 gains RoutingContext on CheckoutSession/PaymentIntent/Payment, frozen at checkout-session creation and immutable after - Phase 7 gains routing on Refund/ReconciliationRecord, plus the rule that seller settlement splits happen after routing, never as a hierarchy level - Phase 9 gains Company/Project above Marketplace and PaymentPoint below it, with a backfill sequence for existing marketplaces - Track S gains partner credentials: public key only, node-scoped authority, rotation with overlap, immediate revoke, audit coverage Also: Track P (P1-P10) in the delivery plan, and backend ownership closed as answered across the contract set. Card payment was checked, not added - qr and card both already ship in cart.component.ts with separate create paths and status pollers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
197 lines
7.9 KiB
Markdown
197 lines
7.9 KiB
Markdown
# Phase 9 Backend Contract — Tenant Registry, Domain Automation, Publish Model
|
||
|
||
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 9 (Sprints 9.1–9.3). Covers plan §4.3, §8.
|
||
|
||
**Status: ready to build.** Zero `hostinger` references exist in the codebase today.
|
||
|
||
---
|
||
|
||
## 1. Entities
|
||
|
||
Added 2026-08-18: two levels now sit **above** `Marketplace`, introduced by [PARTNER-PROVISIONING-API-CONTRACT.md §10](PARTNER-PROVISIONING-API-CONTRACT.md).
|
||
|
||
```ts
|
||
interface Company {
|
||
id: string;
|
||
name: string;
|
||
externalReference?: string; // partner's own id, when provisioned via the partner API
|
||
status: 'active' | 'suspended' | 'disabled';
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
interface Project {
|
||
id: string;
|
||
companyId: string;
|
||
name: string; // a product line, e.g. "marketplaces"
|
||
externalReference?: string;
|
||
status: 'active' | 'suspended' | 'disabled';
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
```
|
||
|
||
Both are deliberately thin — they exist to scope ownership, credentials, and payment routing, not to hold configuration. All marketplace configuration stays on `Marketplace` below.
|
||
|
||
A `Marketplace` **is** the partner hierarchy's `store` level. One project holds many marketplaces; one marketplace holds many sellers (Phase 5), and sellers are not part of that hierarchy.
|
||
|
||
```ts
|
||
interface Marketplace {
|
||
id: string;
|
||
companyId: string; // added 2026-08-18
|
||
projectId: string; // added 2026-08-18
|
||
externalReference?: string; // added 2026-08-18, partner's own id for this store
|
||
name: string;
|
||
code: string;
|
||
type: 'commerce' | 'mall_directory' | 'hybrid' | 'single_brand';
|
||
ownerId: string;
|
||
countries: string[];
|
||
locales: string[];
|
||
currencies: string[];
|
||
timezone: string;
|
||
lifecycleState: MarketplaceLifecycleState;
|
||
}
|
||
|
||
type MarketplaceLifecycleState =
|
||
| 'draft' | 'configured' | 'content_ready' | 'domains_planned'
|
||
| 'staging_live' | 'qa_passed' | 'production_ready' | 'live' | 'paused' | 'archived';
|
||
|
||
interface MarketplaceDomain {
|
||
marketplaceId: string;
|
||
domain: string;
|
||
type: 'production' | 'www' | 'staging' | 'preview' | 'api' | 'seller';
|
||
status: 'planned' | 'dns_pending' | 'ssl_pending' | 'active' | 'failed';
|
||
}
|
||
|
||
interface MarketplaceFeatureSet {
|
||
marketplaceId: string;
|
||
features: Record<string, boolean>; // e.g. { catalog: true, sellers: true, cart: true, checkout: true, payments: true, orders: true, refunds: true, directory: false, ... }
|
||
}
|
||
|
||
interface MarketplaceRevision {
|
||
id: string;
|
||
marketplaceId: string;
|
||
status: 'draft' | 'validated' | 'preview' | 'published';
|
||
publishedAt?: string;
|
||
supersedesRevisionId?: string; // rollback creates a NEW revision, never mutates the old one
|
||
}
|
||
```
|
||
|
||
**Hard invariant:** `Order`, `Payment`, `InventoryRecord`, and every financial ledger row are **not part of a `MarketplaceRevision`**. Rolling back a storefront design revision must never touch commerce data.
|
||
|
||
### 1.1 PaymentPoint
|
||
|
||
Added 2026-08-18. The leaf of the partner hierarchy: one payment method accepted at one marketplace. A marketplace taking both QR and card has two payment points.
|
||
|
||
```ts
|
||
interface PaymentPoint {
|
||
id: string;
|
||
marketplaceId: string;
|
||
method: 'qr' | 'card'; // extensible; both ship today
|
||
currencies: string[]; // ISO 4217 subset this channel accepts
|
||
externalReference?: string;
|
||
status: 'active' | 'suspended' | 'disabled';
|
||
providerAccountRef?: string; // set only by financial enablement, never by provisioning
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
```
|
||
|
||
- Creating a payment point registers the channel. It does **not** enable real money — that requires `providerAccountRef`, set through a separate approved flow.
|
||
- A payment point is what `RoutingContext.leafNodeId` points at (Phase 1 §6.5).
|
||
- `MarketplaceFeatureSet.features.payments` gates whether the marketplace may have enabled payment points at all; the payment point gates which method.
|
||
|
||
### 1.2 Backfill
|
||
|
||
Existing marketplaces predate `Company` and `Project`. Migration, in this order:
|
||
|
||
```
|
||
1. Create one Company for the current owning entity.
|
||
2. Create one Project ("marketplaces") under it.
|
||
3. Set companyId + projectId on every existing Marketplace.
|
||
4. Create PaymentPoints for the methods each marketplace already accepts (qr, card).
|
||
5. Make companyId and projectId non-nullable only after 3 completes.
|
||
```
|
||
|
||
`externalReference` stays null for backfilled rows — it is only meaningful for partner-provisioned nodes.
|
||
|
||
## 2. Lifecycle state machine
|
||
|
||
```
|
||
draft -> configured -> content_ready -> domains_planned -> staging_live -> qa_passed -> production_ready -> live -> paused/archived
|
||
```
|
||
|
||
Every state transition endpoint must return the specific blocker preventing the next transition — not just "not ready."
|
||
|
||
```
|
||
GET /api/admin/v2/marketplaces/{id}/lifecycle -> { currentState, nextState, blockers: string[] }
|
||
POST /api/admin/v2/marketplaces/{id}/lifecycle/advance
|
||
```
|
||
|
||
## 3. Onboarding wizard (8 steps, plan §4.3)
|
||
|
||
```
|
||
POST /api/admin/v2/marketplaces -- step 1: name/code/type/owner/countries/locales/currencies/timezone
|
||
PATCH /api/admin/v2/marketplaces/{id}/feature-set -- step 2
|
||
POST /api/admin/v2/marketplaces/{id}/domains -- step 3
|
||
PATCH /api/admin/v2/marketplaces/{id}/design -- step 4
|
||
POST /api/admin/v2/marketplaces/{id}/roles -- step 5
|
||
PATCH /api/admin/v2/marketplaces/{id}/integrations -- step 6
|
||
POST /api/admin/v2/marketplaces/{id}/staging-launch -- step 7, runs smoke tests
|
||
POST /api/admin/v2/marketplaces/{id}/production-launch -- step 8, requires all P0 blockers closed + explicit approval
|
||
```
|
||
|
||
## 4. Domain automation (Hostinger API, per plan §8.2)
|
||
|
||
```
|
||
GET /api/dns/v1/zones/{domain}
|
||
POST /api/dns/v1/zones/{domain}/validate
|
||
PUT /api/dns/v1/zones/{domain}
|
||
DELETE /api/dns/v1/zones/{domain}
|
||
GET /api/dns/v1/snapshots/{domain}
|
||
GET /api/dns/v1/snapshots/{domain}/{snapshotId}
|
||
POST /api/dns/v1/snapshots/{domain}/{snapshotId}/restore
|
||
```
|
||
|
||
Process, strictly in this order:
|
||
```
|
||
1. Read current DNS zone.
|
||
2. Save a snapshot (rollback payload) BEFORE any change.
|
||
3. Build and validate a DNS plan.
|
||
4. NEVER touch MX/SPF/DKIM/DMARC/CAA records without a separate, explicitly scoped task.
|
||
5. Apply records only after production approval.
|
||
6. Verify propagation, SSL issuance, and health checks.
|
||
7. Mark the domain 'active' only after all checks in step 6 pass.
|
||
```
|
||
|
||
## 5. Publish model
|
||
|
||
```
|
||
draft -> validation -> preview -> publish
|
||
```
|
||
|
||
```
|
||
POST /api/admin/v2/marketplaces/{id}/revisions -- create draft
|
||
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/validate
|
||
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/publish -- becomes immutable
|
||
POST /api/admin/v2/marketplaces/{id}/revisions/{revId}/rollback -- creates a NEW revision pointing at the prior published content
|
||
```
|
||
|
||
Replaces the current builder's `localStorage`-only draft persistence and the empty `apiEndpoints.builder: {}` placeholder in bootstrap. CMS/static-page content (currently in-memory bootstrap only) gets a real write path through this same revision model.
|
||
|
||
## 6. Tenant resolution hardening
|
||
|
||
```
|
||
GET /api/v2/storefront/bootstrap -- resolved server-side from verified Host header
|
||
```
|
||
|
||
- Host is normalized and matched against `MarketplaceDomain` server-side — the marketplace ID from the browser is never a trust boundary.
|
||
- Unknown Host → `404`, with **no fallback to any other tenant**.
|
||
|
||
## 7. What the frontend will start doing once this ships
|
||
|
||
- Build the backoffice **Marketplaces** section (missing from admin nav today): registry, type, status, domains, currencies, feature set, responsible manager.
|
||
- Build the **Domains & Releases** section: DNS/SSL status, staging/production, health checks, rollback.
|
||
- Wire the project editor/builder to real revision persistence instead of `localStorage`.
|
||
- Marketplace dashboard: GMV, paid orders, conversion, payment failure rate, moderation queue, low stock, unmatched events, integration health, domain/SSL/release status (plan §4.2).
|