# Free shipping thresholds are applied correctly

- **Pattern:** `ab-001223` (`ecommerce-shipping-tax.shipping-calc.free-shipping-threshold`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001223
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001223)

## Why it matters

A free shipping threshold that is hardcoded in two places with different values — $50 in `lib/shipping.ts` and $100 in `components/CartSummary.tsx` — is a CWE-682 defect that will apply free shipping to some customers who don't qualify and deny it to others who do. ISO 25010:2011 functional correctness requires a single source of truth for business rule constants. Inconsistent thresholds also break customer trust: the cart banner says "Add $10 for free shipping" but the checkout still charges $12.99 after the customer crosses the threshold. This produces support tickets and abandoned carts at the highest-intent stage of the funnel.

## Severity rationale

Medium because threshold inconsistency produces incorrect customer-facing charges and promotional failures that erode trust and generate refund requests.

## Remediation

Define the threshold once in `lib/shipping.ts` (or a shared config file) and reference it everywhere — never hardcode the value in component files:

```ts
// lib/shipping.ts
export const FREE_SHIPPING_THRESHOLD_CENTS = 10_000 // $100.00

export function applyFreeShippingThreshold(
  subtotalCents: number,
  baseShipping: number
): number {
  return subtotalCents >= FREE_SHIPPING_THRESHOLD_CENTS ? 0 : baseShipping
}
```

Import `FREE_SHIPPING_THRESHOLD_CENTS` in `components/CartSummary.tsx` for the promotional banner calculation so the display threshold and the charge threshold are always in sync. If the threshold is configurable per region or campaign, store it in an environment variable or database row, not a scattered literal.

## Detection

- **ID:** `free-shipping-threshold`
- **Severity:** `medium`
- **What to look for:** List all free shipping threshold logic in the codebase. Count the number of conditions that must be met for free shipping (subtotal threshold, method restriction, region restriction). Verify the threshold value is stored in a configurable location (env var, config file, database) rather than scattered across multiple files.
- **Pass criteria:** Free shipping logic exists with at least 1 configurable threshold value (e.g., `FREE_SHIPPING_THRESHOLD = 10000` in a config or env) that is checked during shipping calculation. When the order subtotal meets or exceeds the threshold, shipping cost is set to $0.00 and the UI reflects this.
- **Fail criteria:** Free shipping threshold logic is missing when business rules define one, or the threshold is hardcoded in multiple locations with inconsistent values, or qualifying orders still show a non-zero shipping cost.
- **Skip (N/A) when:** The business does not offer free shipping thresholds (search for "free shipping" or threshold-related constants across the codebase; if none found, skip).
- **Detail on fail:** `"Free shipping threshold found in 2 files with different values: $50 in lib/shipping.ts and $100 in components/CartSummary.tsx."` or `"Threshold logic exists but cart UI still shows $12.99 for orders over the threshold."`
- **Remediation:** Implement a configurable free shipping threshold in `lib/shipping.ts`:

  ```ts
  const FREE_SHIPPING_THRESHOLD = 10000 // $100 in cents

  function calculateOrderShipping(subtotalCents: number, method: ShippingMethod): number {
    if (subtotalCents >= FREE_SHIPPING_THRESHOLD) {
      return 0
    }
    return calculateShippingCost(items, address, method)
  }
  ```

---

## External references

- cwe CWE-682
- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001223
