# Tax computation excludes shipping costs unless jurisdiction requires shipping tax

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

## Why it matters

Approximately 12 US states tax shipping charges; the remaining 38 do not. When shipping is always included in the taxable subtotal — or always excluded with no jurisdiction check — the store either overcharges customers in non-taxing states or under-collects tax in states like New York, Illinois, and Pennsylvania (CWE-682, ISO 25010:2011 functional correctness). The EU VAT Directive 2006/112/EC treats shipping as part of the taxable supply, applying a different rule than most US states. US state shipping taxability rules are jurisdiction-specific and cannot be flattened to a global default. When no code comment documents the shipping tax policy, the decision becomes invisible and unauditble.

## Severity rationale

High because shipping tax treatment errors produce systematic over- or under-collection across every order that involves a jurisdiction with non-default shipping tax rules, compounding into audit exposure.

## Remediation

Implement jurisdiction-aware shipping tax in `lib/tax.ts` with a documented configuration object:

```ts
// lib/tax.ts
// Source: state revenue authority rulings. Last reviewed: 2024-01.
// US states that tax shipping charges as of 2024:
const STATES_TAXING_SHIPPING = new Set(['NY', 'IL', 'PA', 'WA', 'GA', 'IN', 'TX', 'KS'])

function buildTaxableSubtotal(
  items: OrderItem[],
  shippingCents: number,
  address: Address
): number {
  const productSubtotal = items
    .filter(i => !i.is_tax_exempt)
    .reduce((sum, i) => sum + i.price * i.quantity, 0)

  const taxShipping = address.country === 'US'
    ? STATES_TAXING_SHIPPING.has(address.state)
    : true // EU VAT applies to shipping

  return productSubtotal + (taxShipping ? shippingCents : 0)
}
```

Replace any global `taxableSubtotal = productSubtotal + shippingCents` with this function and add a comment citing the source of the jurisdiction list.

## Detection

- **ID:** `shipping-tax-handling`
- **Severity:** `high`
- **What to look for:** Before evaluating, quote the tax calculation function that computes the taxable subtotal. Classify whether shipping cost is: (1) always included in the taxable subtotal, (2) always excluded, or (3) conditionally included based on jurisdiction. If conditional, enumerate all jurisdictions that tax shipping and count the total.
- **Pass criteria:** Shipping tax handling is jurisdiction-aware: either shipping is excluded from the taxable subtotal by default with a configurable list of at least 3 states where it is included (e.g., NY, IL, PA), or a tax service API handles shipping tax automatically. A code comment must document the policy.
- **Fail criteria:** Shipping is always included in the taxable subtotal regardless of jurisdiction, or always excluded without any jurisdiction awareness, or no documentation exists for the shipping tax policy.
- **Skip (N/A) when:** Shipping is always free (no shipping cost exists in the calculation) or the business is non-US with VAT applied to the total including shipping by default.
- **Detail on fail:** `"Shipping cost of $12.99 always included in taxable subtotal for all 50 states. Shipping is only taxable in ~12 states."` or `"Shipping excluded from tax calculation globally — no jurisdiction-specific override for NY, IL, PA."`
- **Cross-reference:** Related to `ecommerce-shipping-tax.regional-compliance.shipping-tax-per-jurisdiction` (per-jurisdiction rules) and `ecommerce-shipping-tax.tax-computation.jurisdiction-detection` (jurisdiction must be detected first).
- **Remediation:** Implement jurisdiction-aware shipping tax in `lib/tax.ts`:

  ```ts
  const STATES_TAXING_SHIPPING = ['NY', 'IL', 'PA'] // example jurisdictions

  function calculateTax(items: OrderItem[], shipping: number, address: Address): number {
    let taxableSubtotal = items.reduce((sum, item) => {
      return sum + (item.is_tax_exempt ? 0 : item.price * item.quantity)
    }, 0)

    if (STATES_TAXING_SHIPPING.includes(address.state)) {
      taxableSubtotal += shipping
    }

    const rate = getTaxRate(address)
    return Math.round(taxableSubtotal * rate)
  }
  ```

## External references

- cwe CWE-682
- iso-25010 functional-correctness
- external us-state-shipping-taxability-rules — https://www.taxjar.com/resources/sales-tax/taxability/shipping
- external eu-vat-directive-2006-112-ec — https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32006L0112

Taxons: regulatory-conformance, data-integrity

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