# Architect — F-020 Shipping module ## Feature F-020 adds server-side shipping quotation behind `ShippingService.calculate(cart, address)`. ## Design ### Module boundaries Create `project/src/modules/shipping/` with domain/application/infrastructure/api/tests. Shipping owns zones and methods; it does not own cart data. Callers pass a cart summary and destination address into the public service. ### Data model Add migration `015_shipping.js`: - `shipping_zones`: id, name, country, postal_code_prefix nullable, active. - `shipping_methods`: id, zone_id, name, base_cost_cents, free_shipping_threshold_cents nullable, active. ### Rules - Address matching uses country plus optional postal-code prefix. Prefer the most specific prefix. - `ShippingService.calculate(cart, address)` returns the cheapest active method in the matching zone. - If cart total is at or above method threshold, shipping cost is zero. - If no zone/method matches, throw a domain error mapped to HTTP 422. - Carrier adapter is an interface only; no real carrier API. ### API - Admin create/update can be minimal for test setup: `POST /shipping/zones`, `POST /shipping/methods`. - Public/authenticated calculation endpoint: `POST /shipping/calculate` with cart total/address payload. ## Acceptance trace - Known zone returns shipping cost. - Unknown zone returns HTTP 422 with clear code. - Cart above free shipping threshold returns zero cost.