Files
marketplaces/docs/backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md
sdarbinyan 71da5a8d80
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
docs: partner provisioning API contract, routing context, Track P
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>
2026-08-18 11:22:24 +04:00

7.9 KiB
Raw Blame History

Phase 9 Backend Contract — Tenant Registry, Domain Automation, Publish Model

Companion to PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md Phase 9 (Sprints 9.19.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.

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.

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.

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).