Skip to content

Cancel Customer Order

Overview

The Cancel Order API allows authenticated customers to cancel their pending orders. This mutation validates order ownership, checks cancellation eligibility, and reverses inventory allocation when successful.

Endpoint

GraphQL Mutation URL: POST /api/graphql

Authentication

Required: Bearer Token (Sanctum)
Scope: Customer only (order must belong to authenticated customer)

Request

GraphQL Mutation Schema

graphql
mutation CreateCancelOrder($input: CancelOrderInput!) {
  createCancelOrder(input: $input) {
    cancelOrder {
      success      # Boolean: true if cancel succeeded
      message      # String: response message
      orderId      # Int: the order ID
      status       # String: final order status
    }
  }
}

Input Parameters

ParameterTypeRequiredDescription
orderIdIntegerYesThe numeric ID of the order to cancel

Response

Success Response (200 OK)

json
{
  "data": {
    "createCancelOrder": {
      "cancelOrder": {
        "success": true,
        "message": "Order has been canceled successfully",
        "orderId": 2,
        "status": "canceled"
      }
    }
  }
}

Failure Response - Cannot Cancel

When order cannot be canceled (already canceled, shipped, or completed):

json
{
  "data": {
    "createCancelOrder": {
      "cancelOrder": {
        "success": false,
        "message": "Order cannot be canceled. It may have already been processed, shipped, or canceled",
        "orderId": 2,
        "status": "canceled"
      }
    }
  }
}

Error Response - Unauthorized (401)

json
{
  "errors": [
    {
      "message": "Unauthenticated",
      "extensions": {
        "code": "UNAUTHENTICATED"
      }
    }
  ]
}

Error Response - Order Not Found (404)

When order ID doesn't exist or belongs to a different customer:

json
{
  "data": {
    "createCancelOrder": null
  }
}

Business Logic

Cancellation Eligibility

An order can be canceled if:

  • Order status is pending (not shipped, completed, or already canceled)
  • Order belongs to the authenticated customer
  • Order is not a composite/grouped order (or all child items are cancelable)

When Cancellation Succeeds

  1. Order Status: Changed from pendingcanceled
  2. Inventory: All ordered items are returned to stock
  3. Qty Canceled: Updated to match ordered quantity for all items
  4. Events Fired:
    • order.cancel.before — before cancel operations
    • order.cancel.after — after successful cancellation
  5. Database: Order and item records updated atomically

When Cancellation Fails

  • Order status remains unchanged
  • No inventory is returned
  • Appropriate error message returned

State Transitions

pending → canceled (valid)
shipped → (cannot cancel)
completed → (cannot cancel)
canceled → (cannot re-cancel)

Use Cases

  • Cancel unwanted orders before processing
  • Allow customers to self-service cancel pending orders
  • Automatically restore inventory when customers cancel
  • Reduce support requests by enabling customer-driven cancellation
  • Workflow: Customer views order → clicks cancel → inventory restored

Testing Examples

Test Case 1: Cancel Pending Order (Success)

Precondition: Order #2 with status pending exists for customer #2

Query:

graphql
mutation {
  createCancelOrder(input: { orderId: 2 }) {
    cancelOrder {
      success
      message
      orderId
      status
    }
  }
}

Expected Result:

json
{
  "success": true,
  "message": "Order has been canceled successfully",
  "orderId": 2,
  "status": "canceled"
}

Database Verification:

sql
SELECT id, status FROM orders WHERE id = 2;
-- Result: id=2, status='canceled'

SELECT order_id, qty_ordered, qty_canceled FROM order_items WHERE order_id = 2;
-- Result: qty_canceled should equal qty_ordered for all items

Test Case 2: Re-Cancel Already Canceled Order (Failure)

Precondition: Order #2 with status canceled (from Test Case 1)

Query:

graphql
mutation {
  createCancelOrder(input: { orderId: 2 }) {
    cancelOrder {
      success
      message
      orderId
      status
    }
  }
}

Expected Result:

json
{
  "success": false,
  "message": "Order cannot be canceled. It may have already been processed, shipped, or canceled",
  "orderId": 2,
  "status": "canceled"
}

No Database Changes: Order remains in canceled status

Test Case 3: Cancel Without Authentication (Failure)

Query (without Authorization header):

bash
curl -X POST http://127.0.0.1:8000/api/graphql \
  -H "Content-Type: application/json" \
  -H "X-STOREFRONT-KEY: pk_storefront_qrr4vsdbs6xNpL7DN0GHUcB0XnhjnjIS" \
  -d '{"query":"mutation { createCancelOrder(input: { orderId: 2 }) { cancelOrder { success message } } }"}'

Expected Result:

HTTP 401 Unauthorized
{
  "errors": [
    {
      "message": "Unauthenticated",
      "extensions": {"code": "UNAUTHENTICATED"}
    }
  ]
}

Implementation Details

Files Involved

FilePurpose
CancelOrderInput.phpInput DTO with orderId parameter
CancelOrderProcessor.phpHandles mutation business logic
CancelOrder.phpResponse model with success/message/orderId/status

Key Components

CancelOrderProcessor:

  • Authenticates request via Auth::guard('sanctum')
  • Scopes orders to authenticated customer only
  • Delegates to OrderRepository::cancel() for business logic
  • Returns CancelOrder response model

OrderRepository::cancel():

  • Validates $order->canCancel() method
  • Fires order.cancel.before event
  • Iterates order items and returns inventory
  • Updates qty_canceled for each item
  • Calls updateOrderStatus('canceled')
  • Fires order.cancel.after event

HTTP Status Codes

CodeScenario
200Mutation executed (check success field for actual result)
400Invalid request (missing storefront key, malformed query)
401Unauthorized (missing or invalid Bearer token)
422Validation error (invalid orderId type)

Rate Limiting

No specific rate limiting for cancel mutations. General API rate limits apply per storefront key.

Notes

  • Customer isolation: Orders are scoped to the authenticated customer. A customer cannot cancel another customer's order.
  • Inventory restoration: When an order is canceled, all items are returned to inventory automatically.
  • Status tracking: The operation is governed by Bagisto's order cancellation rules and configuration.
  • Event hooks: Extensions can listen to order.cancel.before and order.cancel.after events.
  • Localization: All messages are localized to the customer's language.

Released under the MIT License.