Start a project
Blog

Designing checkout for a cash-on-delivery market

Almost everything written about e-commerce checkout assumes a card. Stripe or a local acquirer takes the payment, the order is paid before it ships, and failure means a declined transaction on the spot. In Morocco, most of the market does not work that way. The dominant flow is cash on delivery: the customer orders without paying, someone calls to confirm, a courier carries the parcel, and money changes hands at the door.

We have now built this flow for a retail platform serving two cities, and the same lessons carried into a grocery marketplace. This is what actually changes when the payment happens last.

The order is a lead until someone confirms it

With card payment, a placed order is a commitment. With cash on delivery it is closer to a lead. Nothing has been paid, and a meaningful share of orders are mistyped numbers, second thoughts, or children playing with a phone. Shipping every raw order burns courier fees on parcels that will be refused.

The fix is a role, not a feature: the confirmation agent. Their whole job is calling customers and pressing one of two buttons. That means the order lifecycle needs a state between placed and shipped, and the admin panel needs a queue built for one-handed phone work: big targets, the number dialable in one tap, the outcome recorded without leaving the list.

Order states multiply, and each one is a business decision

Our first draft had four states. The version that survived contact with a real merchant has seven: placed, confirmed, preparing, dispatched, delivered, refused, returned. Refused and returned are not exotic edge cases in a COD market; they are Tuesday. The stats that matter to the merchant follow from those states, and one detail matters more than it looks: the success rate must be computed against the whole queue, not against completed orders. A courier who delivers 9 of 10 assigned parcels is not at 100% because the tenth never completed.

The city list is a feature

Delivery pricing and reach in Morocco depend on the city, and couriers quote by city name. A free-text address field produces unroutable orders. We ship an exhaustive, curated list of Moroccan cities in the checkout, and the courier integration maps each to the carrier's own zone table. Boring, and it removes a whole class of failed deliveries.

Integrate the carriers, not a fantasy API

There is no Stripe of Moroccan delivery. There are several carriers, each with its own API shape, auth quirks and webhook reliability. We integrated five plus a configurable custom provider, and the platform decides at dispatch which carrier serves the order's city. Tracking references are written back onto the order so the merchant never leaves the panel.

What we would tell anyone building this

Design for the phone call. Put the confirmation queue at the centre of the admin, model refusal and return as first-class states, treat the city as structured data, and assume every carrier API will surprise you. None of this is glamorous. All of it is the difference between a demo and a platform a merchant runs their business on.

All articles
Start here

Tell us what you need.

One paragraph is enough. You'll get a straight answer on whether it's a fit, roughly what it takes, and what happens next.

Reply within one business day NDA on request Fixed-price quotes, no hourly billing
Company
NeedBridge LLC
Registered
United States
Studio
Morocco