nopSolutions/nopCommerce · error · Exception

This shipment is already marked as 'ready for pickup'

Error message

This shipment is already marked as 'ready for pickup'

What it means

Thrown by OrderProcessingService.ReadyForPickupAsync when a shipment is already flagged ready for pickup (shipment.ReadyForPickupDateUtc has a value). It is an idempotency guard that prevents a store admin or API caller from double-marking the same in-store-pickup shipment as ready, which would otherwise reset the timestamp and re-queue customer notifications. The check runs only after the order has been confirmed as a PickupInStore order, so hitting it means the shipment is in a valid pickup workflow but is further along than the caller assumed.

Source

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

    }

    /// <summary>
    /// Marks a shipment as ready for pickup
    /// </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 ReadyForPickupAsync(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 marked as 'ready for pickup'. The order has been placed without 'pickup in store' shipping option.");

        if (shipment.ReadyForPickupDateUtc.HasValue)
            throw new Exception("This shipment is already marked as 'ready for pickup'");

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

        await AddOrderNoteAsync(order, $"Shipment# {shipment.Id} has been ready for pickup");

        if (notifyCustomer)
        {
            var queuedEmailIds = await _workflowMessageService.SendShipmentReadyForPickupNotificationAsync(shipment, order.CustomerLanguageId);
            if (queuedEmailIds.Any())
                await AddOrderNoteAsync(order, $"\"Ready for pickup\" email (to customer) has been queued. Queued email identifiers: {string.Join(", ", queuedEmailIds)}.");
        }

        await _eventPublisher.PublishShipmentReadyForPickupAsync(shipment);
    }

    /// <summary>
    /// Marks a shipment as delivered

View on GitHub (pinned to 64bdf2ff08)

Solutions

  1. Before calling ReadyForPickupAsync, guard with: if (shipment.ReadyForPickupDateUtc.HasValue) return; (or skip / treat as success).
  2. Make the calling endpoint idempotent by keying off shipment.Id so duplicate events collapse to one effective call.
  3. In the UI, disable or hide the 'ready for pickup' action once shipment.ReadyForPickupDateUtc is set.
  4. If calling from an integration, fetch the shipment fresh and check ReadyForPickupDateUtc immediately before the call to close the check-then-act race.

Example fix

// before
await _orderProcessingService.ReadyForPickupAsync(shipment, true);

// after
if (shipment.ReadyForPickupDateUtc.HasValue)
    return; // already ready

await _orderProcessingService.ReadyForPickupAsync(shipment, true);
Defensive patterns

Strategy: validation

Validate before calling

if (shipment.ReadyForPickupDateUtc.HasValue)
    return; // already marked ready

await _orderProcessingService.ReadyForPickupAsync(shipment, notifyCustomer);

Try / catch

try { await _orderProcessingService.ReadyForPickupAsync(shipment, true); }
catch (NopException ex) when (ex.Message.Contains("already marked as 'ready for pickup'"))
{
    // idempotent: treat as success
}

Prevention

When it happens

Trigger: Calling ReadyForPickupAsync(shipment, notifyCustomer) on a Shipment whose ReadyForPickupDateUtc is non-null. This happens on a duplicate admin 'Mark as ready for pickup' action, a retry of a webhook/integration that re-fires the call, or two concurrent requests operating on the same shipment row.

Common situations: Store staff clicking the ready-for-pickup button twice; an ERP/order-sync integration that does not dedupe its events; a webhook handler that reprocesses the same shipment after a transient failure; idempotency keys missing on a custom checkout endpoint.

Related errors


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