Skip to content

Commerce logic

Delivery and coupon rules belong in pure functions

Zone-based delivery pricing and coupon logic look like data and behave like code. Keeping them pure is what stops the quoted price and the charged price from disagreeing.

3 min readUpdated 2 Oct 20263 sections

Every store I have built eventually grows a delivery rule. It starts as free delivery above a threshold, and it becomes a table of zones with different rates, and sometimes different rates per zone depending on the order total. The zone table is a small piece of business logic that grows tentacles.

The failure mode is always the same, and it is always expensive: three code paths compute the delivery cost, one of them is stale, and a customer is quoted ৳50 and charged ৳120.

Make it a function

The whole fix is to make pricing a pure function of its inputs: a zone, a subtotal, and the rule table. No database access inside it, no clock reads that are not passed in, no branching on anything the caller did not supply. Then the checkout screen, the order endpoint and the admin estimate can all call it and be certain they agree.

lib/delivery.ts
export type Zone = "inside-dhaka" | "outside-dhaka" | "suburban" | "remote";

type Rule = { zone: Zone; flat: number; freeAbove: number | null };

const RULES: Rule[] = [
  { zone: "inside-dhaka", flat: 60, freeAbove: 3000 },
  { zone: "outside-dhaka", flat: 80, freeAbove: 5000 },
  { zone: "suburban", flat: 100, freeAbove: null },
  { zone: "remote", flat: 130, freeAbove: null },
];

export function deliveryCost(zone: Zone, subtotal: number): number {
  const rule = RULES.find((r) => r.zone === zone);
  if (!rule) throw new Error(`Unknown delivery zone: ${zone}`);
  if (isInvalidSubtotal(subtotal)) throw new Error("Invalid subtotal");

  if (rule.freeAbove !== null && subtotal >= rule.freeAbove) return 0;
  return rule.flat;
}

// Kept separate so the guard reads as a precondition, not as logic.
function isInvalidSubtotal(subtotal: number): boolean {
  return !Number.isFinite(subtotal) || subtotal < 0;
}

Two details are doing real work. The rule for an unknown zone throws rather than defaulting to zero, because a default of zero means a typo in a form silently gives away free delivery. And the negative-subtotal guard exists because this function will eventually be called with a coupon applied twice, and the second application is how a total goes below zero.

Fix the order of operations once

The subtler bug is not the rate, it is the sequence. If the discount is applied before delivery is priced, the customer can cross the free-delivery threshold with a coupon and get delivery free. If delivery is priced first, they do not. Both are defensible products. What is not defensible is different code choosing different orders.

lib/quote.ts
export type Quote = {
  subtotal: number;
  discount: number;
  delivery: number;
  total: number;
};

/**
 * The single canonical order: subtotal, then discount, then delivery on the
 * discounted subtotal, then tax-free total.
 */
export function quote(params: {
  lines: { unitPrice: number; quantity: number }[];
  zone: Zone;
  percentOff: number;
}): Quote {
  const subtotal = params.lines.reduce(
    (sum, line) => sum + line.unitPrice * line.quantity,
    0
  );

  const discount = Math.floor((subtotal * clampPercent(params.percentOff)) / 100);
  const delivery = deliveryCost(params.zone, subtotal - discount);

  return { subtotal, discount, delivery, total: subtotal - discount + delivery };
}

function clampPercent(value: number): number {
  if (!Number.isFinite(value) || value <= 0) return 0;
  return Math.min(value, 90);
}

This is the code worth testing

A pure function with no dependencies is trivially testable, and pricing is exactly the logic you cannot afford to get wrong. The table does not need to be large to be useful: boundary values around each free-delivery threshold catch most real mistakes.

lib/delivery.test.ts
import { describe, it, expect } from "vitest";
import { deliveryCost } from "./delivery";

describe("deliveryCost", () => {
  it("charges the flat rate below the free threshold", () => {
    expect(deliveryCost("inside-dhaka", 2999)).toBe(60);
  });

  it("is free exactly at the threshold", () => {
    expect(deliveryCost("inside-dhaka", 3000)).toBe(0);
  });

  it("always charges remote zones", () => {
    expect(deliveryCost("remote", 99_999)).toBe(130);
  });

  it("rejects an unknown zone rather than defaulting to free", () => {
    expect(() => deliveryCost("mars" as Zone, 1000)).toThrow();
  });

  it("rejects a negative subtotal", () => {
    expect(() => deliveryCost("inside-dhaka", -1)).toThrow();
  });
});