Files
marketplaces/docs/backend/PHASE-4-CONNECTOR-FRAMEWORK-CONTRACT.md
sdarbinyan c91b75c036 docs: backend contracts for Phases 2-4 (orders/notifications, catalog/offer, connectors)
Continues the Phase 1 contract doc with the same wire-contract-only style.
All three build on Sprint 0.1's answered decisions - no further business
input needed to start implementation once backend ownership is confirmed:

- Phase 2: canonical Order/OrderLine/Fulfillment/OrderEvent per the unified
  multi-seller decision (one Order, per-seller Fulfillment groups), event
  bus, Notification Center contract.
- Phase 3: Product/Offer split, InventoryRecord, publish-time executability
  validation (the mechanism behind "no branch may distinguish an inspector
  from a normal buyer").
- Phase 4: generic config-driven connector framework per the "no fixed
  marketplace list" decision - onboarding a new partner is configuration
  against a fixed pipeline, not a bespoke integration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:02:52 +04:00

5.6 KiB
Raw Blame History

Phase 4 Backend Contract — External Order Connector Framework

Companion to PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md Phase 4 (Sprint 4.1, generic framework; Sprint 4.2 retired as "per named marketplace"). Depends on Phase 3 (Offer/sellerSku must exist to map onto) and Phase 2 (Order canonical model).

Status: ready to build, generic by design. Sprint 0.1 decision (2026-08-17): no fixed marketplace list — "our new ones, partners, new, etc." This contract specifies a config-driven framework, not a per-provider integration. Zero of this exists in the codebase today (reconcil*, idempot*, hostinger all return 0 hits).


1. Design principle

A new partner connector is an onboarding action against this framework, not a code change. Auth type, field mapping, and rate limits are configuration; the pipeline (ingest → normalize → map → idempotency-check → create/update order → notify) is fixed and shared across every connector.

2. Entities

interface Connector {
  id: string;
  marketplaceId: string;
  provider: string;          // free-text label, e.g. "ozon", "wildberries" - not an enum, new values need no code change
  authType: 'webhook_signed' | 'api_key' | 'oauth2';
  credentialRef: string;     // pointer into secret storage, never the secret itself
  pollingIntervalSeconds?: number; // set only when the provider has no webhook
  cursorState?: string;      // opaque, connector-specific pagination/since cursor
  status: 'active' | 'paused' | 'error';
}

interface RawExternalEvent {
  id: string;
  connectorId: string;
  payload: unknown;          // stored verbatim, before any parsing - the traceability anchor
  receivedAt: string;
  processedAt?: string;
}

interface ExternalOrderMapping {
  connectorId: string;
  externalSellerId: string;
  externalProductId: string;
  externalSku: string;
  internalSellerId: string;
  internalOfferId: string;   // references Phase 3's Offer
}

interface DeadLetter {
  id: string;
  connectorId: string;
  rawEventId: string;
  reason: string;
  retryCount: number;
  lastAttemptAt: string;
  resolvedAt?: string;
}

3. Pipeline (fixed, shared across every connector)

1. Connector receives webhook, or polling finds a new event via cursorState.
2. Signature/auth verified. Idempotency key = connectorId + externalOrderId/eventId.
3. Payload persisted as RawExternalEvent BEFORE any parsing.
4. Normalizer maps payload -> canonical ExternalOrderEvent shape (fixed schema, see §4).
5. SKU mapping resolves externalSku -> internal Offer via ExternalOrderMapping.
   No mapping found -> event goes to the Unmatched queue (§5), does NOT fail silently.
6. Order created/updated via the Phase 2 Order API, source: 'external', externalOrderRef set.
7. external_order.imported and order.created events emitted (Phase 2 event bus).
8. Fulfillment/status changes pushed back to the external marketplace if its API supports it.

4. Canonical external order event (what the normalizer produces)

interface ExternalOrderEvent {
  connectorId: string;
  externalOrderId: string;
  externalCreatedAt: string;
  customer: { name?: string; contact?: string };
  lines: Array<{ externalSku: string; qty: number; unitPriceMinor: number; currency: string }>;
  totalMinor: number;
  currency: string;
  rawEventId: string; // traceability back to §2
}

Every provider's adapter is responsible only for producing this shape from its own payload — everything downstream (§3 steps 58) is provider-agnostic.

5. Unmatched queue + retry

GET  /api/admin/v2/integrations/{connectorId}/unmatched
POST /api/admin/v2/integrations/{connectorId}/unmatched/{eventId}/resolve  { internalOfferId }
POST /api/admin/v2/integrations/{connectorId}/dead-letter/{id}/replay

Retry policy: exponential backoff, capped attempts, then DeadLetter with manual replay from backoffice. No connector is allowed to silently drop an event.

6. Connector-agnostic SLA (applies to every provider, per plan §5.2)

  • Webhook source: 99% of valid events processed in under 60 seconds.
  • Polling source: delay no worse than pollingIntervalSeconds + 60.
  • Zero duplicate orders on repeated event delivery (guaranteed by the idempotency key in §3 step 2).
  • Every connector error carries a trace id, visible in backoffice.

7. Endpoints

POST /api/providers/v1/{connector}/webhook   -- generic entrypoint, connector resolved by path + auth
GET  /api/admin/v2/integrations              -- list all connectors + health (last success, lag, errors, backlog)
POST /api/admin/v2/integrations              -- onboard a new connector: { provider, authType, credentialRef, marketplaceId }
PATCH /api/admin/v2/integrations/{id}        -- pause/resume, update mapping config

8. Onboarding a new partner (replaces the old "one sprint per named marketplace")

  1. Register credentials in secret storage, scoped to marketplace/seller.
  2. POST /api/admin/v2/integrations with the provider's auth type and mapping config.
  3. Write the provider-specific adapter (payload → §4 canonical shape) — the only genuinely bespoke piece per partner.
  4. Verify in sandbox against the fixed pipeline (§3) — nothing else changes.

9. What the frontend will start doing once this ships

  • Build the backoffice Integrations section (missing from admin nav today): connector list, health (last success/lag/errors/backlog/unmatched), FX sources, messaging providers.
  • Trace-id surfacing on connector errors.
  • Unmatched-queue resolution UI.