TASE Logo
E-commerce · 7 MIN READ
← All production logs

Checkout for markets that don't use cards: cash on delivery, wallets and human verification

"If your checkout assumes a card, you have already lost most of the market in half the world. Here is what we build instead, and why the server has to own every number."

WRITTEN BY
Mahad Ali
PUBLISHED
18 September 2026
ESTIMATED READ
7 minutes
CORE DISCIPLINE
E-commerce

Western checkout design has one silent assumption: the buyer has a card and will complete a redirect to a gateway. In Pakistan, and in plenty of other markets, that assumption throws away most of your customers. Money arrives by mobile wallet, by bank transfer, or in cash at the door, and often with a human confirming it.

We have built two storefronts around that reality: a jewellery store with cash on delivery and bank transfer, and an eSIM store with four payment rails including JazzCash and EasyPaisa. Both taught the same lesson twice.

Model how money actually arrives

A card gateway is synchronous: you get a callback in seconds and the order moves on. A wallet transfer with a screenshot is not. The customer pays, uploads proof, and somebody looks at it minutes or hours later. If your order model cannot express that gap, you will paper over it with a status flag and lose track of real money.

So the eSIM store runs a seven-state lifecycle with the human step inside it: pending, paid, processing, delivered, activated, then expired or cancelled, with a payments row recorded per attempt. An admin views the uploaded proof and sets the eSIM, and that single action transitions the order to delivered, cascades any still-pending payment to success so the payment record matches the verified state, and fires a delivery email.

"For a market where card penetration is low, a manual-verification state machine models how payment actually happens rather than forcing customers through a gateway they would not complete."

Keep providers behind their own modules

Four providers sit behind one shared order and payment model, each in its own module: two local wallets, a card gateway and a regional processor. Adding or swapping a provider never touches the order lifecycle. That separation is what lets a store add a rail when a market shifts, which in these markets it does.

The server owns every number

On the jewellery store, the cart persists in the browser and caches each line's price at the moment it is added. A shopper who returned after a re-price saw the old amount in the bag and all the way through checkout, while the order route recomputed every price at submit time. The figure shown and the figure charged could diverge.

The fix was not to trust the cart. A validation endpoint recomputes the authoritative price for each line, base plus any selected variant modifiers, exactly as the order route does. The cart reconciles against it every time it is shown, and checkout re-checks at submit: if anything has moved, the shopper is shown the corrected bag and asked to confirm.

Placing the order then runs in a single transaction that recomputes every line, validates required options, checks product and per-variant stock, applies coupon rules including per-customer caps, then commits the order, decrements stock, writes an inventory ledger entry, records an order event and books the coupon redemption together. It lands consistently or not at all.

Make the business configurable without a deploy

Cash on delivery usually comes with an advance, and the right advance changes with the product, the season and the city. That number belongs to the business, not to the codebase, so it lives in a key-value settings store the admin edits, along with payment design and site copy.

One warning from experience: read that value carefully. Ours was read with a truthy fallback, so an explicit zero, being falsy, collapsed back to the default. A store configured for pure cash on delivery quietly reverted every time. The fix was a shared parser that separates a blank field, which keeps the historical default, from an explicit zero, which means zero.

Fulfilment proof travels with the order

When a human verifies a payment, the thing they verified has to be attached to the record. The payment screenshot is stored and referenced from the payment row, so an admin approves actual proof rather than a status flag, and a dispute weeks later has an artefact behind it.

A checklist for a local-rails storefront

  • List the rails your buyers actually use before choosing a platform, not after.
  • Give the order a state machine with room for a human step, and keep every transition server-side.
  • Recompute price on the server at validation and again at submit; never trust a cached cart total.
  • Put stock, ledgers and events in one transaction so an order cannot half-commit.
  • Store the proof of a manual payment against the payment record.
  • Put business numbers such as the COD advance in editable settings, and parse zero properly.

Do that and the checkout stops being a translation of a Western flow and starts being the one your customers already know how to complete.

ARCHITECTURE INQUIRIES

Questions we get asked

Can you integrate JazzCash and EasyPaisa into a custom storefront?
Yes. Both sit behind their own provider modules over a shared order and payment model, alongside card and bank-transfer rails, so the order lifecycle stays the same regardless of which rail the customer picks.
How do you handle cash on delivery with an advance?
Treat the advance as an editable business setting rather than a constant, compute the amount on the server, and give the order a state that expresses partial payment so the remaining balance is never implied.
Should the browser ever calculate the order total?
It can display one for speed, but it must never be the authority. Recompute every line on the server at validation and again at submit, and show the shopper a corrected bag if anything moved.
Mahad Ali

Mahad Ali

Co-founder & CEO · Principal Architect

The tech guy. Architecture, data models, pipelines, and the production incidents at three in the morning. Most of the engineering behind the sixteen products, and the first reply to your message.

Direct Dispatch →
PRODUCTION ARCHITECTURE

Need this
in your product?