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
- Before calling ReadyForPickupAsync, guard with: if (shipment.ReadyForPickupDateUtc.HasValue) return; (or skip / treat as success).
- Make the calling endpoint idempotent by keying off shipment.Id so duplicate events collapse to one effective call.
- In the UI, disable or hide the 'ready for pickup' action once shipment.ReadyForPickupDateUtc is set.
- 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
- Disable the 'ready for pickup' UI action once shipment.ReadyForPickupDateUtc is set.
- Make the calling endpoint idempotent, keyed on shipment.Id.
- Fetch the shipment fresh immediately before the call to narrow the race window.
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
- This shipment is already delivered
- This shipment is already shipped
- This shipment is not yet ready for pickup
- Cannot do cancel for order.
- recurringCyclesError
AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13).
Data as JSON: /api/errors/8784149851d9d41f.
Report an issue: GitHub.