# Post-shipment cancellation requires special handling or admin approval

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

## Why it matters

An order that was shipped yesterday cannot be cancelled the same way as an order placed five minutes ago. If the same code path applies to both, a customer can trigger a 'cancelled' transition on an already-shipped order — potentially generating an automated refund while the package is still in transit and arrives at the customer's door. This is a CWE-285 failure: the system grants a cancellation action to a user for an order state where that action should be restricted to admin review. Post-shipment cases require a separate branch: queue the request, notify the admin, and communicate a different resolution timeline to the customer.

## Severity rationale

Medium because a single undifferentiated cancellation path enables customers to cancel shipped orders with no admin oversight, creating refund and logistics conflicts.

## Remediation

Add a second branch to your cancellation handler at `app/api/orders/[id]/cancel/route.ts` that routes post-shipment requests to an admin review queue rather than auto-cancelling.

```ts
const POST_SHIPMENT = ['shipped', 'delivered']

if (POST_SHIPMENT.includes(order.status)) {
  await db.cancellationRequests.create({
    data: {
      orderId: order.id,
      userId: session.user.id,
      requestedAt: new Date(),
      status: 'pending_review',
      reason: body.reason,
    },
  })
  await sendAdminNotification({ type: 'post_shipment_cancellation', orderId: order.id })
  return Response.json({
    message: 'Request received. Our team will contact you within 1–2 business days.',
  })
}
```

## Detection

- **ID:** `postshipment-cancellation`
- **Severity:** `medium`
- **What to look for:** Check whether the cancellation logic explicitly distinguishes between pre-shipment and post-shipment orders. Count the number of distinct code branches handling cancellation for different order statuses. For orders that are already `shipped` or `delivered`, look for at least 1 of these special handling patterns: a separate endpoint or flow for post-shipment cancellation requests, a flag or queue entry that routes the request to an admin for approval, or a clear user-facing error message (quote the exact message if found) explaining that standard cancellation is not available. The key question is: does the system have at least 2 distinct branches — one for pre-shipment and one for post-shipment?
- **Pass criteria:** The cancellation flow explicitly handles the post-shipment case differently from pre-shipment, with at least 2 distinct code branches. Shipped or delivered orders either cannot be cancelled through the standard customer path (with a helpful error message directing the customer to contact support or initiate a return) or enter a separate admin-approval queue. The system does not silently apply a standard cancellation to an already-shipped order. A single code path that treats all statuses identically does not count as pass.
- **Fail criteria:** The cancellation handler treats all order statuses the same (only 1 code branch) — a shipped order can be cancelled through the exact same path as a pending order with no differentiation, no admin notification, and no flagging for special handling.
- **Skip (N/A) when:** The project does not support post-shipment cancellations at all and clearly communicates this to customers at the time of purchase (e.g., "All sales are final once shipped"). Or the project delegates all post-shipment handling to an external returns management platform. The cancellation handler explicitly rejects all non-pre-shipment statuses.
- **Detail on fail:** `"The cancellation endpoint at POST /api/orders/[id]/cancel applies the same logic regardless of order status (1 code branch, 0 post-shipment guards). A shipped order can be cancelled by the customer with no admin notification or special handling."`
- **Remediation:** Add explicit post-shipment routing in your cancellation handler at `app/api/orders/[id]/cancel/route.ts`:

  ```ts
  const POST_SHIPMENT_STATUSES = ['shipped', 'delivered']

  if (POST_SHIPMENT_STATUSES.includes(order.status)) {
    // Route to admin queue instead of auto-cancelling
    await db.cancellationRequests.create({
      data: {
        orderId: order.id,
        userId: session.user.id,
        requestedAt: new Date(),
        status: 'pending_review',
        reason: body.reason,
      },
    })
    // Notify admin
    await sendAdminNotification({ type: 'post_shipment_cancellation_request', orderId: order.id })
    return Response.json({
      success: true,
      message: 'Your cancellation request has been received and is under review. ' +
               'Our team will contact you within 1-2 business days.',
    })
  }
  ```

## External references

- cwe CWE-285
- cwe CWE-841
- owasp A01

Taxons: access-control, data-integrity

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