ivanmitrikeski/laravel-shipping
Laravel shipping package with UPS, FedEx, Canada Post, Purolator, and USPS v3 support. Get rates and create shipments via REST/OAuth APIs, with sandbox mode and flat-rate options (boxes/prices) via Eloquent models. Usable outside Laravel, too.
Installation:
composer require ivanmitrikeski/laravel-shipping
php artisan migrate
Run migrations to create shipments, shipment_providers, and related tables.
Configure .env:
Add credentials for your target providers (e.g., UPS_CLIENT_ID, CANADA_POST_CUSTOMER_NUMBER). Set SHIPPING_SANDBOX=true for testing.
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();
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();
->sandbox() for testing without modifying .env.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();
Provider-Specific Logic:
Shipping::rates('usps')..env early.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();
Handling Responses: Iterate over rate results:
foreach ($rates as $rate) {
echo $rate->service . ': ' . $rate->rate->amount . ' ' . $rate->rate->currency;
}
Laravel Services: Bind the package to a service container for dependency injection:
$this->app->singleton(Shipping::class, function ($app) {
return new \IvanMitrikeski\Shipping\ShippingManager();
});
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();
});
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',
]);
Logging: Log API responses for debugging:
Shipping::rates('ups')
->setLogger(app(\Monolog\Logger::class))
->get();
Queueable Jobs: Offload shipment creation to a queue for long-running processes:
CreateShipmentJob::dispatch($shipmentData)->onQueue('shipping');
Sandbox vs. Production:
SHIPPING_SANDBOX between environments..env files or a config override:
if (app()->environment('local')) {
config(['shipping.sandbox' => true]);
}
Provider-Specific Quirks:
billing_account or registered_account. Validate these early.Rate Limiting:
try {
$rates = Shipping::rates('usps')->get();
} catch (\IvanMitrikeski\Shipping\Exceptions\RateLimitException $e) {
sleep(10);
retry();
}
Currency/Unit Mismatches:
$weightKg = $weightLbs * 0.453592;
Missing Required Fields:
shipmentType).dd($rate->toArray()) to inspect the full response structure.Enable Verbose Logging:
Shipping::setLogLevel(\Monolog\Logger::DEBUG);
Check logs at storage/logs/laravel.log.
Inspect Raw API Responses: Override the provider’s HTTP client to log requests:
Shipping::extend('ups', function ($app) {
$client = new \GuzzleHttp\Client([
'handler' => \GuzzleHttp\HandlerStack::create([
'debug' => function ($handler) {
return function ($request, $options) use ($handler) {
\Log::debug('API Request:', [$request->getUri(), $request->getBody()]);
return $handler($request, $options);
};
},
]),
]);
return new \IvanMitrikeski\Shipping\Providers\Ups\UpsService($client);
});
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).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,
],
Modify Request/Response: Override the default HTTP client or response parser:
Shipping::extend('ups', function ($app) {
return new \IvanMitrikeski\Shipping\Providers\Ups\UpsService(
$app->make(\GuzzleHttp\Client::class),
new \App\Shipping\Parsers\CustomUpsParser()
);
});
Add Custom Fields:
Extend the Shipment model or use accessors:
// app/Models/Shipment.php
public function getTrackingUrlAttribute() {
return $this->provider->getTrackingUrl($this->tracking_number);
}
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);
}
How can I help you explore Laravel packages today?