Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Shipping Laravel Package

ivanmitrikeski/laravel-shipping

Laravel package for multi-carrier shipping quotes and shipments, supporting UPS, FedEx, Canada Post, USPS and Purolator rate and shipment APIs, with flat-rate models and standalone use outside Laravel.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require ivanmitrikeski/laravel-shipping
    php artisan migrate
    

    Run migrations to create shipments, shipment_providers, and related tables.

    v1.0.10 note: this release moves the package onto Guzzle 8. The HTTP client is now a major version ahead of what most Laravel apps installed previously, so resolve it explicitly before running anything else:

    composer update ivanmitrikeski/laravel-shipping guzzlehttp/guzzle -W
    composer show guzzlehttp/guzzle   # confirm you're on 8.x, not 7.x
    

    If another package pins guzzlehttp/guzzle:^7.0, Composer will refuse the update — that's the first thing to check if composer require fails after upgrading.

  2. Configure .env: Add credentials for your target providers (e.g., UPS_CLIENT_ID, CANADA_POST_CUSTOMER_NUMBER). Set SHIPPING_SANDBOX=true for testing.

  3. First Use Case: Fetch rates for a shipment via UPS:

    use IvanMitrikeski\Shipping\Facades\Shipping;
    
    $rates = Shipping::rates('ups')
        ->credentials()
        ->setShipper([
            'name' => 'Your Company',
            'address' => '123 Main St',
            // ... other required fields
        ])
        ->setRecipient([
            'name' => 'Recipient Name',
            'address' => '456 Oak Ave',
        ])
        ->setPackage([
            'weight' => 2.0, // in lbs
            'dimensions' => ['length' => 10, 'width' => 5, 'height' => 5],
        ])
        ->get();
    

Implementation Patterns

Core Workflows

  1. Rate Calculation: Chain methods to build a request, then call get():

    $rates = Shipping::rates('fedex')
        ->credentials()
        ->setShipper($shipperData)
        ->setRecipient($recipientData)
        ->setPackages([$package1, $package2]) // Array for multi-package shipments
        ->get();
    
    • Tip: Use ->sandbox() for testing without modifying .env.
  2. Shipment Creation: For providers supporting shipment APIs (UPS, FedEx, etc.):

    $shipment = Shipping::shipment('ups')
        ->credentials()
        ->setShipper($shipperData)
        ->setRecipient($recipientData)
        ->setPackages([$package])
        ->setService('01') // UPS Ground
        ->create();
    
  3. Provider-Specific Logic:

    • USPS: Only supports rate APIs (no shipment tracking). Use Shipping::rates('usps').
    • Canada Post/Purolator: Require billing/account numbers. Validate these in .env early.
  4. Reusable Credentials: Store provider credentials in a config file or database for dynamic switching:

    $credentials = config('shipping.providers.ups');
    $rates = Shipping::rates('ups')
        ->credentials($credentials)
        ->get();
    
  5. Handling Responses: Iterate over rate results:

    foreach ($rates as $rate) {
        echo $rate->service . ': ' . $rate->rate->amount . ' ' . $rate->rate->currency;
    }
    

Integration Tips

  1. Laravel Services: Bind the package to a service container for dependency injection:

    $this->app->singleton(Shipping::class, function ($app) {
        return new \IvanMitrikeski\Shipping\ShippingManager();
    });
    
  2. API Caching: Cache rate responses to reduce API calls (e.g., for cart pages):

    $rates = Cache::remember("shipping_rates_{$cacheKey}", now()->addHours(1), function () {
        return Shipping::rates('ups')->get();
    });
    
  3. Validation: Validate package dimensions/weights before API calls:

    $validator = Validator::make($request->all(), [
        'weight' => 'required|numeric|min:0.1',
        'dimensions.length' => 'required|numeric|min:1',
    ]);
    
  4. Logging: Log API responses for debugging:

    Shipping::rates('ups')
        ->setLogger(app(\Monolog\Logger::class))
        ->get();
    
  5. Queueable Jobs: Offload shipment creation to a queue for long-running processes:

    CreateShipmentJob::dispatch($shipmentData)->onQueue('shipping');
    
  6. Inject the HTTP client instead of constructing it (Guzzle 8 friendly): Since v1.0.10 the package resolves Guzzle 8, where client construction options and PSR-7 handler behaviour have moved on. Prefer resolving the client (or the package service) from the container over new \GuzzleHttp\Client([...]) in your own code:

    // app/Providers/AppServiceProvider.php
    $this->app->bind(\GuzzleHttp\ClientInterface::class, function () {
        return new \GuzzleHttp\Client([
            'timeout' => 15,
            'connect_timeout' => 5,
            // Guzzle 8: check option names against the current upgrade guide
        ]);
    });
    

    Type-hint ClientInterface, not Client, in custom providers/parsers — that survives future major bumps.


Gotchas and Tips

Pitfalls

  1. Guzzle 8 upgrade (v1.0.10):

    • Issue: A major Guzzle bump can break code that touches the HTTP layer directly — custom providers, custom parsers, extend() closures, and any test doubles built on HandlerStack/MockHandler.
    • Fix: Treat this as an upgrade, not a patch. Run composer update -W, then re-run your rate/shipment integration tests (sandbox credentials) before deploying. Anything that type-hints GuzzleHttp\Client concretely or relies on Guzzle 7 handler-stack middleware signatures should be switched to GuzzleHttp\ClientInterface and re-verified.
    • Also check: transitive PSR-7 (psr/http-message, guzzlehttp/psr7) and promises versions now resolved in your composer.lock; older packages that pinned psr/http-message:^1.0 may need bumping alongside.
  2. Sandbox vs. Production:

    • Issue: Forgetting to toggle SHIPPING_SANDBOX between environments.
    • Fix: Use environment-specific .env files or a config override:
      if (app()->environment('local')) {
          config(['shipping.sandbox' => true]);
      }
      
  3. Provider-Specific Quirks:

    • USPS: Requires a Commercial Plus account for accurate rates. Sandbox credentials may not reflect live pricing.
    • Canada Post/Purolator: Reject requests with missing billing_account or registered_account. Validate these early.
    • UPS/FedEx: OAuth tokens expire. Implement token refresh logic or use the package's built-in retry mechanism.
  4. Rate Limiting:

    • Issue: API providers throttle requests (e.g., USPS blocks after 5 calls/minute).
    • Fix: Implement exponential backoff or use a queue:
      try {
          $rates = Shipping::rates('usps')->get();
      } catch (\IvanMitrikeski\Shipping\Exceptions\RateLimitException $e) {
          sleep(10);
          retry();
      }
      
  5. Currency/Unit Mismatches:

    • Issue: Providers expect weights in lbs (USPS) or kg (Canada Post). Convert units before sending:
      $weightKg = $weightLbs * 0.453592;
      
  6. Missing Required Fields:

    • Debugging: Check the provider's API docs for hidden fields (e.g., UPS requires shipmentType).
    • Tip: Use dd($rate->toArray()) to inspect the full response structure.

Debugging Tips

  1. Enable Verbose Logging:

    Shipping::setLogLevel(\Monolog\Logger::DEBUG);
    

    Check logs at storage/logs/laravel.log.

  2. Inspect Raw API Responses: Rather than hand-rolling a HandlerStack, attach a middleware to the client you bound in the container — this is the pattern most likely to survive the Guzzle 8 change:

    use GuzzleHttp\Middleware;
    use Psr\Http\Message\RequestInterface;
    use Psr\Http\Message\ResponseInterface;
    
    $stack = \GuzzleHttp\HandlerStack::create();
    $stack->push(Middleware::tap(
        fn (RequestInterface $req) => \Log::debug('API Request', [$req->getMethod(), (string) $req->getUri()]),
        fn (RequestInterface $req, $opts, ResponseInterface $res) => \Log::debug('API Response', [$res->getStatusCode()]),
    ));
    

    Then hand that client to the provider via Shipping::extend() (see Extension Points). Verify the middleware closure arity against Guzzle 8's docs — signatures are the usual casualty of a major bump.

  3. Common Errors:

    • InvalidCredentialsException: Double-check .env values (e.g., UPS_ACCOUNT_NUMBER format).
    • ValidationException: Use ->validate() to check inputs before API calls.
    • ConnectionException: Ensure SHIPPING_SANDBOX matches your endpoint (e.g., sandbox.ups.com).
    • Class not found / handler errors after upgrading: Almost always a stale composer.lock or a package still requiring Guzzle 7. Run composer why guzzlehttp/guzzle and composer why-not guzzlehttp/guzzle 8.0.
  4. Tests after the upgrade: Swap the real client for a MockHandler in your test suite so provider tests stay offline and version-agnostic:

    $mock = new \GuzzleHttp\Handler\MockHandler([new \GuzzleHttp\Psr7\Response(200, [], $fixtureJson)]);
    $client = new \GuzzleHttp\Client(['handler' => \GuzzleHttp\HandlerStack::create($mock)]);
    

Extension Points

  1. Custom Providers: Extend the package by creating a new provider class:

    namespace App\Shipping\Providers;
    
    use IvanMitrikeski\Shipping\Contracts\Provider;
    
    class DHLService implements Provider {
        public function getRates(array $data) {
            // Custom logic
        }
    }
    

    Register it in config/shipping.php:

    'providers' => [
        'dhl' => \App\Shipping\Providers\DHLService::class,
    ],
    
  2. Modify Request/Response: Override the default HTTP client or response parser. Use container resolution and ClientInterface so the override keeps working across Guzzle majors:

    Shipping::extend('ups', function ($app) {
        return new \IvanMitrikeski\Shipping\Providers\Ups\UpsService(
            $app->make(\GuzzleHttp\ClientInterface::class),
            new \App\Shipping\Parsers\CustomUpsParser()
        );
    });
    

    The constructor signature you target must match the package's own — confirm it against the installed version after upgrading.

  3. Add Custom Fields: Extend the Shipment model or use accessors:

    // app/Models/Shipment.php
    public function getTrackingUrlAttribute() {
        return $this->provider->getTrackingUrl($this->tracking_number);
    }
    
  4. Webhook Handling: For real-time updates (e.g., FedEx shipment status), create a controller:

    public function handleWebhook(Request $request) {
        $provider = $request->provider;
        $data = $request->json()->all();
        return Shipping::webhook($provider)->handle($data);
    }
    
  5. Pin your override points: After the Guzzle 8 upgrade, add a smoke test that boots every registered provider (Shipping::rates($provider)) against sandbox credentials. Extension points are the surface area most likely to break on a dependency major, and a one-minute test catches it before deploy.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style