# Purchasing Journey: Cart → Order Confirmation

## Scope

Covers the full path the brief names — Cart, Mini Cart, Checkout, Guest Checkout, Customer Checkout, Address Book, Shipping, Payment, Review Order, Order Confirmation, Order Tracking, Post-Purchase — plus Upsells, Cross-sells, Frequently Bought Together, Review requests, Wishlist reminders, and Marketing automation hooks. Apple/Nike are used the way `10` used Apple's PDP: as a *feel* reference (calm, low-friction, confident), not a layout to clone.

Most of the checkout mechanics already existed and work correctly — this phase's job was to audit what was real, close the two genuine gaps (no mini cart, a bare-bones confirmation page), and connect the confidence/retention touches the brief asks for using data this app already has, rather than fabricating analytics it doesn't.

## What already existed and needed no rework

- **Checkout flow** (`pages/checkout/Checkout.tsx`) — a 4-step Contact → Shipping → Review → Payment flow. Guest checkout is the *only* path (there's no separate "customer checkout" mode); an authenticated customer gets the same steps pre-filled from their profile and address book, which is the honest way to satisfy "Customer Checkout" without maintaining two parallel flows.
- **Address Book** — `getMyAddresses()` feeds a "Use a saved address" selector on the Shipping step; picking one calls the existing `applyAddress()` fill. Guests without an account simply don't see the selector and type an address directly.
- **Shipping** — real rate lookup (`getShippingRates(country)`), scoped per destination country, with stale-selection cleanup if a re-entered country invalidates the previously chosen rate.
- **Payment** — real gateway integration (Paystack, client-side charge + server-side reference verification) with a test-token fallback when no gateway is configured; zero-total orders (fully covered by a gift card) skip payment collection entirely rather than asking to "pay $0."
- **Review Order** — subtotal/discount/shipping/tax/gift-card/total breakdown from a server-computed quote (`quoteCheckout`), not recomputed client-side, so what's shown is what gets charged.
- **Order Tracking** (`pages/TrackOrder.tsx`) — email + order-number lookup against `OrdersService::lookupOrderForCustomer`, a deliberate pair (not a public by-ID endpoint) so one order can't be used to enumerate another customer's history.

None of the above needed new backend logic; this phase's checkout-side changes are additive UI on top of it.

## What this phase actually built

**Backend — `CommerceCoreService::checkout()` enrichment.** The order-placement response discarded `OrdersService::placeOrder()`'s own return value (`orderId`, `orderNumber`) and never carried the priced line items back either, even though both were already computed in-request. The confirmation page needs a human-readable order number and an itemized receipt; rather than add a new "fetch my order" lookup (which would need to be either public-by-ID — an enumeration risk — or re-authenticate a guest who has no session), the existing checkout response now simply carries `orderNumber` and `lineItems` home. Whoever holds that one response is inherently the browser that just placed the order, so this is safe in a way a general lookup endpoint wouldn't be.

**Mini Cart** (`components/MiniCart.tsx`) — a dropdown anchored under the header's cart icon: line items with thumbnail/name/variant/qty/price, a remove action per row, subtotal, and Checkout/View cart actions. Closes on outside click. This is the one genuinely missing piece for "minimize friction" — previously the only way to see the cart was a full page navigation.

**Order Confirmation rebuild** (`pages/OrderConfirmation.tsx`) — previously just an order number and a total. Now: success state, order number + email confirmation, an itemized line-items summary (from the enrichment above), a trust-badge row, a "Track your order" link that deep-links straight into `/track-order?email=…&orderNumber=…` (see below), a wishlist-reminder nudge, and a cross-sell rail.

**Order Tracking auto-lookup** — `TrackOrderPage` now reads `email`/`orderNumber` from the URL query string and runs the lookup automatically on mount if both are present, so the confirmation page's tracking link lands on a filled-in result rather than an empty form asking the customer to retype what they just saw.

**Checkout form redesign, pass one** — the 4-step flow's raw `<input>`/`<label>` pairs became `TextField`/`SelectField` (`components/Input.tsx`), matching the design system used everywhere else on the storefront.

**Checkout layout redesign, pass two** — a second pass replaced the plain numbered step list and single-column form with:

- A horizontal timeline stepper: circular nodes connected by a line, a checkmark for completed steps, the current step accent-filled. Completed steps are clickable (jump back to re-check something already entered); the current and future steps aren't (Payment specifically needs Review's server quote first, so "jump ahead" is never a safe click, not just an unstyled one).
- A two-column layout (`~70/30`) — the step form on the left, a sticky **Receipt** panel (`components/Receipt.tsx`) on the right styled like a printed paper receipt (monospace type, dashed rule lines, a torn-edge bottom via a CSS gradient trick) that updates live as the customer progresses: it shows cart contents and a running subtotal from Contact onward, and gains real Shipping/Tax/Discount/Gift-card rows the moment Review's quote resolves — rows for amounts not yet known simply don't render, rather than showing a placeholder dash.
- The `TrustBadges` row (tried under the stepper in pass one) was removed from checkout entirely — redundant next to a running receipt that already demonstrates the order is being computed correctly, and it's still shown once, more meaningfully, on Order Confirmation below.
- `RecentlyViewedRail` now sits below the whole checkout flow, on every step, matching its placement on Cart/PDP.

**Order Confirmation receipt** — the confirmation page's itemized summary is now the *same* `Receipt` component, with `status="paid"` (a rotated "PAID" stamp) and the full subtotal/shipping/tax/discount/gift-card breakdown (the checkout response was extended to carry these — they were already computed server-side in `CommerceCoreService::checkout()`'s `$b` breakdown and simply not returned before now, same zero-extra-query enrichment pattern as the `orderNumber`/`lineItems` addition above). The rest of the confirmation page (trust badges, tracking link, wishlist nudge, cross-sell rail) is unchanged from pass one.

## Upsells, cross-sells, and frequently bought together

`10` already drew the line between **Frequently Bought Together** (companion-item add-on, shown on the PDP) and **Related products / "You might also like"** (`RelatedProducts`/`ProductRail`). This phase reuses that same honest distinction rather than inventing a third flavor:

- **Cart** (`pages/Cart.tsx`) — a "Frequently bought together" rail keyed off the first cart line's product (`getRelatedProducts`), with anything already in the cart filtered out client-side so it never recommends what's already being bought.
- **Order Confirmation** — a "You might also like" rail (`listProducts({})`), intentionally the broader, unfiltered discovery rail rather than a same-category one, since post-purchase is a better moment for "something new" than "more of the same."

There's still no purchase-pattern analytics (order-history ML) behind either — same honest caveat `10` already documented for FBT — so "frequently bought together" here means *merchandising-relevant*, not *statistically observed*.

## Review requests and Wishlist reminders

- **Wishlist reminders** — Cart (both empty and populated states) and Order Confirmation each show a "you have N items saved" nudge when the signed-in customer's wishlist (`getMyWishlist`) is non-empty. Guests see nothing, since there's no wishlist without an account.
- **Review requests** — already exist as server-side automation (the Notifications/Automation modules' `workflow.review_request` send, wired in an earlier phase) and are unaffected by this frontend pass; they're out of scope here because they're a post-delivery, async email trigger, not a page in this journey.

## Marketing automation hooks

Genuinely already wired, not new to this phase: abandoned-cart recovery (`CartRecoveryService`) and the automation/notification send log both run server-side today, independent of anything built in this pass. This phase's only touch point is that a completed checkout still correctly clears the cart (`clear()`) and marks the order placed, which is what lets those existing automations distinguish "abandoned" from "converted." No new automation logic was added — the brief's "hooks" already existed; this phase just made sure the frontend doesn't fight them (e.g. no longer wiping `orderPlaced` state in a way that would re-trigger the empty-cart guard).

## Calm vs. fast

The tension the brief names — Apple's calm vs. Nike's speed — resolves the same way `10` resolved it for the PDP: reduce visible steps where the visible step wasn't earning its keep (a persistent trust row instead of re-litigating "is this secure?" per page, a mini cart instead of a full navigation just to glance at contents, a pre-filled tracking lookup instead of a second form), and add real information density only where it increases confidence at the moment it's needed (the itemized review breakdown, the itemized confirmation receipt). Nothing here trades correctness for speed — every total shown is still the server-quoted one, never recomputed client-side.
