Looking to hire Laravel developers? Try LaraJobs

laravel-ups maintained by beziworld

Description
UPS REST API client for Laravel: OAuth2, shipments with GIF/ZPL/EPL labels and customs forms, void, label recovery, rating, tracking, address validation, Access Point locator, pickups and paperless documents.
Last update
2026/11/04 20:46 (dev-master)
License
Downloads
1

Comments
comments powered by Disqus

Laravel UPS

tests Latest Version License

UPS REST API client for Laravel:

  • OAuth 2.0 client credentials with cached tokens, CIE (sandbox) and production
  • typed shipment builder: shipper, recipient, packages with weight and dimensions, references, declared value, Saturday delivery, COD, UPS Access Point delivery, negotiated rates, duties paid by the shipper, third-party billing
  • international shipments with a UPS-generated commercial invoice and paperless documents (Forms History)
  • labels in GIF, ZPL, EPL or SPL, decoded from base64; label recovery, also as PDF; void shipments or single packages
  • rating (one service or "shop" all services) with published and negotiated prices
  • tracking with typed events and normalised statuses, tracking by reference
  • address validation and classification, UPS Access Point locator, on-call pickups
  • several UPS accounts (named connections and Ups::using() for runtime credentials), Laravel events, ups:check command

Polska wersja dokumentacji

Why this package

Package API Laravel 12/13 Notes
gabrielbull/ups-api legacy XML with an access key no last release 2022
ptondereau/laravel-ups-api wraps gabrielbull/ups-api yes legacy API underneath
rawilk/laravel-ups legacy no (Laravel 7/8, PHP 7.4)
shipstream/ups-rest-php-sdk REST (generated) not a Laravel package PHP 7.4 SDK, no token cache
laravelshipping/ups REST Laravel 10 to 12 new, part of a suite

This package targets the current OAuth 2.0 REST APIs (Shipping v2409, Rating v2409, Tracking v1, Address Validation v2, Locator v3, Pickup v2409, Paperless v2) and adds a typed builder, decoded labels and documents, multiple accounts, the Laravel HTTP client (Http::fake() works in your tests), a strict no-retry policy for writes and Laravel events.

Installation

composer require beziworld/laravel-ups
php artisan vendor:publish --tag=ups-config   # optional
UPS_CLIENT_ID=...              # application on developer.ups.com
UPS_CLIENT_SECRET=...
UPS_ACCOUNT_NUMBER=A1B2C3      # six-character UPS shipper number
UPS_SANDBOX=true               # wwwcie.ups.com (Customer Integration Environment)

UPS_SHIPPER_NAME="ACME Sp. z o.o."
UPS_SHIPPER_CONTACT="Jan Nowak"
UPS_SHIPPER_PHONE=500600700
UPS_SHIPPER_EMAIL=shop@example.com
UPS_SHIPPER_ADDRESS="ul. Prosta 1"
UPS_SHIPPER_CITY=Warszawa
UPS_SHIPPER_POSTAL_CODE=00-001
UPS_SHIPPER_COUNTRY=PL

UPS_UNITS=metric               # metric (KGS, CM) or imperial (LBS, IN)
UPS_LABEL_FORMAT=GIF           # GIF, ZPL, EPL or SPL

Check the credentials (requests a token, no other call): php artisan ups:check.

Create a shipment

use Beziworld\Ups\Data\Money;
use Beziworld\Ups\Data\Parcel;
use Beziworld\Ups\Data\Party;
use Beziworld\Ups\Enums\LabelFormat;
use Beziworld\Ups\Enums\Service;
use Beziworld\Ups\Facades\Ups;

$shipment = Ups::shipment()
    ->to(new Party('Max Mustermann', 'Potsdamer Str. 1', 'Berlin', '10785', 'DE', phone: '030123456', email: 'max@example.com'))
    ->service(Service::Standard)
    ->parcel(new Parcel(weight: 2.5, length: 30, width: 20, height: 10, reference: 'ORDER-1'))
    ->parcel(new Parcel(weight: 1.2, declaredValue: Money::of(200, 'EUR')))
    ->reference('ORDER-1')
    ->labelFormat(LabelFormat::Zpl)
    ->create();

$shipment->shipmentId;          // 1Z12345E6605272234
$shipment->trackingNumbers;     // one per package
$shipment->label()->content;    // decoded label (GIF image or ZPL commands)
$shipment->totalCharges;        // Money: published price; negotiatedCharges with ->negotiatedRates()

Ups::voidShipment($shipment->shipmentId);                 // whole shipment
Ups::voidShipment($shipment->shipmentId, ['1Z...']);       // selected packages
Ups::recoverLabel('1Z12345E6605272234', LabelFormat::Pdf); // list<Label>, PDF on demand

Other builder options: shipper(), shipFrom(), description(), saturdayDelivery(), cashOnDelivery(Money, $fundsCode), toAccessPoint($id, Party $location, holdForPickup: true), billReceiver(), billThirdParty(), dutiesPaidByShipper(), negotiatedRates(), shipDate(), validateCity(), documentsOnly(), invoiceLineTotal() and with([...]) for any other element of the Shipment container. toArray() returns the request body without sending it.

International shipments

use Beziworld\Ups\Data\Customs;
use Beziworld\Ups\Data\CustomsItem;
use Beziworld\Ups\Data\PaperlessDocument;

$shipment = Ups::shipment()
    ->to(new Party('John Smith', '5th Avenue 1', 'New York', '10001', 'US', stateCode: 'NY'))
    ->service(Service::WorldwideSaver)
    ->parcel(new Parcel(1.0))
    ->description('Cotton T-shirts')
    ->customs(new Customs(
        items: [new CustomsItem('Cotton T-shirt', 3, '12.00', hsCode: '610910', originCountry: 'PL')],
        currency: 'USD',
        invoiceNumber: 'FV/1/2026',
        termsOfShipment: 'DAP',
    ))
    ->create();

$shipment->documents[0]->content;   // commercial invoice generated by UPS (PDF)

// Paperless: upload your own invoice and attach it to the shipment.
$ids = Ups::uploadDocuments([PaperlessDocument::fromPath(storage_path('invoice.pdf'))]);
Ups::shipment()->/* ... */->paperlessDocuments($ids)->create();
Ups::attachDocuments($ids, $shipmentId, now());  // for a shipment created without them

Rates

$builder = Ups::shipment()->to($recipient)->parcel(new Parcel(3))->negotiatedRates();

$rate = $builder->service(Service::Standard)->rate();  // Rate
$rates = $builder->shop();                             // list<Rate>, cheapest first

$rate->price();                  // negotiated price when available, otherwise published
$rate->businessDaysInTransit;    // from GuaranteedDelivery, when returned

Tracking

$package = Ups::track('1Z12345E6605272234');

$package->status;               // TrackingStatus::Delivered, InTransit, Exception, PickedUp, LabelCreated, Unknown
$package->deliveredAt;          // CarbonImmutable
foreach ($package->events as $event) {   // newest first
    $event->occurredAt;         // local scan time with the UPS offset
    $event->description;
    $event->city;
}

Ups::trackShipment('1Z...');                 // every package of the shipment
Ups::trackByReference('ORDER-1', now()->subWeek(), now());

Addresses, Access Points and pickups

use Beziworld\Ups\Data\PickupPiece;
use Beziworld\Ups\Data\PickupRequest;

$result = Ups::validateAddress(new Party('ACME', '26601 Aliso Creek Rd', 'Aliso Viejo', '92656', 'US', stateCode: 'CA'));
$result->valid; $result->isResidential(); $result->candidates;

$points = Ups::accessPoints(new Party('x', 'Marszałkowska 1', 'Warszawa', '00-001', 'PL'), limit: 10, radius: 5);
$points = Ups::accessPointsNear(52.23, 21.01, 'PL');
Ups::shipment()->toAccessPoint($points[0]->id, $points[0]->toParty());

$pickup = Ups::schedulePickup(new PickupRequest(
    date: now()->addDay(),
    readyTime: '09:00',
    closeTime: '17:00',
    pieces: [new PickupPiece(Service::Standard, quantity: 2, destinationCountryCode: 'DE')],
    totalWeight: 5.0,
));
Ups::cancelPickup($pickup->prn);

Street level address validation covers the United States and Puerto Rico; in the CIE environment UPS answers only for New York addresses.

Multiple accounts

// config/ups.php: 'connections' => ['default' => [...], 'eu' => [...]]
Ups::connection('eu')->shipment()->...;

// Credentials stored per tenant:
Ups::using(['client_id' => $tenant->ups_id, 'client_secret' => $tenant->ups_secret, 'account_number' => $tenant->ups_account])->track('1Z...');

Tokens are cached per client id and account in the configured cache store (UPS_CACHE_STORE).

Events

ShipmentCreated, ShipmentVoided and PickupScheduled (namespace Beziworld\Ups\Events) carry the connection name and the result object.

Errors

Exception When
ApiException UPS rejected the request or could not be reached: errorCode() (e.g. 120100), errors(), httpStatus(), isNotFound(), isRateLimited() with retryAfter()
AuthenticationException the OAuth endpoint refused the client id or secret (e.g. 10401 ClientId is Invalid)
InvalidConfiguration missing credentials, account number, shipper, recipient or parcels

All extend UpsException. Read-only calls are retried on connection errors and HTTP 5xx (UPS_RETRIES); shipments, voids, pickups and paperless uploads are never retried automatically. A request rejected with HTTP 401 is repeated once with a new token.

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need an integration? Get in touch through beziworld.eu.

Other packages by the author

laravel-fedex · laravel-dhl-express · laravel-dpd · laravel-inpost · laravel-furgonetka

License

MIT. See LICENSE.md. This package is not affiliated with, endorsed or sponsored by United Parcel Service. UPS and the UPS brandmark are trademarks of United Parcel Service of America, Inc.