nopSolutions/nopCommerce · error · NopException

Cannot do cancel for order.

Error message

Cannot do cancel for order.

What it means

Thrown by OrderProcessingService.CancelOrderAsync when CanCancelOrder(order) returns false. CanCancelOrder returns false only when order.OrderStatus == OrderStatus.Cancelled, so this is an idempotency guard: you cannot cancel an order that is already cancelled. Cancellation triggers inventory return, reward-point return, gift-card history deletion, and recurring-payment cancellation, all of which must not run twice.

Source

Thrown at src/Libraries/Nop.Services/Orders/OrderProcessingService.cs:2394

        if (order.OrderStatus == OrderStatus.Cancelled)
            return false;

        return true;
    }

    /// <summary>
    /// Cancels order
    /// </summary>
    /// <param name="order">Order</param>
    /// <param name="notifyCustomer">True to notify customer</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task CancelOrderAsync(Order order, bool notifyCustomer)
    {
        ArgumentNullException.ThrowIfNull(order);

        if (!CanCancelOrder(order))
            throw new NopException("Cannot do cancel for order.");

        //cancel order
        await SetOrderStatusAsync(order, OrderStatus.Cancelled, notifyCustomer);

        //notify store owner
        var currentCustomer = await _workContext.GetCurrentCustomerAsync();
        if (order.CustomerId == currentCustomer.Id)
            await _workflowMessageService.SendOrderCancelledStoreOwnerNotificationAsync(order, _localizationSettings.DefaultAdminLanguageId);

        //add a note
        await AddOrderNoteAsync(order, "Order has been cancelled");

        //return (add) back redeemded reward points
        await ReturnBackRedeemedRewardPointsAsync(order);

        //delete gift card usage history
        if (_orderSettings.DeleteGiftCardUsageHistory)
            await _giftCardService.DeleteGiftCardUsageHistoryAsync(order);

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Guard with the public helper: if (!_orderProcessingService.CanCancelOrder(order)) return; or check order.OrderStatus != OrderStatus.Cancelled before calling.
  2. Make the cancel endpoint idempotent (key off order.Id) so duplicate events are no-ops.
  3. Disable the 'Cancel' UI action when order.OrderStatus == Cancelled.
  4. In integrations, fetch the order fresh right before cancelling to reduce the race window.

Example fix

// before
await _orderProcessingService.CancelOrderAsync(order, true);

// after
if (!_orderProcessingService.CanCancelOrder(order))
    return;

await _orderProcessingService.CancelOrderAsync(order, true);
Defensive patterns

Strategy: validation

Validate before calling

if (!_orderProcessingService.CanCancelOrder(order))
    return;

await _orderProcessingService.CancelOrderAsync(order, true);

Try / catch

try { await _orderProcessingService.CancelOrderAsync(order, true); }
catch (NopException ex) when (ex.Message == "Cannot do cancel for order.")
{
    // already cancelled; treat as no-op
}

Prevention

When it happens

Trigger: Calling CancelOrderAsync on an Order whose OrderStatus is already Cancelled. Happens on duplicate cancel requests, retries, or an admin re-clicking Cancel after the status flipped.

Common situations: Admin UI double-click; an order-management integration that re-sends cancel events; a background job that cancels orders and re-runs after a failure; concurrent cancel requests.

Related errors


AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13). Data as JSON: /api/errors/e134935a0b701e79. Report an issue: GitHub.