# Pre-shipment cancellation allowed and functional

- **Pattern:** `ab-001139` (`ecommerce-order-management.cancellation-refunds.preshipment-cancellation`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001139
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001139)

## Why it matters

Missing pre-shipment cancellation is a direct OWASP A01 (Broken Access Control) and CWE-285 gap: a customer has a legitimate right to cancel an order they just placed, and if no endpoint exists to exercise that right, they lose money or are forced to contact support for a routine action. Beyond customer experience, an absent cancellation flow means that if a payment is captured for an order that should not have been fulfilled — duplicate order, payment error, customer mistake — there is no programmatic path to unwind it. The handler must also guard against cancelling already-shipped orders through the same path, which could incorrectly trigger a refund for a package in transit.

## Severity rationale

High because the absence of a guarded cancellation endpoint forces customers to contact support for a routine action and creates no programmatic path to unwind incorrectly captured payments.

## Remediation

Implement a cancellation route at `app/api/orders/[id]/cancel/route.ts` with explicit status validation before allowing the transition.

```ts
// app/api/orders/[id]/cancel/route.ts
export async function POST(req: Request, { params }: { params: { id: string } }) {
  const session = await getServerSession()
  if (!session?.user) return new Response('Unauthorized', { status: 401 })

  const order = await db.orders.findUnique({ where: { id: params.id, userId: session.user.id } })
  if (!order) return new Response('Not found', { status: 404 })

  const CANCELLABLE = ['pending', 'confirmed']
  if (!CANCELLABLE.includes(order.status)) {
    return Response.json(
      { error: `Cannot cancel an order with status '${order.status}'.` },
      { status: 409 }
    )
  }

  await transitionOrder(params.id, 'cancelled', session.user.id, 'Customer requested cancellation')
  return Response.json({ success: true })
}
```

## Detection

- **ID:** `preshipment-cancellation`
- **Severity:** `high`
- **What to look for:** Find the order cancellation endpoint or handler — count every route or function that can set an order to `cancelled` status. This is typically a POST to `/api/orders/[id]/cancel` or a PATCH that sets status to `cancelled`. For each cancellation path, enumerate which order statuses it accepts. Verify that orders in `pending` or `confirmed` status (at least 2 pre-shipment statuses) can successfully be cancelled. Check whether the handler validates that the order is in a pre-shipment state before allowing cancellation. Quote the exact guard clause if one exists.
- **Pass criteria:** A cancellation endpoint or action exists and successfully transitions pre-shipment orders (`pending`, `confirmed`) to `cancelled` status. The handler validates the order's current status against at least 2 allowed pre-shipment statuses. Attempting to cancel an already-shipped or delivered order is rejected with an appropriate error (HTTP 409 or similar) or redirected to a different flow. A cancellation handler with no status guard does not count as pass.
- **Fail criteria:** No cancellation endpoint exists (0 cancel handlers found). Or the endpoint exists but does not function — it throws unhandled errors, returns incorrect status codes, or silently fails without changing the order status. Or the endpoint allows cancellation of already-shipped orders through the standard customer path with no guard.
- **Skip (N/A) when:** The project has no order cancellation feature — all cancellations are handled manually by admin staff via direct database access or an external platform. No cancel route or handler exists.
- **Detail on fail:** Describe the specific gap. Example: `"No customer-facing cancellation endpoint found. 0 cancel handlers exist. The only way to cancel an order is for an admin to change the status directly in the database."` or `"A cancel endpoint exists at DELETE /api/orders/[id] but it accepts any order status, including shipped orders, with 0 status guards."`
- **Remediation:** Implement a cancellation handler at `app/api/orders/[id]/cancel/route.ts` with explicit pre-shipment state validation:

  ```ts
  // app/api/orders/[id]/cancel/route.ts
  export async function POST(req: Request, { params }: { params: { id: string } }) {
    const session = await getServerSession()
    if (!session?.user) return new Response('Unauthorized', { status: 401 })

    const order = await db.orders.findUnique({
      where: { id: params.id, userId: session.user.id },
    })
    if (!order) return new Response('Not found', { status: 404 })

    const CANCELLABLE_STATUSES = ['pending', 'confirmed']
    if (!CANCELLABLE_STATUSES.includes(order.status)) {
      return Response.json(
        { error: `Cannot cancel an order with status '${order.status}'.` +
                 ` Contact support for post-shipment cancellation.` },
        { status: 409 }
      )
    }

    await transitionOrder(params.id, 'cancelled', session.user.id, 'Customer requested cancellation')
    return Response.json({ success: true })
  }
  ```

## External references

- cwe CWE-285
- cwe CWE-841
- owasp A01
- iso-25010 functional-correctness

Taxons: access-control, data-integrity

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