Order Shipping & Fulfillment
The Orders module manages the full shipping lifecycle from shipment creation through delivery, including partial fulfillment, real-time tracking sync, and NDR (Non-Delivery Report) workflows.
Shipment Model
Each shipment is linked to an order and optionally to specific order items (for partial fulfillment):
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
order_id |
UUID | Parent order |
carrier_module |
string | Shipping provider module alias (e.g., shipping-delhivery) |
awb_number |
string | Air Waybill / tracking number |
status |
string | pending, picked_up, in_transit, out_for_delivery, delivered, rto_initiated, rto_received, cancelled |
tracking_url |
string | Carrier tracking page URL |
label_url |
string | Shipping label PDF URL |
pickup_token |
string | Pickup schedule token from carrier |
estimated_delivery_at |
datetime | Estimated delivery date |
order_item_ids |
jsonb | Items in this shipment (for partial fulfillment) |
dimensions |
jsonb | Package dimensions {length, width, height, weight} |
ndr_count |
integer | Number of NDR attempts |
shipped_at |
datetime | When carrier picked up |
delivered_at |
datetime | When delivered |
Creating a Shipment
POST /api/v1/orders/{order}/ship
{
"carrier_module": "shipping-delhivery",
"awb_number": "DLV123456789",
"tracking_url": "https://track.delhivery.com/DLV123456789",
"estimated_delivery_at": "2026-03-15T00:00:00Z",
"dimensions": {
"length": 30,
"width": 20,
"height": 10,
"weight": 500
}
}
Creating a shipment:
- Creates the
Shipmentrecord - Updates the order status to
shipped - Publishes
order.shipment_createdevent - Publishes
order.fulfilledevent
Partial Fulfillment
Orders can be shipped in multiple packages. Specify order_item_ids to indicate which items are in each shipment:
POST /api/v1/orders/{order}/ship
{
"carrier_module": "shipping-delhivery",
"awb_number": "DLV123456789",
"order_item_ids": ["item-uuid-1", "item-uuid-2"]
}
POST /api/v1/orders/{order}/ship
{
"carrier_module": "shipping-delhivery",
"awb_number": "DLV123456790",
"order_item_ids": ["item-uuid-3"]
}
The order relationship is HasMany — an order can have multiple shipments.
Listing Shipments
GET /api/v1/orders/{order}/shipments
Returns all shipments for an order with their tracking events.
Tracking Events
Each shipment records tracking events as ShipmentEvent records:
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
shipment_id |
UUID | Parent shipment |
status |
string | Event status |
location |
string | Location description |
description |
string | Event description |
raw_data |
jsonb | Raw carrier webhook data |
occurred_at |
datetime | When the event happened |
Tracking Sync
A scheduled SyncTrackingStatusJob runs every 30 minutes to poll in-transit shipments from carrier APIs. Carriers that support webhooks push updates via ShipmentWebhookController.
NDR (Non-Delivery Report) Handling
When a delivery attempt fails, carriers report an NDR:
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
shipment_id |
UUID | Parent shipment |
attempt_number |
int | Which delivery attempt |
reason |
string | Failure reason |
action |
string | Requested action (reattempt, return_to_origin) |
resolved_at |
datetime | When resolved |
NDR workflows:
- Carrier reports failed delivery attempt
NdrCaserecord created,ndr_countincremented on shipment- System can auto-reattempt or escalate based on configured rules
- If max attempts exceeded, triggers RTO (Return to Origin)
Order Status Transitions
The shipping lifecycle drives order status:
confirmed → processing → ready_to_ship → shipped → out_for_delivery → delivered
→ rto_initiated → rto_received
Can Ship Check
The can_ship accessor on the Order model returns true for:
confirmedprocessingready_to_ship
Returns false for all other statuses (pending, shipped, delivered, cancelled, etc.).
Carrier Integration via Bus
Shipping operations call carrier modules through the Module API Bus:
// Rate comparison
$rates = $bus->call('shipping-delhivery', 'shipping.getRates', [
'pickup_pincode' => '400001',
'delivery_pincode' => '560001',
'weight' => 500,
'dimensions' => ['length' => 30, 'width' => 20, 'height' => 10],
]);
// Create shipment with carrier
$result = $bus->call('shipping-delhivery', 'shipping.createShipment', [
'order_number' => $order->order_number,
'pickup_address' => [...],
'delivery_address' => [...],
'items' => [...],
'weight' => 500,
'cod_amount' => $order->is_cod ? $order->total : 0,
]);
Bulk Shipping
Create shipments for multiple orders at once:
POST /api/v1/orders/bulk/ship
{
"shipments": [
{
"order_id": "uuid1",
"carrier_module": "shipping-delhivery",
"awb_number": "DLV123456",
"tracking_url": "https://track.delhivery.com/DLV123456"
},
{
"order_id": "uuid2",
"carrier_module": "shipping-delhivery",
"awb_number": "DLV123457",
"tracking_url": "https://track.delhivery.com/DLV123457"
}
]
}
Events
| Event | When |
|---|---|
order.shipment_created |
Shipment created with tracking info |
order.fulfilled |
Order shipped or out for delivery |
order.delivered |
Order delivered to customer |
order.status_changed |
Any status transition (includes tracking updates) |