# PHASE 08: CART, CHECKOUT AND PAKISTANI ADDRESS HANDLING

Read `docs/03-UX-SPECIFICATION.md` sections 4 and 5, plus `docs/01-BUSINESS-REQUIREMENTS.md` section 4.1.

## Scope

Cart, one-page checkout, phone normalisation, the province and city problem. COD rules and verification come in phase 9, this phase gets the data captured correctly.

## Build

### Cart

Template overrides per the UX spec. Line items with image, variation, stepper, unit price, line total, remove with an undo link. Coupon field collapsed behind "Have a coupon code?". Free shipping progress line. Full-width Proceed to Checkout, repeated at the top with three or more items. Desktop: items left, sticky summary right.

**Free shipping threshold is computed on the subtotal after coupon discount and excluding shipping** (business rule 4.7). A coupon must not be stackable into free delivery.

### Checkout

One page, three cards: Contact, Delivery, Payment. Field order exactly as the table in UX spec section 5. Order summary collapsed at the top on mobile.

Do not use multi-step. Do not add fields that are not in the table.

### `BC_Phone`

- `normalise( $raw ): ?string` accepting `+92 3XX XXXXXXX`, `0092...`, `92...`, spaces, dashes and brackets, returning canonical `03XXXXXXXXX` or null
- `is_valid( $number ): bool` checking eleven digits, `03` prefix, and an operator prefix in the live set (030 to 034, plus 035)
- Rejects landlines as the COD contact number
- Stores both `_bc_customer_phone` and `_bc_customer_phone_raw`
- **Do not show a `+92` prefix chip in the UI.** Pakistani buyers type `03xxxxxxxxx`.

### `BC_Locations` and the city field

- `data/pk-locations.json`: seven provinces, each with 40 to 120 real cities. Build this list properly, it is load-bearing.
- Province is a native `select`. City is a combo input backed by a `datalist`, populated by a REST fetch of that province's list after the province is chosen. 2 to 6 KB per province, never the full list. This route is the third of the three deliberately public endpoints in `CLAUDE.md` section 6: named `permission_callback`, read-only, rate limited, and it serves a static file so it should be cacheable.
- **Free text is always accepted.** A non-matching entry sets `_bc_city_recognised` to false, which flags the order for verification in phase 9. Never block the order on an unrecognised city, that loses real orders from smaller towns.
- Area placeholder varies by city: Karachi "e.g. Gulshan-e-Iqbal Block 5", Islamabad "e.g. F-11/3", Lahore "e.g. DHA Phase 5", otherwise a generic hint.

### Validation

Validate on blur, never on keystroke, never before first blur. Inline errors with `aria-invalid` and `aria-describedby`. On failed submit, scroll to and focus the first invalid field and show one summary line. **Never permanently disable the submit button for invalid state**, only during the request.

Address minimum 12 characters, rejecting "same" and "as above". Use the exact error strings from the microcopy table, do not write new ones.

### Other

- Capture UTM parameters on landing into a cookie and write them to `_bc_order_source` at `woocommerce_checkout_create_order`. Without this, the field in the data model is dead and there is no way to tell which campaign produced an order.
- Shipping fee and delivery estimate read through `BC_Locations::tier_for_city()`, so the city tier drives both. Launch with one tier covering everywhere.
- Persist all field values in `sessionStorage` on blur
- Guest checkout only, no registration prompt anywhere
- Payment method as radio cards, COD first and preselected, with its sub-line
- Duplicate detection: same phone with an open order in the last 24 hours sets `_bc_duplicate_flag` and shows a soft warning, does not block
- Double-submit protection: the button disables on first click and the order is idempotent
- Thank-you page per UX spec section 6, with the "we will call you on 03xx" line and a WhatsApp button

## Acceptance criteria

- [ ] Every QA test in section C passes
- [ ] Every phone case in section D, D1 to D7, behaves exactly as the table says
- [ ] D8: the 240 character transliterated address stores and renders in full, in admin and in the email
- [ ] D9: an order from Kot Addu places successfully with `_bc_city_recognised` false
- [ ] D11 and D12: double-tap and triple refresh produce exactly one order
- [ ] D13: a repeat order from the same phone within 10 minutes carries the duplicate flag
- [ ] C5: a coupon cannot unlock free shipping by crossing the threshold pre-discount
- [ ] Form values survive a back-navigation
- [ ] An order placed from a URL carrying UTM parameters records them in `_bc_order_source`
- [ ] Checkout is usable one-handed at 360px, all tap targets 44px

## Stop

No COD threshold logic, no verification workflow, no admin screens. Phase 9.
