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 Ups Api Laravel Package

ptondereau/laravel-ups-api

Laravel package that wraps the UPS API, letting you easily add shipping rates, tracking, address validation, and label creation to your Laravel applications through a clean, configurable integration.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require ptondereau/laravel-ups-api
php artisan vendor:publish --tag=ups   # publishes config/ups.php

Fill config/ups.php (or the matching env vars) with the five values you get from the UPS Developer Portal: access key, user id, password, shipper number, and account number. Note that shipper number and account number are not interchangeable — negotiated rates need the account number plus the shipper entity on the request.

Start with the rate sandbox. Point the package at the UPS CIE (test) environment via the integration/environment config flag, then:

use Ups\Rate;
use Ups\Entity\Address;
use Ups\Entity\Package;
use Ups\Entity\UnitOfMeasurement;

$shipper = new Address();
$shipper->setPostalCode('90210');
$shipper->setCountryCode('US');
$shipper->setCity('Beverly Hills');

$shipTo = new Address();
$shipTo->setPostalCode('10001');
$shipTo->setCountryCode('US');
$shipTo->setCity('New York');

$package = new Package();
$package->getPackagingType()->setCode(Package::PKG_TYPE_CUSTOMER_SUPPLIED);
$package->getPackageWeight()->setWeight(2.5);
$package->getPackageWeight()->getUnitOfMeasurement()->setCode(UnitOfMeasurement::PROD_KILOGRAMS);

$rates = Rate::getRate($shipper, $shipper, $shipTo, $package);

Where to look first:

  1. config/ups.php — credentials, environment toggle, debug/logging.
  2. src/Facades/ — one facade per UPS service (Rate, Tracking, Shipping, Address, TimeInTransit, Tradeability, Locator, QuantumView). In v5 each declares @mixin to its Ups\* root class, so hovering a facade call in PhpStorm now shows the real return type.
  3. vendor/ptondereau/ups-api — the underlying SDK. The entities (Address, Package, Shipment, PackageWeight, …) and their magic getters/setters live here, not in the Laravel package. This is the actual API surface you'll spend your time in.

If you're on v4, bump deliberately: v5.0.0 is a major tag that adds Laravel 13 / PHP 8.5 support, so confirm the illuminate/* and php constraints in composer.json before assuming your current Laravel 10/11 app is still supported. Run composer why-not ptondereau/laravel-ups-api 5.0 first.

Implementation Patterns

Wrap the facades; don't sprinkle them. UPS entities are verbose and mutable, and mapping them into your own DTOs at the boundary keeps the rest of the app free of Ups\Entity\*. A UpsRateService that accepts a plain ShipmentQuoteRequest and returns a Collection of ShippingOption values is the pattern that ages best, and it gives you one place to add caching, timeouts and fallbacks.

Checkout rate quoting. Cache aggressively keyed on (postal code, country, weight bucket, residential flag) — UPS rate calls are 200–800 ms and will dominate your checkout latency if uncached. Always wrap the call in a try/catch with a hardcoded fallback rate so a CIE/production outage doesn't take down checkout. Bucket weights (round up to 0.5 kg) to raise cache hit rate.

Label purchase should be queued and idempotent. Ups\Shipping::shop() returns rates and a label in one call, which is what you usually want for "buy cheapest label". Do it in a job, not in the request cycle. UPS has no idempotency key, so before retrying a failed job, look up whether a shipment with your reference number already exists — retries are a real source of duplicate labels and double charges. Persist GraphicImage (base64) to your storage disk immediately and keep the tracking number in a column, not in a log.

Tracking is poll-based. There are no webhooks for the standard Tracking API, so schedule a job over open shipments and back off on delivery (M/D status codes) versus in-transit. QuantumView exists for bulk file-based updates but requires a separate UPS subscription — only reach for it at high volume.

Validate addresses before rating or shipping. Address validation rejects bad postal codes cheaply; a bad address at label time is a hard failure after you've already quoted the customer.

Test by swapping the container binding. Bind your own UpsRateService interface to a fake in tests rather than hitting CIE. For the few integration tests that do call UPS, group them (@group ups) and skip when credentials are absent.

Gotchas and Tips

  • HTTP 200 is not success. UPS returns faults inside the body with a 200 status. Check Response->Error / the Alert collection on every response. A silently empty rate list is almost always this, not a config problem.
  • @mixin helps tooling, not runtime. If you mock a facade with Facade::shouldReceive(...), the method name still has to match the root class method, and static analysis of a shouldReceive expectation is only as good as the mixin. After upgrading to v5, re-index your IDE — stale caches will still show mixed and you'll wrongly conclude the annotations don't work.
  • Drop your old suppressions. If you were on v4, you likely had /** @var mixed $rates */ casts or PHPStan baseline entries around facade calls. v5 makes those redundant and level-10 analysis will now flag the redundant cast or the assumption that hid a type error. Clean them up while the diff is small.
  • Units are silent. KG vs LB and IN vs CM are enum codes, not inferred. Mixing them gives plausible-but-wrong rates rather than an error — a classic bug found weeks later on an invoice.
  • Residential vs commercial matters. The residential indicator changes surcharges; get it from your customer data, don't default it.
  • Negotiated rates need three things: account number in config, a shipper Address/Shipper on the request, and the negotiated-rates request option set. Miss one and you get list rates that look fine.
  • Never use Ups; for the root facade in a file that also imports Ups\Entity\* — the alias collides with the namespace. Import the specific facades (use Ups\Rate;).
  • Config caching. Everything goes through config('ups.*'). A stray env('UPS_ACCESS_KEY') outside a config file returns null the moment you run config:cache in production.
  • Logging. The package's debug flag logs full request XML; that includes credentials and customer addresses. Keep it off in production, or route it to a redacting handler.
  • Timeouts. Set a Guzzle timeout on the underlying client (via the package service provider binding or a small extension) — the default can hang a queue worker much longer than you'd like during a UPS incident.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin