Offers Architecture

Data flow and lifecycle of the offers system

Data Flow

Buyer submits offer (authenticated)

sequenceDiagram participant Browser participant ZLinkPage as ZLinkPage (zooly-app2) participant API as zooly-app API participant Auth as Auth (cookie) participant DB as Postgres Browser->>ZLinkPage: Visit /z/:slug (must be logged in) ZLinkPage->>API: POST /api/offers/submit (credentials: include) API->>Auth: resolveAccountId(cookie) Auth-->>API: buyerAccountId API->>DB: getAccountBySlug(slug) DB-->>API: sellerAccount API->>DB: createOffer with buyerAccountId + sellerAccountId DB-->>API: offer API-->>ZLinkPage: offerId

Unauthenticated users visiting a Z-link are redirected to login by App.tsx (root-level auth check). After login, returnTo brings them back to the Z-link page.

Seller lists offers (authenticated)

sequenceDiagram participant Browser participant AgentSidebar as AgentSidebar (zooly-app2) participant API as zooly-app API participant Auth as Auth (cookie) participant DB as Postgres Browser->>AgentSidebar: Navigate to /agent/z AgentSidebar->>API: GET /api/offers/list (credentials: include) API->>Auth: resolveAccountId(cookie) Auth-->>API: accountId API->>DB: listOffersBySellerAccount(accountId) DB-->>API: offers[] API-->>AgentSidebar: { offers }

Seller responds to offer (authenticated)

sequenceDiagram participant Browser participant AgentDetailPage as AgentDetailPage participant API as zooly-app API participant DB as Postgres Browser->>AgentDetailPage: Click Accept / Reject / Counter AgentDetailPage->>API: POST /api/offers/respond { offerId, action, ... } API->>API: resolveAccountId, getOfferById API->>API: Validate offer.sellerAccountId === accountId API->>DB: updateOfferStatus(id, status, counterData?) DB-->>API: updated offer API-->>AgentDetailPage: { offer }

Status Lifecycle

stateDiagram-v2 [*] --> PENDING: Buyer submits PENDING --> ACCEPTED: Seller accepts PENDING --> REJECTED: Seller rejects PENDING --> COUNTERED: Seller counters (counterAmountCent, counterNote) COUNTERED --> ACCEPTED: Buyer accepts counter COUNTERED --> REJECTED: Buyer rejects counter ACCEPTED --> [*] REJECTED --> [*]
  • PENDING: New offer, awaiting seller response
  • COUNTERED: Seller proposed different terms; awaiting buyer response (buyer flow not yet implemented)
  • ACCEPTED: Deal agreed
  • REJECTED: Offer declined

CORS

All offer endpoints use getCorsHeaders(origin) based on ALLOWED_DOMAINS_CORS. The zooly-app2 origin must be in that env var for cross-origin requests to succeed.