webit/gls-tracking
Laravel package that brings GLS parcel tracking into your PHP application. Look up shipments by tracking number and receive structured status and delivery updates, giving you a simple, consistent way to add GLS tracking to your app.
composer require webit/gls-tracking and immediately run composer show webit/gls-tracking to see the resolved version and its dependency constraints. This package's last tag is from 2017, so check whether Composer picked a PHP/Guzzle version that is compatible with your app; if it errors, you will likely need to fork the repo and relax the constraints before doing anything else.composer.json and look for an extra.laravel.providers block. Given the package age, assume there is no auto-discovery: register the service provider manually in config/app.php (or bootstrap/providers.php on Laravel 11) and add a facade alias if the README references one.src/ and identify the single class that talks to GLS (usually something like a GlsTracking/TrackingClient with methods such as getParcelStatus(...) / track(...)). That class is your entire integration surface. Check its constructor signature — it will tell you whether credentials, an endpoint URL, or a pre-built HTTP client are injected.php artisan vendor:publish --provider="Webit\GlsTracking\..." (get the exact FQCN from the README). Otherwise create config/gls-tracking.php yourself with the keys the client expects: API endpoint, username/login, password, and any country/tenant identifier. Keep these in .env and never commit them.Where to look first, in order: README → src/ client class → tests/ (if any) for real request/response fixtures → the GLS tracking API docs for the field meanings.
Wrap the client in your own service. Never let controllers or Livewire components call the vendor class directly. Create App\Services\GlsTrackingService that takes the vendor client in its constructor and exposes intent-revealing methods (getStatus(string $parcelNumber): ParcelStatus). This isolates you from the package being abandoned, and it's the one place you'd edit to swap in a hand-rolled Guzzle call later.
Bind an interface for testability. Register TrackingProvider::class => GlsTrackingService::class in a service provider, and resolve that interface everywhere. Because this package predates Laravel's Http facade, you cannot use Http::fake(). Instead, either (a) inject a Guzzle Client with a MockHandler into the vendor client if its constructor allows it, or (b) bind a fake implementation of your own interface in tests and never touch the vendor class in feature tests.
Normalize before calling. Parcel numbers arrive from users with spaces, dashes, and case variation. Strip and validate in a FormRequest or in your service before handing the value to the client; the GLS API expects a specific format and will return an opaque error otherwise.
Cache aggressively. Tracking statuses change on the order of hours, not seconds. Cache each parcel result for 5–15 minutes keyed by normalized parcel number (Cache::remember("gls:parcel:{$number}", now()->addMinutes(10), ...)) with a refresh flag to bypass. This is the single biggest thing that keeps you under rate limits and off the API's critical path.
Queue bulk lookups. Any screen that lists more than a couple of parcels should dispatch a job per batch rather than loop in the request. Use a dedicated queue with a low concurrency (2–3 workers) to avoid hammering the endpoint, add retryUntil/backoff, and persist results into your own shipment_events table so the UI reads from the database, not the API.
Persist a normalized event timeline. Map the vendor's response into your own parcel_status / parcel_events tables (parcel number, status code, description, occurred_at, raw payload). Keep the raw payload in a json column for later debugging; you'll need it when a customer disputes a status.
Multi-account/multi-region. GLS credentials are per country subsidiary and the endpoints differ. Don't rely on a single global config if you ship to more than one country — construct a client per account (a small factory reading an array of configs) rather than reading config() inside the vendor class.
Scheduled refresh. A scheduled command every 15–30 minutes that selects open shipments (not yet delivered), dispatches refresh jobs, and marks terminal states once observed. Combine with your cache so manual page views don't re-fetch.
"php": "^8.x" and "guzzlehttp/guzzle": "^7.0" in the fork's composer.json, consumed via a VCS repository entry or a private Packagist package.providers config block.config('gls-tracking.enabled') short-circuit or bind the fake interface in TestCase::setUp().TrackingFailedException, and log the request payload (minus credentials) plus the full raw response at debug level.Log::info($request) leaks them.data_get() with defaults and validate that the parcel number in the response matches the one you asked for before persisting.$tries, backoff()), not inside the package.composer.json so an unrelated composer update can't silently change behavior, and add a note in the repo that the dependency is frozen/abandoned.How can I help you explore Laravel packages today?