# Shipping rates are calculated accurately based on weight, distance, or method

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

## Why it matters

A flat shipping rate applied to every order regardless of weight, destination, or carrier method is a CWE-682 (Incorrect Calculation) defect that costs money in both directions: the store eats losses on heavy or long-distance shipments, or overcharges customers on lightweight local orders — which triggers chargebacks and cart abandonment. ISO 25010:2011 functional correctness requires that computed outputs match business rules. When every order is charged the same $5.00 regardless of a 50 lb appliance vs. a greeting card, the store is either subsidizing shipping losses or alienating customers who see inflated charges and leave.

## Severity rationale

Critical because a flat-rate-for-all calculation directly produces financial losses on heavy shipments or overcharges that drive customer disputes and abandonment.

## Remediation

Implement dynamic shipping calculation in `lib/shipping.ts` that uses at least 2 of 4 factors — weight, destination zone, method, or carrier API:

```ts
// lib/shipping.ts
async function calculateShippingCost(
  items: OrderItem[],
  destination: Address,
  method: ShippingMethod
): Promise<number> {
  const totalWeight = items.reduce((sum, item) => sum + item.weight * item.quantity, 0)

  if (method === 'carrier') {
    const shipment = await easypost.shipment.create({
      from_address: { city: ORIGIN_CITY, state: ORIGIN_STATE },
      to_address: { city: destination.city, state: destination.state },
      parcel: { weight: totalWeight },
    })
    return Number(shipment.rates[0].rate) * 100 // convert to cents
  }

  const base = getBaseRate(destination.state)
  const weightSurcharge = Math.ceil(totalWeight / 5) * 50 // 50¢ per 5 lbs
  return base + weightSurcharge
}
```

Delete any hardcoded constant returned from a `calculateShipping` function and replace with this parameterised implementation.

## Detection

- **ID:** `rate-calculation-accuracy`
- **Severity:** `critical`
- **What to look for:** Enumerate all shipping rate calculation functions. For each, classify which factors are used: (1) product weight, (2) destination distance/zone, (3) selected shipping method, (4) carrier API integration. Count the number of factors considered. Report: X of 4 factors used.
- **Pass criteria:** A shipping calculation function exists that applies rates based on at least 2 of 4 factors (weight, distance/zone, method selection, or carrier API). Rates are dynamically computed from order data, not hardcoded as a single flat rate for all orders.
- **Fail criteria:** No shipping calculation logic exists, or all orders use a single hardcoded flat rate regardless of weight, destination, or method. Do not pass when a function named `calculateShipping` exists but returns a constant value.
- **Skip (N/A) when:** Search `package.json` dependencies and all route/lib directories for shipping-related terms. If the store sells only digital products with no physical shipping, or shipping is fully managed by a third-party service with no in-house logic, skip.
- **Detail on fail:** `"Shipping calculation uses 0 of 4 factors. All orders charged flat $5.00 regardless of weight or destination."` or `"Weight-based function found but excludes items over 50 lbs — only 1 of 4 factors considered."`
- **Cross-reference:** Related to `ecommerce-shipping-tax.shipping-calc.recalc-on-address-change` (recalculation requires dynamic rates) and `ecommerce-shipping-tax.checkout-integration.shipping-preview` (displayed rate must match calculation).
- **Remediation:** Implement dynamic shipping calculation in `lib/shipping.ts` based on order characteristics:

  ```ts
  // lib/shipping.ts
  async function calculateShippingCost(items: OrderItem[], destination: Address, method: ShippingMethod): Promise<number> {
    const totalWeight = items.reduce((sum, item) => sum + item.weight * item.quantity, 0)

    // Option 1: Integration with shipping API
    if (method === 'carrier') {
      const shipment = await easypost.shipment.create({
        from_address: { city: 'origin_city' },
        to_address: { city: destination.city, state: destination.state },
        parcel: { weight: totalWeight }
      })
      return shipment.rates[0].rate // in cents
    }

    // Option 2: Weight-based lookup table
    const baseCost = getBaseShippingRate(destination.state)
    const weightFactor = Math.ceil(totalWeight / 5) * 0.5 // $0.50 per 5 lbs
    return (baseCost + weightFactor) * 100 // convert to cents
  }
  ```

## External references

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

Taxons: data-integrity

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