Skip to content
advanced Phase 90 · Shipping Deep Dive

Multi-Address Shipping

Multi-address shipping - split shipments, multiple addresses, partial shipments

45m
0 problems
Topic Progress 0%

Multi-Address Architecture

Multi-Address Checkout Flow

  Cart with Items
       │
  ┌────▼────┐
  │  Select  │
  │ Addresses│
  └────┬────┘
       │
  ┌────▼────┐
  │  Assign  │
  │  Items   │
  └────┬────┘
       │
  ┌────▼────┐
  │  Create  │
  │ Shipments│
  └────┬────┘
       │
  ┌────▼────┐
  │  Calculate│
  │  Rates   │
  └────┬────┘
       │
  ┌────▼────┐
  │  Place   │
  │  Order   │
  └─────────┘

Multi-Address Service

namespace Vendor\Shipping\MultiAddress;

class MultiAddressService
{
    private AddressResolverInterface $addressResolver;
    private RateCalculatorInterface $rateCalculator;
    private ShipmentSplitterInterface $shipmentSplitter;

    public function processMultiAddress(
        CartInterface $cart,
        array $addressAssignments
    ): MultiAddressResult {
        // Validate assignments
        $this->validateAssignments($cart, $addressAssignments);

        // Split cart items by address
        $shipments = $this->shipmentSplitter->split(
            $cart->getItems(),
            $addressAssignments
        );

        // Calculate rates for each shipment
        $rates = [];
        foreach ($shipments as $shipment) {
            $rates[] = $this->rateCalculator->calculate(
                $shipment->getAddress(),
                $shipment->getItems()
            );
        }

        return new MultiAddressResult([
            'shipments' => $shipments,
            'rates' => $rates,
            'total_shipping' => array_sum(array_column($rates, 'price')),
        ]);
    }
}

Shipment Splitting

Shipment Splitter

namespace Vendor\Shipping\MultiAddress\Split;

class ShipmentSplitter implements ShipmentSplitterInterface
{
    public function split(
        array $items,
        array $addressAssignments
    ): array {
        $shipments = [];

        foreach ($addressAssignments as $addressId => $itemIds) {
            $shipmentItems = [];

            foreach ($items as $item) {
                if (in_array($item->getId(), $itemIds)) {
                    $shipmentItems[] = $item;
                }
            }

            if (!empty($shipmentItems)) {
                $shipments[] = new ShipmentData([
                    'address_id' => $addressId,
                    'items' => $shipmentItems,
                    'subtotal' => $this->calculateSubtotal($shipmentItems),
                    'weight' => $this->calculateWeight($shipmentItems),
                ]);
            }
        }

        return $shipments;
    }
}

Item Availability Check

namespace Vendor\Shipping\MultiAddress\Validation;

class ItemAvailabilityChecker
{
    public function validate(
        array $items,
        array $addressAssignments,
        InventoryInterface $inventory
    ): ValidationResult {
        $errors = [];
        $itemAddressMap = [];

        // Build item→address mapping
        foreach ($addressAssignments as $addressId => $itemIds) {
            foreach ($itemIds as $itemId) {
                $itemAddressMap[$itemId] = $addressId;
            }
        }

        // Check each item can ship to its assigned address
        foreach ($items as $item) {
            $addressId = $itemAddressMap[$item->getId()] ?? null;

            if (!$addressId) {
                $errors[] = sprintf('Item %s not assigned to any address', $item->getName());
                continue;
            }

            $address = $this->addressRepo->get($addressId);

            if (!$inventory->canShipTo($item, $address)) {
                $errors[] = sprintf(
                    'Item %s cannot ship to %s',
                    $item->getName(),
                    $address->getCity()
                );
            }
        }

        return new ValidationResult(empty($errors), $errors);
    }
}

Partial Shipments

Partial Shipment Service

namespace Vendor\Shipping\PartialShipment;

class PartialShipmentService
{
    private ShipmentFactoryInterface $shipmentFactory;
    private ShipmentTrackFactoryInterface $trackFactory;

    public function createPartialShipment(
        OrderInterface $order,
        array $itemsToShip,
        string $carrierCode,
        string $trackingNumber
    ): ShipmentInterface {
        $shipment = $this->shipmentFactory->create($order);

        foreach ($itemsToShip as $item) {
            $qty = $item['qty'] ?? $item->getQtyToShip();

            $shipmentItem = $this->addShipmentItem($shipment, $item, $qty);

            // Decrement order item quantity
            $orderItem = $item->getOrderItem();
            $orderItem->setQtyShipped($orderItem->getQtyShipped() + $qty);
        }

        // Add tracking
        $track = $this->trackFactory->create();
        $track->setCarrierCode($carrierCode);
        $track->setTitle($carrierCode);
        $track->setNumber($trackingNumber);
        $shipment->addTrack($track);

        // Register shipment
        $shipment->register();

        $this->shipmentRepository->save($shipment);

        return $shipment;
    }
}

Shipment Status Tracking

namespace Vendor\Shipping\PartialShipment\Status;

class ShipmentStatusTracker
{
    public function getOrderShipmentStatus(
        OrderInterface $order
    ): ShipmentStatusResult {
        $totalQty = 0;
        $shippedQty = 0;

        foreach ($order->getItems() as $item) {
            $totalQty += $item->getQtyOrdered();
            $shippedQty += $item->getQtyShipped();
        }

        return new ShipmentStatusResult([
            'total_items' => $totalQty,
            'shipped_items' => $shippedQty,
            'remaining_items' => $totalQty - $shippedQty,
            'is_complete' => $shippedQty >= $totalQty,
            'completion_percentage' => ($shippedQty / $totalQty) * 100,
        ]);
    }
}

Gift Registry Support

Gift Registry Address

namespace Vendor\Shipping\GiftRegistry;

class GiftRegistryAddressResolver implements AddressResolverInterface
{
    private GiftRegistryRepositoryInterface $registryRepo;

    public function resolve(array $itemIds): array
    {
        $addresses = [];

        // Group items by registry
        $registryItems = [];
        foreach ($itemIds as $itemId) {
            $item = $this->cartItemRepo->get($itemId);
            $registryId = $item->getGiftRegistryId();

            if ($registryId) {
                $registryItems[$registryId][] = $itemId;
            }
        }

        // Get registry addresses
        foreach ($registryItems as $registryId => $items) {
            $registry = $this->registryRepo->get($registryId);
            $addresses[$registry->getShippingAddressId()] = $items;
        }

        return $addresses;
    }
}

Multi-Address Order Creation

namespace Vendor\Shipping\MultiAddress\Order;

class MultiAddressOrderCreator
{
    public function create(
        CartInterface $cart,
        MultiAddressResult $result
    ): OrderInterface {
        $order = $this->orderFactory->create();
        $order->setData($cart->toArray());

        // Create order items
        foreach ($cart->getItems() as $item) {
            $orderItem = $this->orderItemFactory->create();
            $orderItem->setData($item->toArray());
            $order->addItem($orderItem);
        }

        // Create shipments
        foreach ($result->getShipments() as $shipment) {
            $orderShipment = $this->createOrderShipment($order, $shipment);
            $order->addShipment($orderShipment);
        }

        // Set shipping amounts
        $order->setShippingAmount($result->getTotalShipping());
        $order->setBaseShippingAmount($result->getTotalShipping());

        return $order;
    }
}

Quiz

1. What is multi-address shipping?

Question 1 options

2. What is a partial shipment?

Question 2 options

3. How does gift registry affect shipping?

Question 3 options

Flashcards

Question

What is multi-address shipping?

Answer

One order, items ship to different addresses

Question

What is a partial shipment?

Answer

Shipping a portion of order items before the rest

Question

What is shipment splitting?

Answer

Dividing cart items into separate shipments by address

Question

What is completion percentage?

Answer

Shipped items / total items × 100

Revision Notes

Key Takeaways

  • 1. Multi-address shipping splits one order into multiple shipments
  • 2. Shipment splitter assigns items to addresses
  • 3. Partial shipment ships items in stages
  • 4. Gift registry redirects shipments to registry addresses
  • 5. Each shipment has its own shipping rate and tracking

Interview Tips

  • Explain the multi-address checkout flow
  • Discuss how to split shipments by address
  • Describe partial shipment tracking
  • Talk about gift registry address resolution

Cheat Sheet

Multi-Address:
  Cart → Assign items → Split → Rates → Order

Partial Shipment:
  Ship some items → Track remaining
  Completion = shipped / total × 100

Shipment Splitting:
  Group items by address
  Calculate rate per shipment
  Create separate tracking

Gift Registry:
  Item → registry_id → registry address