# Architect — F-019 Promotions v1 ## Feature F-019 adds server-side promo code validation and discount calculation. Discounts are recalculated from current cart totals and never trusted from the client. ## Design ### Module boundaries Create `project/src/modules/promotions/` with domain/application/infrastructure/api/tests. Promotions owns promo definitions and validation. Cart receives a public `PromotionServicePort` from the composition root and stores only the selected promo code, not discount amounts. ### Data model Add migration `014_promotions.js`: - `promotions_promotions`: code, type (`percent`, `fixed_amount`), value, validity window, usage_limit, usage_count, active, timestamps. - Add `promo_code text` to `cart_carts`. ### Rules - Percent discounts are basis points over current cart total. - Fixed amount discounts are cents and capped at current cart total. - Validity checks: active, starts_at <= now, ends_at >= now, usage_count < usage_limit when limit exists. - No stacking in v1: one promo code per cart. - Client discount fields are stripped/ignored by cart schemas. ### API - Admin `POST /promotions` creates promo codes. - Public/authenticated cart endpoint `POST /cart/promo-code` stores a code after server validation. - `GET /cart` recalculates discount through `PromotionService`. ## Acceptance trace - Valid promo: cart read applies recalculated server discount. - Expired/exhausted: applying code returns HTTP 422 reason. - Client discount ignored: schemas strip discount fields and cart stores no discount amount.