- Phase 5: Seller Portal from scratch (zero backend bytes exist today) - SellerOrganization/SellerUser/SellerMarketplaceMembership, all endpoints scoped server-side to the unified-orders Fulfillment model from Phase 2. - Phase 6: server-owned Cart/CartLine/CheckoutSession, extending Phase 1's server-authoritative-amount contract into the cart itself. Replaces localStorage/Telegram-CloudStorage cart persistence. - Phase 7: Refund and ReconciliationRecord entities, settlement contract. Flags additional payment providers (wallets/BNPL) as still an open business decision - not blocking, schema is provider-agnostic already. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.8 KiB
Phase 6 Backend Contract — Server Cart + Checkout Session
Companion to PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md Phase 6 (Sprints 6.1–6.2). Extends Phase 1 §5 (server-authoritative checkout amount) into a full server-owned cart.
Status: ready to build — payment chain unfrozen per Sprint 0.1.
1. Why this exists
Cart today is localStorage + Telegram CloudStorage — no backend cart exists at all. features/website/checkout/ is an empty directory; checkout lives entirely inside a 751-line cart popup component. Phase 1 §5 already specifies the server-authoritative amount at checkout time; this phase makes the cart itself server-owned, from add-to-cart onward.
2. Entities
interface Cart {
id: string;
marketplaceId: string;
customerId?: string; // set for authenticated customers
sessionToken?: string; // set for guest carts
createdAt: string;
expiresAt: string; // TTL for inactive carts
}
interface CartLine {
id: string;
cartId: string;
offerId: string; // never a client-supplied price - see Phase 1 §5
qty: number;
addedAt: string;
}
interface CheckoutSession {
id: string;
cartId: string;
customerContact: { email?: string; phone?: string; verified: boolean };
deliveryOptionId: string;
status: 'open' | 'confirmed' | 'expired';
createdAt: string;
expiresAt: string;
}
interface DeliveryOption {
id: string;
marketplaceId: string;
label: string;
price: Money;
type: 'pickup' | 'courier' | 'digital';
}
3. Cart endpoints
POST /api/v2/storefront/cart/lines { offerId, qty }
PATCH /api/v2/storefront/cart/lines/{lineId} { qty }
DELETE /api/v2/storefront/cart/lines/{lineId}
GET /api/v2/storefront/cart
Invariants:
- Idempotent add/update/remove.
- Quantity validated against
Offer/InventoryRecord(Phase 3) on every mutation, not just at checkout. - Guest cart identified by
sessionToken(cookie or header); authenticated cart bound tocustomerId. Adding to a guest cart, then logging in, must merge into the customer's cart — not silently drop items. - Inactive carts and their
StockReservations (Phase 3 §3) clear onexpiresAt.
4. Price-refresh rule
If an offer's price changed since it was added to the cart, GET /api/v2/storefront/cart returns both the line's captured price and the current price, with a priceChanged: boolean flag. The frontend must show this and require explicit confirmation before checkout proceeds if the total moved — this is a UX requirement on the frontend, but the backend must expose the comparison, not silently use whichever price it prefers.
5. Checkout session
Builds directly on Phase 1 §5.2:
POST /api/v2/storefront/checkout { cartId, currency, deliveryOptionId }
reads the server-owned Cart/CartLines directly (no client-supplied offer list needed anymore, unlike the Phase 1 doc's example which pre-dates the server cart). Response shape unchanged from Phase 1 §5.2.
Additional checkout-time validation beyond Phase 1:
- Contact requirement enforced per tenant policy: email and/or phone must be present and (if the tenant requires it) verified before
CheckoutSession.statuscan move toconfirmed. - Guest checkout allowed/disallowed per tenant policy (
MarketplaceFeatureSet, see Phase 9).
6. What the frontend will start doing once this ships
- Build the
features/website/checkout/module for real — currently an empty directory. - Retire
localStorage/Telegram-CloudStorage cart persistence. - Show the price-refresh confirmation UI described in §4.
- Delete the client-side offer/qty tracking currently duplicated inside the cart popup component.