LogisticsModule runs on-demand delivery: a customer asks for a pickup and dropoff, the job is priced, offered to drivers, driven stop by stop, and settled into the driver's wallet. Two surfaces: /logistics/delivery/* for operators (52 routes) and /client/logistics/* for drivers and the customers who order deliveries (54 routes).
Datatypes: delivery_config (pricing and assignment rules), delivery_zone (service areas), delivery_agent (a driver), delivery_job (one delivery). Payments are recorded as sf_transaction; driver earnings land as wallet_transaction credits.
Who may call
- Operator routes carry
@StaffOnly()and@Roles(RoleType.User)on the controller: a person signed in to the business. The site's app token and customers are refused. - Client routes need a JWT and nothing more at the controller — no
@PublicRoute, no@Roles. The caller's own record id is used as their driver id and customer id, and the service does the rest:- Driver actions (accept, reject, start/arrive/complete pickup and dropoff, complete) require a
delivery_agentwhose status isapprovedoractive; otherwise403with a message forpending/under_review,suspendedorrejected. - Order routes (edit, cancel, pay, rate, tip, payment details) check the caller is the job's customer — by customer id or email — and answer
403otherwise. - A driver reading a job they did not order gets it without
pricing,paymentandpriceReconciliation.
- Driver actions (accept, reject, start/arrive/complete pickup and dropoff, complete) require a
The delivery lifecycle
delivery_job.status moves through:
pending → broadcasting → assigned → en_route_pickup → arrived_pickup → picked_up → en_route_dropoff → arrived_dropoff → delivered → completed
with cancelled and failed as exits. A job created with a pending, non-merchant payment starts at awaiting_payment and moves to pending once paid; a failed, cancelled or expired payment sets payment_failed.
- Each stop has its own status (
pending·arrived·completed). Completing a pickup stop moves the job topicked_uponly when every pickup stop is done; the same for dropoffs anddelivered. Proof ({ photos, signature }file references) is stored on the stop. completeonly runs fromdelivered(or acompletedjob whose earnings were never credited). It credits the driver's wallet with the driver pay and the tip as separateearningandtipentries referenced by job number, checks the ledger first so a retry never pays twice, frees the driver and sets them backonlinewhen they have no other active job.- The customer is notified when all pickups are done, when the job is delivered, and when it completes; the driver when it completes.
Pricing and addresses
/logistics/delivery/geocodeJWT/logistics/delivery/validate-addressJWT/logistics/delivery/quoteJWT/logistics/delivery/route-distanceJWT/logistics/delivery/configJWTquote takes stops[] (type pickup/dropoff, location as coordinates or an address) and an optional configName, geocodes what it must and checks each stop is in a service area. config returns the named delivery_config, else the default; an organization with none gets a Default config created on first read.
Zones
/logistics/delivery/zonesJWT/logistics/delivery/zones/lookup?lat=&lng=JWT/logistics/delivery/zones/:zoneNameJWTA delivery_zone is bounded by polygon, radius, zipcodes, cities, states or countries. zones lists active zones by default. zones/lookup returns the zone a point falls in. When a job is created, the first stop that falls in a zone sets the job's zone.
Drivers
/logistics/delivery/agentsJWT/logistics/delivery/agents/onlineJWT/logistics/delivery/agents/:agentIdJWT/logistics/delivery/agents/:agentId/locationJWT/logistics/delivery/agents/:agentId/availabilityJWT/logistics/delivery/agents/:agentId/approveJWT/logistics/delivery/agents/:agentId/suspendJWT/logistics/delivery/agents/:agentId/rejectJWT/logistics/delivery/agents/:agentId/available-jobsJWT/logistics/delivery/agents/:agentId/recalculate-performanceJWTA driver registers themselves through POST /client/logistics/register: the delivery_agent is created with the caller's id, status: pending, availability: offline, capacity.maxActiveJobs: 1, and the vehicle if one was sent. A second registration answers 409. Staff then approve, suspend or reject it, and the driver is emailed for each. agents/online returns approved/active drivers who are online, optionally within radius miles of lat/lng.
Jobs and assignment
/logistics/delivery/jobsJWT/logistics/delivery/jobsJWT/logistics/delivery/jobs/:jobIdJWT/logistics/delivery/jobs/:jobId/broadcastJWT/logistics/delivery/jobs/:jobId/offerJWT/logistics/delivery/jobs/:jobId/assignJWT/logistics/delivery/jobs/:jobId/alert-agentsJWT/logistics/delivery/jobs/:jobId/acceptJWT/logistics/delivery/jobs/:jobId/rejectJWTjobs with requireSystemQuote: true re-quotes on the server and refuses the job (400) if any stop is out of area; without pricing or driverPay, the price is calculated from the config and zone. broadcast offers the job to online drivers within the config's assignmentRules.broadcastRadius of the first pickup, each offer expiring after offerExpirySeconds. offer targets one driver, assign places the job directly, alert-agents notifies by agentIds, zone, online or a radius.
Accepting needs the job still pending or broadcasting (409 otherwise) and the driver under their maxActiveJobs; the other open offers expire.
Driving the job
/logistics/delivery/jobs/:jobId/start-pickupJWT/logistics/delivery/jobs/:jobId/arrive-pickupJWT/logistics/delivery/jobs/:jobId/complete-pickupJWT/logistics/delivery/jobs/:jobId/start-dropoffJWT/logistics/delivery/jobs/:jobId/arrive-dropoffJWT/logistics/delivery/jobs/:jobId/complete-dropoffJWT/logistics/delivery/jobs/:jobId/completeJWT/logistics/delivery/jobs/:jobId/cancelJWT/logistics/delivery/jobs/:jobId/failJWT/logistics/delivery/jobs/:jobId/trackingJWTArrive and complete take stopIndex. Staff cancel takes cancelledBy, reason and optionally refund with refundAmount or refundPercent (full refund by default); a refund failure is returned alongside the cancelled job rather than undoing it. tracking stores the live lat/lng on the job.
Money, proof, issues and messages
/logistics/delivery/jobs/:jobId/pricingJWT/logistics/delivery/jobs/:jobId/adjustmentsJWT/logistics/delivery/jobs/:jobId/adjustmentsJWT/logistics/delivery/jobs/:jobId/adjustments/:adjustmentId/removeJWT/logistics/delivery/jobs/:jobId/payment-statusJWT/logistics/delivery/jobs/:jobId/process-paymentJWT/logistics/delivery/jobs/:jobId/imagesJWT/logistics/delivery/jobs/:jobId/imagesJWT/logistics/delivery/jobs/:jobId/issuesJWT/logistics/delivery/jobs/:jobId/issuesJWT/logistics/delivery/jobs/:jobId/issues/:issueId/resolveJWT/logistics/delivery/jobs/:jobId/issues/:issueId/escalateJWT/logistics/delivery/jobs/:jobId/messagesJWT/logistics/delivery/jobs/:jobId/messagesJWT/logistics/delivery/statsJWTAdjustments are debit or credit entries — cleaning_fee, toll_fee, parking_fee, waiting_fee, damage_charge, cancellation_fee, bonus, rebate, discount, refund, penalty, other — with an optional driverPortion that flows into driver pay. Images are filed as proof, pickup, dropoff, issue, damage or other, and can be added after completion. Issues open as open and are resolved or escalated.
Driver app
/client/logistics/initJWT/client/logistics/registerJWT/client/logistics/meJWT/client/logistics/availabilityJWT/client/logistics/locationJWT/client/logistics/jobsJWT/client/logistics/jobs/availableJWT/client/logistics/jobs/:jobIdJWT/client/logistics/jobs/:jobId/acceptJWT/client/logistics/jobs/:jobId/rejectJWT/client/logistics/jobs/:jobId/start-pickupJWT/client/logistics/jobs/:jobId/arrive-pickupJWT/client/logistics/jobs/:jobId/complete-pickupJWT/client/logistics/jobs/:jobId/start-dropoffJWT/client/logistics/jobs/:jobId/arrive-dropoffJWT/client/logistics/jobs/:jobId/complete-dropoffJWT/client/logistics/jobs/:jobId/completeJWT/client/logistics/jobs/:jobId/trackingJWT/client/logistics/jobs/:jobId/issuesJWT/client/logistics/jobs/:jobId/issuesJWT/client/logistics/jobs/:jobId/imagesJWT/client/logistics/jobs/:jobId/imagesJWT/client/logistics/jobs/:jobId/messagesJWT/client/logistics/jobs/:jobId/messagesJWT/client/logistics/jobs/:jobId/messages/readJWT/client/logistics/jobs/:jobId/contactsJWT/client/logistics/uploadJWTinit returns the driver's profile, their jobs, the wallet and — only when they are online and approved — the jobs they can take. jobs/available returns an empty list for an unapproved driver and never includes jobs the driver ordered themselves. jobs answers in driver mode for a registered driver (assigned jobs, active or delivered by default) and in customer mode otherwise or with ?role=customer. upload takes a multipart file and stores it under logistics/jobs/{jobId}/… (or logistics/uploads/… without a jobId).
Earnings and payouts
/client/logistics/payout-methodsJWT/client/logistics/payout-methodsJWT/client/logistics/payout-methods/:methodIdJWT/client/logistics/payout-methods/:methodIdJWT/client/logistics/payout-methods/:methodId/defaultJWT/client/logistics/payoutsJWT/client/logistics/payouts/requestJWTPayout methods are bank, paypal, venmo, cashapp, debit_card or crypto, held on the driver's wallet; see Finance for wallets and payouts.
Ordering a delivery
/client/logistics/quoteJWT/client/logistics/quote/validatedJWT/client/logistics/jobsJWT/client/logistics/ordersJWT/client/logistics/ordersJWT/client/logistics/orders/:jobIdJWT/client/logistics/orders/:jobId/cancelJWT/client/logistics/orders/:jobId/trackJWT/client/logistics/orders/:jobId/payment-intentJWT/client/logistics/orders/:jobId/complete-paymentJWT/client/logistics/orders/:jobId/rateJWT/client/logistics/orders/:jobId/tipJWT/client/logistics/orders/:jobId/tip/completeJWT/client/logistics/paymentsJWT/client/logistics/payments/:jobIdJWT/client/logistics/stripe/configJWT/client/logistics/stripe/intentJWT/client/logistics/stripe/verifyJWT/client/logistics/paypal/createJWT/client/logistics/paypal/captureJWTorders prices the stops, creates the job for the caller and starts payment with paymentMethod stripe or paypal; complete-payment confirms it. A customer with an approved merchant account and enough available credit can pay with merchant_account instead, and the job goes straight to pending.
- Edit only while the job is
pending— once a driver is assigned it cannot change. - Cancel refunds a paid order by status:
awaiting_paymentorpending100%,assigned90%,en_route_pickup50%. Any later status is refused. A failed refund does not block the cancellation. - Track is open to the customer or anyone whose phone matches a dropoff contact; it returns the status, stops, the driver's last reported position (
tracking.currentLocation, withlastUpdate), the assigned driver, timeline and estimated/final price. - Rate (1–5) and tip only on
deliveredorcompletedjobs, by the customer who ordered. The rating is recorded against both the job and the driver.
Related
- Finance — wallets, payouts and payments.
- Storefront — carrier shipping for store orders, separate from this module.