Solomon Adeoye
Solomon Adeoye
Solomon Adeoye
In production · B2B logistics
Dispatch, track, and pay for deliveries without the WhatsApp spreadsheet.
RUN Technologies / Deliverys2025
Status
Role
Built with

Screenshot 01
1 / 18
I build the business product end to end: the Next.js dashboard at b2b-hub.rundeliverys.com, and the NestJS business-app that quotes a trip, debits a wallet in a Mongo transaction, hands the order to ops for assignment, and fans status out over Socket.IO, FCM, and signed webhooks.
The merchant dashboard and business API on Run Delivery’s NestJS platform: quoting, wallet checkout, admin rider assignment, live GPS tracking, and HMAC-signed webhooks so a store’s checkout and the rider’s trip stay on the same dispatch.
Before / after
role: full-stack · merchant dashboard + business APIstack: next.js · typescript · tailwind css · tanstack query · redux toolkit · nestjs · mongodb · redis · bullmq · socket.iosite: rundeliverys.com
Breakdown
Run Delivery already ran consumer and rider apps on a NestJS monorepo (JWT auth, Socket.IO tracking, Paystack wallets). Businesses were still booking over calls, WhatsApp, and spreadsheets: no shared quote, no bulk drop-off, no hook back into their own checkout.
The product had to sit on the same dispatch as riders and customers. A dashboard staff can live in all day, and a public API so a store can create, rate, track, and cancel shipments without opening the UI. Ops still assigns the rider; the merchant should never need to.
The dashboard is the merchant surface: email OTP login, KYC, screen-level access for invited staff, Google Maps pin or typed address, a live quote, single orders or Excel bulk upload, Paystack wallet funding, history with PDF receipts and a map, carbon insights by delivery mode, and a developers area for API keys (`run_live_` / `run_test_`) and webhooks (`whsec_`, rotate keeps the previous key for 24 hours).
The business-app is a sibling in the NestJS monorepo. JWT or scoped API keys (`orders:read` / `orders:write`). Quotes use Haversine km + base fare + weight. Place-order re-quotes every row, then writes dispatches and the wallet debit in one Mongo transaction. The same dispatch document the rider app advances is what the dashboard tracks.
A business order is born PENDING and flagged `is_business_trip`. Unlike consumer express/sameday, it does not auto-broadcast to the rider pool. Ops searches eligible riders inside a radius (5–200 km, default 70), filtered by delivery mode, online status, stale GPS, and concurrency caps. Distance is Haversine from pickup to last known location, nearest first.
Assign is atomic: the trip moves to ASSIGNED and the rider is reserved. The rider confirms → ACCEPTED, then a strict machine through pickup OTP to delivered. GPS can step that path when the rider’s ping is near pickup or drop-off. Cancel from the dashboard is allowed only through START_TRIP, and refunds the wallet.
Every status change hits three fans: Socket.IO on `/dispatches`, FCM to the business dashboard, and a webhook if the merchant subscribed. The dashboard timeline is BOOKED → assigned → accepted → pickup → in transit → delivered, with a map and a shareable tracking id.
Webhooks are a delivery pipeline, not a fire-and-forget POST. Status maps to `shipment.created` / `assigned` / `accepted` / `picked_up` / `in_transit` / `out_for_delivery` / `delivered` / `cancelled`, plus `shipment.status_changed`. Payload is a versioned envelope (`api_version: 2026-08-01`), signed HMAC-SHA256. BullMQ workers claim deliveries with retries and backoff; 4xx is dead-lettered, 5xx/429 retried. An outbox sweeper re-queues jobs lost between Mongo and Redis. The dashboard lists attempts and can retry failed or dead-lettered deliveries.
A quote, a wallet debit, and a rider’s trip have to agree. Place-order fails closed on insufficient balance before anything is written; the debit and the dispatches commit together. The public API is the same booking path as the UI. A key never sees another merchant’s book. Owners see every screen; invited users only see the pages they were given.
Assignment and routing are two products on one document: ops needs a wide, mode-aware search; the rider app needs a tight pool and a status machine that cannot skip OTP pickup. Webhooks have to survive a crash between “status saved” and “HTTP 200 from the merchant,” which is why the outbox, HMAC replay window, and secret overlap exist.
Live at b2b-hub.rundeliverys.com, with public API docs for third-party checkout. Local Nigeria deliveries, wallet, assignment, live tracking, and webhooks are in daily use alongside the rider and consumer apps. Interstate and international lanes are stubbed in the UI.