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

124 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 4 Backend Contract — External Order Connector Framework
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../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](PHASE-3-CATALOG-OFFER-FULFILLMENT-CONTRACT.md) (`Offer`/`sellerSku` must exist to map onto) and [Phase 2](PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md) (`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
```ts
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)
```ts
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.