# Phase 8 Backend Contract — Customer Identity, VK ID, MAX/Telegram Messaging Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 8 (Sprints 8.1–8.5). Covers plan §2.9, §3.4, and all of §14 (the v3.1-only addition). **Status: ready to build. Sprint order fixed by Sprint 0.1 decision: VK ID first, then everything else** ("do all after vk"). Sequence below follows that: identity core → VK ID → email/phone OTP → MAX/Telegram → Notification Orchestrator. --- ## 1. Entities ```ts interface Customer { id: string; marketplaceId: string; // or global identity strategy, tenant-configurable name?: string; email?: string; phone?: string; status: 'active' | 'suspended'; createdAt: string; } interface ExternalIdentity { customerId: string; provider: 'vk_id' | 'telegram' | 'max'; providerUserId: string; verifiedAt: string; metadata: Record; lastUsedAt: string; } interface ContactMethod { customerId: string; type: 'email' | 'phone'; value: string; verifiedAt?: string; } interface ContactChannel { customerId: string; provider: 'telegram' | 'vk' | 'max'; chatId: string; verified: boolean; notificationsEnabled: boolean; deliveryEnabled: boolean; } interface MessagingConsent { customerId: string; channel: string; purpose: 'marketing' | 'order_service_messages'; grantedAt?: string; revokedAt?: string; } ``` Telegram is demoted from sole identity to one `ExternalIdentity` provider among several — it must remain fully functional, just no longer the only path. ## 2. Sprint 8.2 — VK ID (build first) ``` GET /api/identity/v1/vk/authorize -> redirects into VK's OAuth 2.1/PKCE flow POST /api/identity/v1/vk/callback { code, codeVerifier } -> completes OAuth **backend-side**, links ExternalIdentity, returns session ``` Invariants: - OAuth completion happens entirely backend-side; the VK client secret never reaches the frontend. - A repeat login for the same `providerUserId` must resolve to the same `Customer`, never create a duplicate. - If `providerUserId` is already linked to a *different* `Customer` than the one currently authenticated (or none), this is an identity conflict — route to controlled resolution, never silently overwrite the existing binding (plan §14.3). ## 3. Sprint 8.3 — Email/phone OTP (after VK ID) Implements the already-approved [email/phone login spec](../superpowers/specs/2026-08-15-email-phone-login-design.md). Per v3.1 §14, position this as **recovery/fallback** when a messenger channel is unavailable — not the primary login path. No new contract beyond that spec; this section exists only to fix its place in the build order relative to VK ID. ## 4. Sprint 8.4 — MAX + Telegram bot channels ```ts interface BotConversationBinding { customerId: string; marketplaceId: string; provider: 'telegram' | 'max'; chatId: string; state: string; // see §5 state machine orderId?: string; lastMessageAt: string; } ``` MAX linking flow (bot-assisted, one-time code): ``` POST /api/identity/v1/max/link-code -> { code, expiresAt } (TTL, single-use, bound to marketplace + browser session) ``` User opens the MAX bot, sends the code; a confirmed bot update on the backend calls: ``` POST /api/providers/v1/max/bot-webhook -- idempotent; a repeated update must not create a duplicate binding ``` which links the pending `Customer` session to the MAX `chatId`. All three providers' incoming bot updates (VK, MAX, Telegram) normalize into one shape: ```ts interface MessagingEvent { provider: 'telegram' | 'vk' | 'max'; chatId: string; orderId?: string; text?: string; receivedAt: string; } ``` Provider bot tokens/secrets never reach the frontend, ever — only the backend calls each provider's Bot API. ## 5. Sprint 8.5 — Notification Orchestrator + Delivery Conversation State Machine On `order.paid` (Phase 2 event bus), the orchestrator picks the customer's chosen channel (captured at checkout, see [Phase 6](PHASE-6-CART-CHECKOUT-CONTRACT.md) and `OrderContactSnapshot` in [Phase 2](PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md)) and drives: ``` not_started -> awaiting_customer -> details_received -> manager_assigned/auto_confirmed -> shipment_planned -> completed ``` ```ts interface DeliveryDetailsSnapshot { orderId: string; city?: string; address?: string; recipientName?: string; phone?: string; timeWindow?: string; comment?: string; receivedAt: string; } ``` Hard rules: - **The bot never changes financial statuses.** It can only write `DeliveryDetailsSnapshot` fields via a dedicated Delivery Service — no bot code path touches `Order.paymentStatus`/`orderStatus`. - The backoffice `Notification` (Phase 2 §6) fires unconditionally on `order.paid`, independent of whether the customer's messenger channel is reachable. - If the chosen channel is unavailable, log a `DeliveryAttempt` error (Phase 2 §6) and fall back per tenant-configured policy (e.g. email/SMS) — never block the order itself. - Follow-up messages are rate-limited per tenant policy; after the configured attempt limit, hand off to a human manager instead of continuing to message. ``` POST /api/providers/v1/{provider}/bot-webhook -- generic entrypoint for all three providers GET /api/admin/v2/orders/{orderId}/conversation -- message history + current state, for manager handoff POST /api/admin/v2/orders/{orderId}/conversation/handoff ``` ## 6. What the frontend will start doing once this ships - VK ID login button + OAuth redirect flow on storefront (primary social login). - MAX/Telegram linking UI (one-time code flow). - Checkout channel-choice step ("where should we send confirmation?") — VK / MAX / Telegram / email/SMS fallback. - Manager-facing conversation view (message history, current delivery state, accept handoff).