{"record":{"id":"8784149851d9d41f","repo":"nopSolutions/nopCommerce","slug":"this-shipment-is-already-marked-as-ready-for-pick","errorCode":null,"errorMessage":"This shipment is already marked as 'ready for pickup'","messagePattern":"This shipment is already marked as 'ready for pickup'","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Libraries/Nop.Services/Orders/OrderProcessingService.cs","lineNumber":2293,"sourceCode":"    }\r\n\r\n    /// <summary>\r\n    /// Marks a shipment as ready for pickup\r\n    /// </summary>\r\n    /// <param name=\"shipment\">Shipment</param>\r\n    /// <param name=\"notifyCustomer\">True to notify customer</param>\r\n    /// <returns>A task that represents the asynchronous operation</returns>\r\n    public virtual async Task ReadyForPickupAsync(Shipment shipment, bool notifyCustomer)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(shipment);\r\n\r\n        var order = await _orderService.GetOrderByIdAsync(shipment.OrderId) ?? throw new Exception(\"Order cannot be loaded\");\r\n\r\n        if (!order.PickupInStore)\r\n            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.\");\r\n\r\n        if (shipment.ReadyForPickupDateUtc.HasValue)\r\n            throw new Exception(\"This shipment is already marked as 'ready for pickup'\");\r\n\r\n        shipment.ReadyForPickupDateUtc = DateTime.UtcNow;\r\n        await _shipmentService.UpdateShipmentAsync(shipment);\r\n\r\n        await AddOrderNoteAsync(order, $\"Shipment# {shipment.Id} has been ready for pickup\");\r\n\r\n        if (notifyCustomer)\r\n        {\r\n            var queuedEmailIds = await _workflowMessageService.SendShipmentReadyForPickupNotificationAsync(shipment, order.CustomerLanguageId);\r\n            if (queuedEmailIds.Any())\r\n                await AddOrderNoteAsync(order, $\"\\\"Ready for pickup\\\" email (to customer) has been queued. Queued email identifiers: {string.Join(\", \", queuedEmailIds)}.\");\r\n        }\r\n\r\n        await _eventPublisher.PublishShipmentReadyForPickupAsync(shipment);\r\n    }\r\n\r\n    /// <summary>\r\n    /// Marks a shipment as delivered\r","sourceCodeStart":2275,"sourceCodeEnd":2311,"githubUrl":"https://github.com/nopSolutions/nopCommerce/blob/64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs#L2275-L2311","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait _orderProcessingService.ReadyForPickupAsync(shipment, true);\n\n// after\nif (shipment.ReadyForPickupDateUtc.HasValue)\n    return; // already ready\n\nawait _orderProcessingService.ReadyForPickupAsync(shipment, true);","handlingStrategy":"validation","validationCode":"if (shipment.ReadyForPickupDateUtc.HasValue)\n    return; // already marked ready\n\nawait _orderProcessingService.ReadyForPickupAsync(shipment, notifyCustomer);","typeGuard":null,"tryCatchPattern":"try { await _orderProcessingService.ReadyForPickupAsync(shipment, true); }\ncatch (NopException ex) when (ex.Message.Contains(\"already marked as 'ready for pickup'\"))\n{\n    // idempotent: treat as success\n}","preventionTips":["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."],"tags":["shipment","pickup-in-store","idempotency","order-processing"],"backgroundTag":null,"analyzedSha":"64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2","analyzedAt":"2026-08-13T21:19:38.062Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}