A production-style multi-tenant logistics backend built with Spring Boot, PostgreSQL, Redis, and Docker.
Not a tutorial project. Built to solve real delivery problems.
I've personally faced three frustrating problems as a customer:
- Too many apps β food delivery, courier, electronics returns all use different platforms. Why can't one backend handle all of them?
- The introvert problem β when an order is out for delivery, the rider randomly calls. Some customers don't pick up because they're anxious about unexpected calls. The order fails β not because the customer was unavailable, but because they weren't prepared. Scheduled slots fix this.
- False no-show claims β riders sometimes mark an order as attempted when they never showed up. Customers get charged. There's no proof either way. OTP verification and GPS-photo proof solve this.
This platform is my attempt to solve all three in one system.
Controller β Service β Repository
β
Policy Enforcement Layer (CompanyPolicy per product + delivery type)
β
State Machine Lifecycle (10 statuses, enforced transitions)
β
Domain Rule Layer (OTP guard β platform invariant, not configurable)
β
Capacity-Safe Rider Dispatch (zone match, duty check, concurrency limit)
β
Scheduler Layer (SLA automation, confirmation flow, auto-cancel)
β
Analytics + Caching Layer (Redis, native SQL, projections)
Controllers are thin HTTP adapters β all business logic lives in service classes.
OTP enforcement is a platform-level invariant: no configuration can bypass it.
| Week | Focus | Status |
|---|---|---|
| 1β2 | Foundation β JWT, roles, entities, migrations | β Done |
| 3 | Order Engine β state machine, slots, WhatsApp, SLA | β Done |
| 4 | Analytics β dashboards, Redis caching, projections | β Done |
| 5 | SLA Automation β scheduler, trend API, zone heatmap | β Done |
| 5+ | Clean Architecture β thin controllers, refactored services | β Done |
| 6 | Delivery Verification β OTP system + platform-mandatory enforcement | β Done |
| 7 | Photo Proof Enforcement β GPS + photo on every FAILED attempt | β Done |
| 8 | Auto-Dispatch + RunSheet | π Planned |
| 9β10 | Intelligence β incentive engine, confidence scoring | π Future |
| 11β12 | Scale β partner API, webhooks, rate limiting, K8s | π Future |
- JWT authentication with BCrypt password hashing
- 4 roles:
ADMIN,COMPANY,RIDER,CUSTOMER - Role-scoped data isolation β COMPANY sees only its own data
@PreAuthorizeon all endpoints, custom 401/403 handlers
| Model | SLA Deadline | Slot Required | Dispatch Style |
|---|---|---|---|
INSTANT |
30 minutes | No | Zone-based, auto-dispatched |
PARCEL |
48 hours | Date only | WhatsApp-confirmed |
SCHEDULED |
24 hours | Date + time window | WhatsApp-confirmed |
PICKUP_RETURN |
48 hours | No | WhatsApp-confirmed |
For time-critical deliveries like food or same-hour courier. The order is assigned to the nearest available rider in the same zone and must be delivered within 30 minutes. No customer confirmation step β the order goes live immediately after creation.
These models use a two-step WhatsApp confirmation flow before dispatch:
Step 1 β 6AM Booking Confirmation:
At 6AM on the delivery date, ConfirmationScheduler sets the order to CONFIRMATION_PENDING and simulates sending a WhatsApp message to the customer:
"Your order is scheduled for delivery today. Please choose:
1οΈβ£CONFIRMβ Yes, deliver today
2οΈβ£CANCELβ Cancel this order"
CONFIRMβ Customer is prompted to pick a time slot nextCANCELβ Order cancelled immediately
Step 2 β Slot Selection (after CONFIRM):
Once the customer confirms, they must choose a delivery window:
"Please select time slot:
1οΈβ£SLOT_9_12β 9AM to 12PM
2οΈβ£SLOT_12_3β 12PM to 3PM
3οΈβ£SLOT_3_6β 3PM to 6PM"
- Slot availability is checked against
SlotCapacity(company + zone + date) - On success β order moves to
CONFIRMED, slotbookedCountincremented
If No Response β Auto-Retry & Auto-Reschedule:
- After 12 hours with no reply β a same-day reminder is sent
- After a full day ignored β order is automatically rescheduled to the next day (
slotDate + 1) - After 3 consecutive ignored days β order is auto-cancelled (
autoCancelled = true)
CREATED β CONFIRMATION_PENDING β CONFIRMED β ASSIGNED β IN_TRANSIT
β
DELIVERED β (OTP verified) β IN_TRANSIT
COLLECTED β (OTP verified) β IN_TRANSIT
FAILED β (GPS + photo required) β retry
DISPUTED β (OPEN_BOX only)
Invalid transitions are blocked at the service layer. An order cannot jump to ASSIGNED before CONFIRMED. This keeps rider analytics and KPI dashboards accurate.
Every DELIVERED and COLLECTED transition requires a verified OTP β with no config flag to skip it.
Flow:
- Rider calls
POST /api/orders/{id}/otp/sendβ 6-digit OTP generated, BCrypt-hashed, stored - Customer shares the OTP with the rider
- Rider calls
POST /api/orders/{id}/otp/verifywith the raw OTP β BCrypt compared - Only after verified β
PATCH /api/orders/{id}/statuswithDELIVERED/COLLECTEDis accepted
Guard implementation:
ensureOtpVerified()is a private domain-rule method called beforeorder.setStatus()β the transition is atomic- 3 wrong attempts β OTP permanently locked; rider must re-send
@Transactional(noRollbackFor = ApiException.class)onverifyOtpensureswrong_attemptsincrements persist even when the exception is thrown- OTP hash stored in
delivery_otpstable;verified_atrecorded as audit trail
This eliminates the "fake delivery" problem β no completion without proof.
- Every
FAILEDstatus update requireslatitude,longitude, andphotoUrl - Stored in
attempt_historyalongsidefailure_reason,rider_id, andattempt_number - Prevents false no-show claims β every failed delivery is GPS-verified and photo-evidenced
- On order creation, if a customer had β₯ 2 failed deliveries in the last 30 days, the order is immediately placed in
CONFIRMATION_PENDING - The
ConfirmationSchedulerpicks it up and re-sends confirmation β no silent dispatch for high-risk customers
SlaBreachSchedulerruns every 5 minutes via@Scheduled- Finds orders past their SLA deadline with
slaBreached = false - Auto-flags them and evicts the Redis dashboard cache
- Admin KPI β 9 executive metrics: total orders, delivered, failed, success rate, SLA breached, active riders, companies
- Company KPI β same metrics, isolated per company (multi-tenant)
- Rider Analytics β success rate, failure grouped by reason, zone + date filtered
- Rider Ranking β sorted by success rate
- Order Trend API β
GET /api/admin/reports/order-trend?interval=DAY|WEEK|MONTH - Zone Heatmap β
GET /api/admin/reports/zone-heatmapβ which zones are failing, which are overloaded
All dashboards use native SQL aggregation + typed Spring projections (no Object[] raw queries).
@Cacheableon admin and company dashboards@CacheEvicton every order create, status update, and rider assignment- All cache names registered in
CacheConfigto preventCannot find cache namederrors
- Inbound replies handled by
WhatsAppWebhookService(interface +WhatsAppWebhookServiceImpl) - Webhook endpoint:
POST /api/webhook/whatsapp?orderId={id}&action={action}β secured withX-Webhook-Secretheader - Supported actions:
CONFIRMβ slot prompt,SLOT_9_12/SLOT_12_3/SLOT_3_6β book slot,CANCELβ cancel order ConfirmationSchedulerfires at 6AM daily to send booking confirmations; retries every hour; auto-reschedules after 1 ignored day; auto-cancels after 3 ignored days- All outbound messaging is currently logged (real WhatsApp API integration is wired in as the next step)
- Per-date, per-window slot capacity management via
SlotCapacityentity SlotControllerexposes slots management for admins- Capacity enforced at order confirmation time
- Riders can only be assigned to orders in their zone (hard constraint)
- Null-safe zone check in
assignRider()β orders without a zone skip enforcement - Zone heatmap shows failure rates and order volume per zone
- DB indexes on zone, status, SLA breach for fast analytics queries
- Companies onboard with full policy setup per product + delivery type
- Email-based user resolution for company linking
- Company status tracking (active/inactive)
- 22 Flyway migrations β full schema versioning from V1 to V22
- Indexes on:
zone,status,sla_breached,company_id,created_at - Key tables:
orders,riders,users,companies,attempt_history,delivery_otps,slot_capacities,zones
| Layer | Technology |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3 |
| Security | Spring Security + JWT |
| Database | PostgreSQL |
| Caching | Redis |
| Migrations | Flyway |
| Containerization | Docker + docker-compose |
| Build | Maven |
Prerequisites: Docker and Docker Compose installed.
# 1. Clone the repository
git clone https://github.com/AditShh-git/delivery-platform
# 2. Create a .env file
cp .env.example .env
# Fill in your values
# 3. Start everything
docker-compose up --buildThe app will start on http://localhost:8080.
PostgreSQL on 5432, Redis on 6379.
src/
βββ controller/ # Thin REST adapters β routing, auth, request/response mapping only
βββ service/ # Business logic, state machine, domain rule enforcement
β βββ impl/ # Service implementations
βββ slot/ # Slot capacity entity, repository, service, and controller
βββ repository/ # Spring Data JPA + native SQL queries
βββ scheduler/ # SlaBreachScheduler, ConfirmationScheduler
βββ entity/ # JPA entities
βββ dto/ # Request/response records
β βββ request/
β βββ response/
βββ projection/ # Typed Spring projection interfaces
βββ config/ # CacheConfig, SecurityConfig, custom auth entry points
βββ security/ # JWT filter, token provider
βββ utils/ # Shared utilities
βββ exception/ # Global exception handler
db/migration/ # V1βV22 Flyway SQL migrations
- AutoDispatchScheduler β INSTANT orders self-assign to the best available rider every 5 seconds
- RunSheet System β admin creates a sorted delivery list for PARCEL riders, route ordered by nearest-neighbor GPS sort, exportable as CSV
Built independently to simulate production SaaS backend architecture.