# NexGate Shipping Service — Build Plan Development How we build docs/shipping/SHIPPING_SERVICE_DESIGN.md (the "design", § numbers below point to it). Status: plan agreed to start, nothing built yet. Built on the Location Service (done: wards, shop locations, user addresses, frozen places). Ground rules for every step Talk first, then build — each step starts with a short talk on its details, then code. Code standard : new top-level package shipping_service/ (same level as location_service/ ), the project's controller / service / service/impl / payload / entity / repo shape, no boilerplate, comments only where needed. Existing responses : add fields; reshape only when agreed (dev phase) and always recorded. Every change recorded in commerce_logistics_changes.md — before / after, examples, app to-do. Design doc kept true — any decision that changes during a step is updated in the design. Settings : fixed rules in properties ( shipping.* ), per-shipper agreement settings in the database (§6.7.1). Money : every money move goes through the ledger; every step that moves money ends with the ledger balance check. Tested live locally after each step (like the location steps), using the worked scenarios (§17) as test cases. Phase-0 hooks (§15b) are built in from the start: attempt records, failure reason codes, the extra statuses, timers as data, reverse legs allowed — even though their rules come later. The order at a glance Step Delivers Real-world result after the step 0 Foundations nothing visible; ground ready 1 Pickup, end to end (with codes and "✓ It's what I ordered") buyers can buy and collect at the shop 2 Native shipper registration (KYC + Service Setup basics) delivery businesses can sign up 3 Door coverage, pricing, ETA businesses describe where / how much 4 Engine: estimation + quotation (single door leg) buyers see real delivery costs 5 Cart / checkout / agreement, escrow per part buyers pay real delivery 6 Job flow: offers, codes, statuses, release first real door-to-door deliveries 7 Reassignment, switching, cancelling, bulky mismatch unhappy-but-common cases handled 8 Reliability, reviews, auto-pause, upcoming orders trust and planning 9 Stations (single leg) intercity: collect at a station 10 Multi-shipper chains shop → bus → door 11 Group purchase and installments shipping inside group / installment buys 12 Free shipping (seller rules + campaigns) "Free delivery" badges and filter 13 Bolt integration boda on demand in town 14 Admin dashboard (screens) ops tooling — last later Notifications set · unhappy paths (§15a/b) · staging deploy after the build Step 0 — Foundations Goal: everything later steps stand on, with no behaviour change. Package shipping_service/ with sub-packages from §2 (empty shells added as each step needs them). shipping.* properties with every value from §6.7.1 (read through one settings class). Shared opening hours (§6.3): weekly ranges, special days, temporarily closed, cut-off, EAT, overnight; "is open at", "next opening", "add opening hours" helpers. Attached to shops now (§6.4): shop hours endpoints, "Open now / Opens Sat 09:00" on shop responses; shop pause modes (keep selling / stop orders, SHOP_CLOSED ). Escrow table : part (ITEMS / LEG), legId , orderId filled, provider payee (§12.1) — new columns only, current payments keep working (they are "ITEMS" parts). Product bulky field (§8.1) — default false; category pre-tick later. File Thunder : new upload types business document (private) and business logo (separate repo, small). Codes service : 4 random digits, hashed, never logged, 5 tries → locked, regenerate (§11). Replaces the old DeliveryConfirmation code generator (and its plain-code log line). Check: existing checkout / orders / cart-escrow tests still pass; shop hours + pause visible; a code can be generated, verified, locked after 5 wrong tries, and never appears in logs. Step 1 — Pickup, end to end Goal: a buyer can buy, the seller prepares, the buyer collects with a code, money is released on confirmation. Move Step-4 e_commerce/fulfillment (JSON) into shipping_service/fulfillment as a table ; orders link to it (§2, §3.5). Frozen places stay inside the record. Shipping agreement entity (§4.8): shipping_agreement + parties + changes; states DRAFT → PENDING → AGREED → DONE / CANCELLED. For pickup the only parties are the buyer and the seller. Escrow parts move only on agreement events from here on. Order flow: seller accepts (24 opening hours, else auto-cancel + refund) → Ready → buyer told → pickup at the counter : buyer's code, seller enters it → collected (§3.1, §10). "✓ It's what I ordered" releases all escrow parts in one transaction; "⚠ Report a problem" freezes them (§12.2). Account setting: code or "received" button (§11). Pickup statuses placed → accepted → ready → collected → completed (+ cancelled / expired); optional pickup time (shop opening hours, inside the window; reminder that morning). 14-day collection expiry with reminders (§3.1). Cancel before the seller accepts (§3.4a). Seller cancel at once (pop-up "I have already informed the buyer"; pickup before collected, delivery before Ready). Recent pins recorded after payment , not at checkout (§4.4). Admin endpoints needed from now: manual release / refund of an escrow part (§6.7.2) — SUPER_ADMIN , reason, audit log. Check: scenario A (§17) live: pay → accept → ready → code → collected → confirm → seller paid; 14-day expiry; seller cancel refunds; cancel before accept refunds; problem report freezes; admin release works and is logged. Step 2 — Native shipper registration Goal: a delivery business can sign up and get approved automatically. KYC (§6.2 part 1): name, contact phone, office location (pin), business licence (file + number + expiry, unique number), director's NIDA (file + number), LATRA (file + number + expiry) → auto-approved KYC_APPROVED . Service Setup basics (§6.2 part 2): profile (logo, description, WhatsApp, email, badge), service type (door / station), hours (shared piece, default Mon–Sat 08:00–18:00), limits (max value, bulky + extra, response window, heads-up). Setup checklist endpoint. Statuses KYC_APPROVED → LIVE ⇄ PAUSED , SUSPENDED ; documents private (owner + admin). "Delivery business" mode in the app (owner only, §6.6) — endpoints for the owner's own business. Admin endpoints: suspend / lift (§6.7.2). Check: register with all documents → auto-approved; duplicate licence number refused; documents not visible to others; checklist shows what's missing; suspend / lift logged. Step 3 — Door coverage, pricing, ETA Goal: a business says where it works, how much, how long — and goes LIVE. Location helpers (in location_service ): regions → districts → wards lists + search; coverage outline for the map. Coverage (§7.1): ticked areas (region / district / ward), trips WITHIN_EACH / WITHIN_AND_ACROSS , "both ends inside", fast area-code matching (the spec's second core function, areaMatch ). Pricing (§8.1): distance bands (price + time each, ×1.3, last band = max distance), flat, value add-on ladder max value, bulky extra, rounding up to 100. Goes LIVE automatically when service type + coverage + pricing exist. Delivery commission fee type (0%) in the fee table + per-shipper override with start date (§6.7.1). Check: the §7.1 crossing table (Sinza → Mikocheni with WITHIN_EACH vs ACROSS…) as tests; band and add-on maths incl. rounding; business flips to LIVE when complete. Step 4 — Engine: estimation + quotation (single door leg) Goal: buyers see real delivery options and costs. Engine (§9): where → who (coverage) → filter (distance, value, bulky, hours / cut-off, paused) → cost + ETA → clean-up (dominated, outliers 3×) → labels 💰 ⚡ ⭐ (⭐ = price + time at launch) → order, top 5, fairness. ETA (§8.2): from "shop ready", ranges, closed hours. Estimation POST /api/v1/shipping/estimations (§4.5): cost {from,to} , eta {from,to} , pickup area; Redis cache shop-area → buyer-area (1 h). Quotation (§4.1): live options for a shop's parcel and a destination (§4.2: saved / recent / pin; station recognition comes with step 9). Product detail: live quotation for the viewer's address. Check: scenario L (cards) and the engine walk-throughs from the master guide (A, B, E) reproduce; cache hit on the second call; closed shipper shifts ETA instead of disappearing. Step 5 — Cart, checkout, agreement, escrow per part Goal: buyers pay the real delivery cost; money split into items + leg. Cart (§4.3, §4.4): one destination per cart, per-shop quotation held 5 days , add-to-cart fulfillment + dropoff , defaults for a new shop, unsaved pin on the cart, re-quote on changes. Checkout → agreement (§4.1, §4.8) with its legs (each leg escrow part points to one): editable on the checkout screen until paid (re-quote, wallet re-check), locked once paid . Buy-now uses the sheet's quotation. Escrow per part (§12.1): items part → seller, leg part → shipper owner; release all on confirm. Seller fee on items + tax only ; delivery commission from the leg (§8.3). shippingByShop now real costs (replaces the 0 placeholder). What the app keeps vs the server (§4.6) honoured. Check: scenario B up to payment: escrows created per part with the right payees and fees; quotation older than 5 days refreshed; edit at checkout re-quotes; ledger balance check passes. Step 6 — Job flow, codes, delivery Goal: the first real door-to-door delivery from offer to release. Seller accepts → heads-up ; Ready → offer with response window (counted while open), closed-shipper 4 h rule (§10). Ready pop-up : "I have packed it well" + "label / number is on the package" (recorded). Parcel number created when the seller accepts (QR label generated by the app, or written on the box); scanned / typed at pickup → "✓ yours / ✗ wrong parcel"; shippers see the parcel, never the order (§11, §6.6). Shipper: accept / reject (reason), timeout; active job steps; pickup code (seller verifies) and delivery code (shipper submits) or the buyer's button (§11); optional handover photos. Shipper accept = signs the agreement ; all signed → AGREED, parcel moves (§4.8). Statuses door ladder (§3.2) incl. heading to the shop + phase-0 statuses. Release on "✓ It's what I ordered": items → seller, leg → shipper (one transaction). Shipper app endpoints for offers / jobs / earnings (§6.6), live push. Check: scenario B end to end; scenario M (a shipper's day) step by step; timeout counted; codes locked after 5 tries. Step 7 — Reassignment, switching, cancelling, bulky mismatch Goal: the common unhappy-but-normal cases. Reassignment (§10a): rejection / timeout → that leg only; tiers 1 km → same district → buyer ; same-or-cheaper and not slower = automatic (escrow re-pointed, difference refunded); dearer / none → buyer decides; loop guard 3 per order ; buyer silent 24 h → pickup ; chain safety net 24 h ; free-shipping caps recalculated; history kept. Switching (§3.4): pickup → delivery (fresh quotation, pay, offer at once if Ready); delivery → pickup (before any shipper accepted, refund). Cancelling (§3.4a): shipping-only until pickup — free before heading to the shop , wasted-trip fee (50% / Bolt's charge) after; whole order when the chain failed. Agreement changes (§4.8): propose → affected parties approve → default on silence; destination change (accept new cost / keep old price / decline; no withdraw after pickup). Bulky mismatch (§8.4): report before pickup, bulky-only reassignment, seller pays difference + 50% wasted trip, auto-flag after 2. Badly packed refusal (§8.5): before pickup, photo, seller repacks + Ready again, seller pays 50% wasted trip. Admin endpoints: force reassign / cancel (§6.7.2). Check: scenarios D, F, G, H, N, O, P ; loop guard; silent buyer → pickup after 24 h; ledger balanced after every adjustment. Step 8 — Reliability, reviews, auto-pause, upcoming orders Reliability counters (acceptance, on time, failed, broke binding) and auto-pause levels with the §14 table, grace period 10 offers, reasons shown. Reviews like shops, verified only, owner replies, moderation (§14); shown in quotations. Two-way private ratings (shipper rates buyer / seller). Upcoming orders on the shipper dashboard, area-only until accepted (§6.6). Real delivery times recorded (for after-launch ETA). Check: the §14 trigger table as tests (2 / 3 missed, 3rd pause in 7 days → suspended…); new shipper not paused inside the grace period; upcoming card hides the exact pin until accepted. Step 9 — Stations (reached by a door leg) Stations (§7.2): pins with counter name, phone, hours; ≥ 1 per ticked area; lines ; reach 30 km (per business override). Station-pair table (price + time, both ways, pre-filled pairs) (§8.1). Handover points (§7.2a): leg 1 always starts with a collection at the shop (option A) — either the station business collects from shops (areas + flat fee, 0 = free; one leg) or a door shipper brings it to the station (the two-leg chain door → station, built here); buyer collects at the destination station (station staff enter the buyer's code). Step 10 extends it to 3 legs, editing and re-confirms. Station ladder (§3.3); quotation meeting-point recognition ("collect at Mbeya Bus Stand", §4.2). Check: a Mbeya → Morogoro trip as boda → bus (two legs) and as a bus company collecting free (one leg, ranked cheapest); no door shipper at the shop → no station route; pin within 1 km of a station offers "collect there"; empty pair = not offered. Step 10 — Multi-shipper chains Chain building (§4.7): up to 3 legs, meeting points within 1 km, always next to single-leg options. Quotation editing per leg: stop early, swap a leg's shipper, change a meeting point. All legs offered at Ready, nothing moves until all accept ; binding after accept ; re-confirm when an accepted leg's time moves > 4 h (§10a 7a). Shipper → shipper handover: "handed over" / "received" + that handover's code, optional photo (§11). One escrow part per leg ; release all together (§12). Check: scenarios C, D, E with three legs; one leg rejecting while others accepted; re-confirm triggered by a 4 h move. Step 11 — Group purchase and installments Group (§5): each member's own fulfillment and fee with their payment; refunds on fail / withdraw; moves with a transferred payment; price frozen at payment. Installments (§5): immediate = fee with the down payment; layaway = choose and pay after the last instalment (reuses switch-to-delivery). Check: scenarios J and K . Step 12 — Free shipping Seller rules (areas, minimum, cap, product override) and NexGate campaigns (areas, dates, cap, audience, shops, budget reserved at payment, auto-stop) (§13). Stacking, the cheapest qualifying option free, faster pays the difference; campaign ledger account; seller share from the items part at release. Estimation freeDelivery + card badge + "Free delivery to my area" search filter. Check: scenario I ; campaign budget never overspent under parallel checkouts; refund returns budget. Step 13 — Bolt integration Adapter (§6.5): OAuth2 token cache, estimations (boda, non-bulky, in-city), book at Ready (re-estimate, held price = max + 2%), cancel, webhooks (3 IPs + token) and polling fallback, state mapping, tracking link + driver details. Bolt ledger account , monthly settlement record, receipt differences; kill-switch and daily spend limit (§6.7); seller pays a shop no-show. Built and tested in Bolt's sandbox first. Check: sandbox flow (estimate → create → states → completed → receipt); NO_DRIVER_FOUND → reassignment; spend limit blocks new Bolt quotations; kill-switch hides Bolt immediately. Step 14 — Admin dashboard (last) Screens on top of the existing admin endpoints (§6.7.3): shippers + KYC documents, Bolt status and settlement, operations board, campaigns, reports (by area), audit log, STAFF_ADMIN / SUPER_ADMIN split. After the build New dedicated shipping notifications (one set, after the shipping service). Unhappy paths (§15a / §15b): auto-confirm window, delivery attempts + office hold, returns, disputes. Staging / production : PostGIS image + CREATE EXTENSION , ward file, settings — at deployment. After-launch items (§16). Dependencies 0 ─► 1 ─► 2 ─► 3 ─► 4 ─► 5 ─► 6 ─► 7 ─► 8 │ ├─► 9 ─► 10 │ ├─► 11 │ ├─► 12 │ └─► 13 └─────────────────────► 14 (last) Steps 9, 11, 12, 13 can be reordered after step 6 (each only needs the single-leg flow). 10 needs 9.