Most delivery-fee tutorials assume one warehouse. You set a flat fee, add a free-shipping threshold, done. Our store doesn't work like that. A single cart on ramo.co.il can mix things that physically leave from different places: a few bags of potting soil from one storage, a stepping stone from another, a tube of glue from a third. Each of those places has its own trucks, its own rules and its own idea of what a delivery costs.

This post is about the model we ended up with, and about one boring bug class that kept coming back: settings that were simply never filled in.

Delivery belongs to the storage, not to the store

The first version had store-wide settings: one fee, one threshold. It fell apart the moment a second supplier warehouse joined, because "free delivery above X" means nothing if half the cart ships separately and someone still has to pay for that truck.

So delivery settings moved down a level. Roughly:

-- one row per physical place goods ship from storages ( id, name, below_free_delivery_fee numeric, -- what we charge when under the threshold free_delivery_threshold numeric, -- subtotal from this storage that ships free min_order_amount numeric -- optional minimum to ship at all ) -- how a storage can ship, and what it costs storage_shipping_methods ( storage_id, method, -- 'HOME', 'PICKUP', ... first_carton_price numeric, additional_carton_price numeric ) 

At checkout the cart is grouped by storage, each group is priced on its own, and the customer sees one line per shipment instead of a single mysterious number. That last part matters more than it sounds. When people see "delivery from warehouse A" and "delivery from warehouse B", the total makes sense to them. When they see one large fee with no explanation, they leave.

NULL is not zero

Here's the bug class. A new storage gets added through the admin, someone fills in the name and the products, and the fee columns stay NULL. Nobody notices, because nothing crashes in the admin.

Then a customer puts one of that storage's products in the cart, and the checkout has to decide what NULL means. Depending on which code path you hit, it meant different things:

  • fee ?? 0: delivery is free. Nice for the customer, not so nice for whoever pays the driver.
  • Number(fee): still 0, same problem, just quieter.
  • fee.toFixed(2): an exception, and an error report in Sentry.

The exception was honestly the best of the three, because at least it got noticed. That's how we found it: an error pointing at checkout, and behind it a set of storages with no fee configured at all. The scarier thought was the other two paths, which would never have told us anything.

What we changed:

  1. Backfilled the missing values on purpose. Not with a code default, but with a real number someone decided on, written into the rows. Before the update we copied the affected rows into a backup table and kept the rollback SQL next to it. Two minutes of work, and a bulk update on production stops feeling like a gamble.
  2. Made "not configured" an explicit state. If a storage has no home-delivery method row, the checkout doesn't invent one. It doesn't offer home delivery for that group and says so.
  3. Left the optional ones optional. min_order_amount and free_delivery_threshold can legitimately be NULL (no minimum, no free tier). The fee can't. The difference is now in the code, with a comment, instead of in someone's head.
type StorageDelivery = { fee: number; // required, validated on load freeThreshold: number | null; // null = no free tier minOrder: number | null; // null = no minimum }; function loadStorageDelivery(row: StorageRow): StorageDelivery { if (row.below_free_delivery_fee == null) { // fail loudly, never guess a price throw new MisconfiguredStorageError(row.id, 'below_free_delivery_fee'); } return { fee: Number(row.below_free_delivery_fee), freeThreshold: row.free_delivery_threshold == null ? null : Number(row.free_delivery_threshold), minOrder: row.min_order_amount == null ? null : Number(row.min_order_amount), }; } 

A check that runs before customers find it

The real fix isn't the code above, it's not finding these at checkout in the first place. A small query that lists any active storage with products for sale but no fee or no home-delivery method is worth more than any amount of defensive code:

SELECT s.id, s.name FROM storages s WHERE s.is_active AND EXISTS (SELECT 1 FROM products p WHERE p.storage_id = s.id AND p.is_active) AND ( s.below_free_delivery_fee IS NULL OR NOT EXISTS ( SELECT 1 FROM storage_shipping_methods m WHERE m.storage_id = s.id AND m.method = 'HOME' ) ); 

If it returns rows, a person should hear about it. It's not clever, but it catches the problem when someone adds the storage, not when a customer tries to pay.

Things I'd tell myself earlier

  • Put delivery rules where the trucks are. Store-level settings are fine until the second warehouse, and then they're wrong in ways that are hard to see.
  • Keep every price in the database. Fees, thresholds and minimums change with business decisions, and a deploy shouldn't be needed to change them. But "in the database" only helps if the database is actually filled in.
  • Decide what NULL means per column, and write it down. "No free tier" and "we forgot" look identical in a table.
  • Prefer a loud failure over a silent default for anything that's money.

If you run a store with more than one ship-from location, I'd like to hear how you split the cart. Per warehouse like us, or one combined fee that you balance out internally?