WritingCommerce

A landed cost engine that honours the duty it quoted at checkout

How to build a landed cost service whose quote survives rate changes between cart and charge: versioned inputs, a stored quote, and a charge against its id.

A customer in Sydney adds a £240 jacket to the cart of a Manchester shop and picks express delivery. Before they can pay, the checkout has to show one number that covers the goods, the £32 carrier charge, the duty the destination will levy on the goods and the import tax charged on top of all three, and it has to show that number within a second or two. The system that produces it is the landed cost engine, and its purpose is that the customer is never asked for money again when the parcel arrives.

The constraint that shapes the engine is that every input moves independently of the customer's session: the exchange rate changes every few minutes, the carrier reprices a lane, the duty table gets a new version, a product is reclassified. So the engine must never be asked to recompute at payment time. It computes once, stores the quote with the exact versions it used, and the charge references the quote id. If a recomputation would give a different figure from what the customer saw, that difference is the merchant's to reconcile, not the customer's to pay.

Checkout asks for one number

Checkout posts the cart lines (sku, quantity, unit price in the shop currency), the destination address and the delivery speed, then waits synchronously with a 2 second timeout. For the Sydney jacket that is one line of 24000 minor units in GBP, a postcode in New South Wales and express. If the engine does not answer in time, checkout shows goods and shipping only, labels duties as payable on delivery and records that choice on the order, so a slow engine degrades the offer instead of blocking the sale.

Classification and the duty table

The engine asks the classifier for a tariff code per sku; the answer is cached against the catalogue version, so a reclassified product invalidates its own cache entry and nothing else. It then reads the duty table row for that code and destination, and the row carries a table version that is copied into the quote. A sku with no classification does not stop the quote: the engine applies the highest rate among the codes the product category can map to and raises a classification task, so the customer is over-quoted rather than under-collected.

Live rates with one fallback

The FX source returns a rate with an id and a timestamp; the engine refuses any rate older than 15 minutes and fails the quote rather than guess, because a stale rate is the one input nobody can reconcile afterwards. The carrier quote is a live call for the parcel's weight, dimensions and lane, capped at 1500 ms and cached per lane for 10 minutes. When the carrier does not answer, the engine substitutes the stored rate card for that lane and marks the quote with carrier_source set to rate_card, which the reconciliation step reads later.

Compute once, store before showing

The engine works in minor units throughout and rounds each line once at the end, then writes a quote row before it returns anything: quote_id, a hash of the cart and address, the tariff code per line, the duty table version, the FX rate id, the carrier rate id or rate card version, the breakdown, the total and an expires_at 30 minutes ahead. The write is synchronous and has no fallback, because a quote that was not stored cannot be honoured. Only after the row commits does the breakdown go back to the customer.

Charge the quote, not the cart

Checkout sends payment the quote_id and the cart hash; payment reads the quote, checks that it is unexpired and that the hash still matches, and charges the stored total of 31240 minor units with the quote_id as the payment reference. The order stores the quote_id, and an asynchronous order.placed event carries it to fulfilment, which buys the label against the same carrier rate id. When the carrier invoices actual duty and shipping weeks later, reconciliation matches the invoice to the quote_id and books any difference to a variance ledger, never to the customer.

Trade-offs

I have built cross-border procurement that buys from Amazon, Alibaba, AliExpress and 1688 and ships with DHL, FedEx, UPS and USPS, and the rule I apply to a landed cost engine is that a price shown is a price honoured, whatever that costs the merchant in variance. Each design choice below pays for that rule in a different currency.

ChoiceWhat it buysWhat it costs
Store the quote before showing itPayment charges exactly what the customer saw, by idA write on every cart change, and a sweeper for expired rows
Expire quotes after 30 minutesBounded exposure to FX and carrier movesA customer who returns after lunch sees a new total
Bind the quote to a cart and address hashA quote for one basket cannot pay for anotherAny edit, even a quantity change, invalidates the quote
Highest plausible rate for unclassified skusNever under-collect on a product nobody has codedSome customers see a higher total and leave
Rate card fallback when the carrier is downCheckout keeps quoting through a carrier outageShipping variance that has to be booked and reviewed
Refuse a stale FX rate outrightNo quote is ever built on a rate the ledger cannot matchAn FX outage turns every quote into duties on delivery

Failure modes

  • The carrier quote times out. At 14:02 the Sydney customer reaches checkout; the engine calls the carrier for a 1.4 kg express parcel to New South Wales and gets nothing back in 1500 ms. The engine reads the rate card for that lane, which says £29 rather than the live £32, computes duty at the table's 5 percent on the goods (£12) and import tax at 10 percent on goods, shipping and duty (£28.10), and stores a quote of 30910 minor units marked carrier_source rate_card. The customer sees £309.10 and pays it. When fulfilment buys the label the carrier charges £32; reconciliation sees the rate_card marker, books £3 of shipping and 30p of tax to the variance ledger and closes the order. You observe the carrier_quote_fallback counter rising; the containment is an alert when fallbacks exceed 5 percent of quotes over 10 minutes, and a weekly rate card refresh so the fallback stays close to the live price.
  • The FX rate is stale. The rate source stops publishing and the engine sees a rate stamped 18 minutes ago. It refuses to quote, checkout shows duties on delivery, and the quote_refused_stale_fx counter rises; a rate that stops moving for more than the freshness window is the trigger, and the containment is a second rate source the engine consults only when the first is stale.
  • The quote store is unavailable. Every quote request returns an error and every cross-border customer gets duties on delivery. You observe quote write latency and a conversion drop on cross-border orders at the same minute; the containment is that the store is the one component with a replica and a failover, because nothing else in the engine needs to be durable.
  • The quote expires between page load and the pay button. Payment finds expires_at in the past and returns quote_expired to checkout, which requests a fresh quote and shows the new total before charging. You observe the ratio of quote_expired to charges; the containment is refreshing the quote silently once the checkout page has been open for 25 minutes.
  • The classification was wrong. The parcel is assessed at a different rate on arrival and the carrier's duty invoice does not match the quote. Reconciliation books the difference against the tariff code, and a code that produces variance on three orders in a row raises a classification review. The customer paid what was quoted, so nothing reaches them.

Build the quote store first, before the classifier, before the carrier integration and before any pricing logic: a row with a quote_id, a cart hash, an expiry and a snapshot of every input version. Once payment charges by quote_id, every other component can be replaced, cached or made to fall back without the customer ever seeing a second bill.

Written by Md Nasim Anjum, senior full-stack engineer in Manchester. He builds payment orchestration, KYC and KYB compliance platforms and conversational AI.

Get in touchAll writingRSS

More writing