nopSolutions/nopCommerce · warning · Exception

This shipment is already shipped

Error message

This shipment is already shipped

What it means

Thrown by ShipAsync when shipment.ShippedDateUtc already has a value. The shipment was already marked shipped, so re-shipping is idempotency-violating and refused. Uses plain Exception.

Source

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

    }

    /// <summary>
    /// Send a shipment
    /// </summary>
    /// <param name="shipment">Shipment</param>
    /// <param name="notifyCustomer">True to notify customer</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public virtual async Task ShipAsync(Shipment shipment, bool notifyCustomer)
    {
        ArgumentNullException.ThrowIfNull(shipment);

        var order = await _orderService.GetOrderByIdAsync(shipment.OrderId) ?? throw new Exception("Order cannot be loaded");

        if (order.PickupInStore)
            throw new Exception("This shipment is can't be shipped. The order has been placed with 'pickup in store' shipping option.");

        if (shipment.ShippedDateUtc.HasValue)
            throw new Exception("This shipment is already shipped");

        shipment.ShippedDateUtc = DateTime.UtcNow;
        await _shipmentService.UpdateShipmentAsync(shipment);

        //process products with "Multiple warehouse" support enabled
        await BookReservedInventoryAsync(shipment, string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.Ship"), shipment.OrderId));

        //check whether we have more items to ship
        if (await _orderService.HasItemsToAddToShipmentAsync(order) || await _orderService.HasItemsToShipAsync(order))
            order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped;
        else
            order.ShippingStatusId = (int)ShippingStatus.Shipped;
        await _orderService.UpdateOrderAsync(order);

        //add a note
        await AddOrderNoteAsync(order, $"Shipment# {shipment.Id} has been sent");

        if (notifyCustomer)

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Guard the caller: check shipment.ShippedDateUtc.HasValue before calling ShipAsync and treat it as a no-op.
  2. Add idempotency at the API/controller layer so duplicate requests are deduplicated.
  3. Disable the Ship control in the UI once ShippedDateUtc is set.

Example fix

// before
await _orderProcessingService.ShipAsync(shipment, notify);

// after
if (shipment.ShippedDateUtc.HasValue)
    return Ok("Already shipped.");
await _orderProcessingService.ShipAsync(shipment, notify);
Defensive patterns

Strategy: validation

Validate before calling

if (shipment.ShippedDateUtc.HasValue)
    return Ok($"Shipment {shipment.Id} already shipped on {shipment.ShippedDateUtc:o}.");

await _orderProcessingService.ShipAsync(shipment, notifyCustomer);

Type guard

bool IsAlreadyShipped(Shipment shipment) => shipment.ShippedDateUtc.HasValue;

Try / catch

try
{
    await _orderProcessingService.ShipAsync(shipment, notifyCustomer);
}
catch (Exception ex) when (ex.Message == "This shipment is already shipped")
{
    // idempotent success - return the existing shipped state
    return Ok("Shipment was already shipped.");
}

Prevention

When it happens

Trigger: Double-click on the Ship button; a retried API call; scheduled job reprocessing the same shipment; webhook redelivery triggering Ship twice.

Common situations: UI double-submit; network retry with no idempotency key; concurrent admin operators; background job crash-and-retry.

Related errors


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