Case study · Order dispatch
DropCare — getting a prescription to the right pharmacy
A pharmacy delivery platform that offers each order to pharmacies in tiers, then widens the search for a courier until someone takes it.
The problem
A customer photographs a prescription, or records a voice note, and wants the medicine delivered. The platform has to find a pharmacy that will fill the order, agree a price, take payment and find a courier. Five kinds of user take part: customers, pharmacies with their pharmacists, delivery companies, couriers and admins. An order moves through 14 statuses.
My role
I led the backend and wrote most of it: order dispatch, the wallet, invoices, settlements, coupons, support chat, financial reports, the realtime server and the deployment pipeline. A second developer joined for part of the project. I assigned their tasks and reviewed their merge requests.
Key decisions
Pharmacies are offered the order in tiers. The client’s rule was that pharmacies offering customers a higher discount should see an order first. I implemented it as a staged fan-out.
The customer posts an order
With prescription images or a voice note.
Find the pharmacies in range
A distance query around the delivery address.
Notify them tier by tier
Grouped by discount, highest first. Each lower tier is a queued job with a short delay.
A pharmacy accepts and sends an invoice
Acceptance opens a chat room. A timeout job handles the case where nobody answers.
The customer pays
Wallet, cash or Apple Pay.
Search for a courier in a widening ring
A delayed job reschedules itself with a larger radius, up to 100 km.
Pickup, delivery, settlement
A courier search that widens itself. Notifying every courier in the city at once is noisy, and a fixed radius can leave an order stuck. The search starts close and grows.
- Each round searches a ring, from the previous radius to the new one, so nobody is notified twice for being inside both.
- Before each round the job reads the order again under a row lock. If a courier has already accepted, it stops.
- A ledger table records who was notified, with a unique constraint. A retried job that tries to notify the same courier again is harmlessly rejected by the database.
Three parties share each payment. The pharmacy, the courier and the platform each take a part. Commissions include VAT, some products are taxable and some are not, and a coupon can target the service fee or the delivery commission. One service computes the breakdown and settlements read from it.
A wallet that survives two clicks. Debits and charges lock the wallet row. Top-up requests go through admin approval, guarded so that the same request cannot be approved twice.
Realtime outside PHP. Chat, calls and live courier tracking run on a small Node server with Socket.IO, so the Laravel application stays stateless.
Also built
- Invoices with a QR code and a PDF, reachable through a tokenised public link.
- A status log written by an observer on every transition, which the reminder jobs read.
- A GitLab pipeline that deploys over SSH and always takes the site out of maintenance mode, even when a step fails.
- Financial reports for admins with Excel export.
Outcome
Five API surfaces and an admin panel, with dispatch, payment and settlement working from the first order to the provider’s payout.
What I would do next
- Apply the courier search’s lock-and-recheck pattern to pharmacy acceptance, so that two pharmacies accepting in the same instant are resolved by the database.
- Add automated tests around the money calculations and the dispatch jobs.