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.
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.
Implement a cancellation route at app/api/orders/[id]/cancel/route.ts with explicit status validation before allowing the transition.
// 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 })
}
ID: ecommerce-order-management.cancellation-refunds.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:
// 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 })
}