Skip to content
advanced Phase 87 · Integration Patterns

Shipping Carrier Integration

Shipping carrier integration - FedEx, UPS, DHL, rate calculation, tracking APIs

45m
0 problems
Topic Progress 0%

Carrier Integration Architecture

Shipping Integration Flow

┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│   Magento   │◄───►│  Shipping        │◄───►│  FedEx/UPS/ │
│   2 Store   │     │  Gateway         │     │  DHL APIs   │
└─────────────┘     └──────────────────┘     └─────────────┘
       │                    │                       │
  Checkout           Rate Calculation          Live Rates
  Shipment           Label Generation          Tracking
  Tracking           Address Validation        Delivery Dates

Carrier Gateway Interface

namespace Vendor\Shipping\Gateway;

interface CarrierGatewayInterface
{
    /**
     * Get real-time shipping rates
     */
    public function getRates(RateRequest $request): RateResponse;

    /**
     * Generate shipping label
     */
    public function createShipment(ShipmentRequest $request): LabelResponse;

    /**
     * Track shipment
     */
    public function track(string $trackingNumber): TrackingResponse;

    /**
     * Validate address
     */
    public function validateAddress(Address $address): AddressValidationResponse;
}

FedEx Adapter

namespace Vendor\Shipping\Gateway\FedEx;

class FedExGateway implements CarrierGatewayInterface
{
    private FedExClient $client;
    private ConfigInterface $config;

    public function getRates(RateRequest $request): RateResponse
    {
        $response = $this->client->request('rates', [
            'accountNumber' => $this->config->getAccountNumber(),
            'requestedShipment' => [
                'shipper' => $this->mapAddress($request->getShipper()),
                'recipient' => $this->mapAddress($request->getRecipient()),
                'packageCount' => count($request->getPackages()),
                'packages' => $this->mapPackages($request->getPackages()),
                'rateRequestType' => ['LIST', 'ACCOUNT'],
            ],
        ]);

        return $this->mapRates($response);
    }

    public function createShipment(ShipmentRequest $request): LabelResponse
    {
        $response = $this->client->request('ship', [
            'accountNumber' => $this->config->getAccountNumber(),
            'requestedShipment' => [
                'shipper' => $this->mapAddress($request->getShipper()),
                'recipient' => $this->mapAddress($request->getRecipient()),
                'serviceType' => $request->getServiceType(),
                'packagingType' => $request->getPackagingType(),
                'packages' => $this->mapPackages($request->getPackages()),
                'labelSpecification' => [
                    'labelFormatType' => 'PDF',
                    'imageType' => 'PDF',
                ],
            ],
        ]);

        return new LabelResponse([
            'tracking_number' => $response['output']['completeTrackResults'][0]['trackResults'][0]['trackingNumber'],
            'label_url' => $response['output']['completeTrackResults'][0]['trackResults'][0]['label']['url'],
        ]);
    }
}

Rate Calculation

Multi-Carrier Rate Aggregation

namespace Vendor\Shipping\Gateway\Aggregator;

class RateAggregator
{
    private array $carriers;

    public function __construct(array $carriers)
    {
        $this->carriers = $carriers;
    }

    public function getRates(RateRequest $request): AggregatedRates
    {
        $allRates = [];

        foreach ($this->carriers as $carrierCode => $carrier) {
            try {
                $rates = $carrier->getRates($request);
                foreach ($rates->getRates() as $rate) {
                    $rate->setCarrier($carrierCode);
                    $allRates[] = $rate;
                }
            } catch (\Exception $e) {
                // Log and skip failed carrier
                continue;
            }
        }

        // Sort by price
        usort($allRates, fn($a, $b) => $a->getPrice() <=> $b->getPrice());

        return new AggregatedRates($allRates);
    }
}

Rate Caching

namespace Vendor\Shipping\Gateway\Cache;

class RateCache
{
    private CacheInterface $cache;
    private int $ttl = 300; // 5 minutes

    public function getRates(RateRequest $request): ?RateResponse
    {
        $cacheKey = $this->buildCacheKey($request);
        $cached = $this->cache->load($cacheKey);

        if ($cached) {
            return unserialize($cached);
        }

        return null;
    }

    public function saveRates(RateRequest $request, RateResponse $response): void
    {
        $cacheKey = $this->buildCacheKey($request);
        $this->cache->save(serialize($response), $cacheKey, [], $this->ttl);
    }

    private function buildCacheKey(RateRequest $request): string
    {
        return 'shipping_rates_' . md5(serialize([
            $request->getShipper(),
            $request->getRecipient(),
            $request->getPackages(),
        ]));
    }
}

Tracking Integration

Tracking Service

namespace Vendor\Shipping\Tracking;

class TrackingService
{
    private array $carriers;
    private TrackingRepositoryInterface $trackingRepo;

    public function track(string $carrierCode, string $trackingNumber): TrackingInfo
    {
        $carrier = $this->getCarrier($carrierCode);
        $response = $carrier->track($trackingNumber);

        // Save to database
        $this->trackingRepo->save(new TrackingData([
            'carrier_code' => $carrierCode,
            'tracking_number' => $trackingNumber,
            'status' => $response->getStatus(),
            'status_description' => $response->getStatusDescription(),
            'estimated_delivery' => $response->getEstimatedDelivery(),
            'events' => $response->getEvents(),
        ]));

        return $response;
    }
}

Tracking API Endpoint

<route url="/V1/tracking/:carrier/:number" method="GET">
    <service class="Vendor\Shipping\Api\TrackingInterface" method="getTrackingInfo"/>
    <resources>
        <resource ref="anonymous"/>
    </resources>
</route>
namespace Vendor\Shipping\Api;

interface TrackingInterface
{
    public function getTrackingInfo(string $carrierCode, string $trackingNumber): TrackingDataInterface;
}

Address Validation

Address Validation Integration

namespace Vendor\Shipping\AddressValidation;

class AddressValidator
{
    private array $validators;

    public function validate(AddressInterface $address): ValidationResult
    {
        $errors = [];
        $suggestions = [];

        foreach ($this->validators as $validator) {
            $result = $validator->validate($address);

            if (!$result->isValid()) {
                $errors = array_merge($errors, $result->getErrors());
            }

            if ($result->hasSuggestions()) {
                $suggestions = array_merge($suggestions, $result->getSuggestions());
            }
        }

        return new ValidationResult([
            'valid' => empty($errors),
            'errors' => $errors,
            'suggestions' => $suggestions,
        ]);
    }
}

FedEx Address Validation

namespace Vendor\Shipping\AddressValidation\FedEx;

class FedExAddressValidator implements ValidatorInterface
{
    public function validate(AddressInterface $address): ValidationResult
    {
        $response = $this->client->request('address/validation', [
            'addresses' => [[
                'streetLines' => $address->getStreet(),
                'city' => $address->getCity(),
                'stateOrProvinceCode' => $address->getRegionCode(),
                'postalCode' => $address->getPostcode(),
                'countryCode' => $address->getCountryId(),
            ]],
        ]);

        $result = $response['output']['parsedResults'][0];

        return new ValidationResult([
            'valid' => $result['classification'] !== 'ERROR',
            'suggestions' => $this->mapSuggestions($result),
        ]);
    }
}

Quiz

1. What does a rate aggregator do?

Question 1 options

2. Why cache shipping rates?

Question 2 options

3. What is address validation used for?

Question 3 options

Flashcards

Question

What does a carrier gateway provide?

Answer

Rates, labels, tracking, address validation

Question

How does rate aggregation work?

Answer

Query multiple carriers, combine and sort results

Question

What is rate caching?

Answer

Storing API responses to avoid repeated calls

Question

What is address validation?

Answer

Verifying addresses are deliverable before shipping

Revision Notes

Key Takeaways

  • 1. Carrier adapters implement standardized gateway interfaces
  • 2. Rate aggregation combines results from multiple carriers
  • 3. Rate caching reduces API calls and improves checkout speed
  • 4. Tracking integration provides real-time shipment status
  • 5. Address validation prevents delivery failures

Interview Tips

  • Explain how multi-carrier rate aggregation works
  • Discuss caching strategies for shipping rates
  • Describe the tracking update flow
  • Talk about handling carrier API failures

Cheat Sheet

Shipping Integration:
  getRates() → live shipping rates
  createShipment() → generate labels
  track() → shipment tracking
  validateAddress() → verify address

Rate Aggregation:
  Query all carriers → combine → sort
  Cache for 5 minutes (TTL)

Tracking:
  Polling: cron checks status
  Webhooks: carrier pushes updates