- 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>
91 lines
3.8 KiB
Markdown
91 lines
3.8 KiB
Markdown
# Phase 6 Backend Contract — Server Cart + Checkout Session
|
||
|
||
Companion to [PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md](../PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md) Phase 6 (Sprints 6.1–6.2). Extends [Phase 1](PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md) §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
|
||
|
||
```ts
|
||
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 to `customerId`. Adding to a guest cart, then logging in, must merge into the customer's cart — not silently drop items.
|
||
- Inactive carts and their `StockReservation`s (Phase 3 §3) clear on `expiresAt`.
|
||
|
||
## 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](PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md#5-server-authoritative-checkout-amount):
|
||
|
||
```
|
||
POST /api/v2/storefront/checkout { cartId, currency, deliveryOptionId }
|
||
```
|
||
|
||
reads the server-owned `Cart`/`CartLine`s 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.status` can move to `confirmed`.
|
||
- Guest checkout allowed/disallowed per tenant policy (`MarketplaceFeatureSet`, see [Phase 9](PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md)).
|
||
|
||
## 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.
|