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?
2. What is a partial shipment?
3. How does gift registry affect shipping?
Flashcards
Question
What is multi-address shipping?
Click to reveal answer
Answer
One order, items ship to different addresses
Question
What is a partial shipment?
Click to reveal answer
Answer
Shipping a portion of order items before the rest
Question
What is shipment splitting?
Click to reveal answer
Answer
Dividing cart items into separate shipments by address
Question
What is completion percentage?
Click to reveal answer
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