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 Ukrposhta Laravel Package

kolirt/laravel-ukrposhta

Laravel wrapper for the Ukrposhta API that simplifies integrating Ukrainian postal services into your app. It provides a configured client with ready-made request handling and easy access to endpoints for tracking shipments, addresses, and delivery data, cutting HTTP boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin Laravel wrapper around the Ukrposhta (Ukrainian national postal operator) HTTP API, so the fastest path to a working call is: install, publish config, set a token, make one read-only request.

composer require kolirt/laravel-ukrposhta
php artisan vendor:publish --provider="Kolirt\Ukrposhta\UkrposhtaServiceProvider"

Then in .env:

UKRPOSHTA_TOKEN=your-bearer-token

Laravel auto-discovery registers the service provider and facade alias, so a first smoke test is a read-only lookup rather than a write:

use Kolirt\Ukrposhta\Facades\Ukrposhta;

$status = Ukrposhta::tracking()->status('UA123456789UA');

Where to look first, in order:

  1. config/ukrposhta.php — base URL, token key, timeout, and any sandbox/production switch. This is the single most important file; almost every "it doesn't work" question traces back here.
  2. The facade class and the service provider's bindings — this tells you the concrete class name, whether the client is a singleton, and whether it is macroable/extendable.
  3. The README's example blocks — the package's API surface is fairly small; the examples are typically more current than any generated docs.

First realistic use case: order-status polling. Read-only tracking is idempotent, needs no waybill lifecycle knowledge, and validates your token, endpoint, and error handling in one call before you attempt anything that creates a shipment.

Implementation Patterns

Wrap the facade in your own service. Do not call Ukrposhta:: from controllers or Blade. Introduce App\Services\Ukrposhta\ShipmentService that depends on the package client via constructor injection. The package is at 1.x with an external API behind it; a single seam means a future breaking release touches one class instead of forty call sites.

Bind the client in a service provider, not by facade resolution. If the underlying class is bound as a singleton, resolve it through the container so tests can $this->app->instance(...) a fake. This is the difference between a testable integration and one you mock at the HTTP layer for every unit test.

Test at the HTTP layer, not the class layer. The client ultimately uses Laravel's HTTP client, so Http::fake(['api.ukrposhta.ua/*' => Http::response($fixture)]) lets you assert both the outgoing payload shape and your response mapping. Capture a handful of real responses (success, validation failure, auth failure) into tests/Fixtures/ on day one — they become the contract you pin against when 1.0.x bumps arrive.

Cache the reference data aggressively. City, street, and post-office (department) dictionaries change on the order of weeks, not seconds. A Cache::remember('ukrposhta:departments:'.$cityId, now()->addDay(), ...) wrapper cuts latency and, more importantly, keeps you clear of any per-key rate limits during address autocomplete. Never cache live tracking status or waybill creation results.

Push write operations onto the queue. Waybill creation, label PDF retrieval, and status changes are network-bound and failure-prone. A job with $tries = 3, backoff() of a minute or two, and failed() writing to a shipment_errors table gives you visibility the synchronous path never will. Keep the idempotency key (your order/shipment UUID) in the payload so a retry after a timeout doesn't create a duplicate waybill.

Model the shipment lifecycle explicitly. Store the returned waybill identifier (barcode) alongside your own order record, plus a status column and last_synced_at. Then a scheduled command polls only shipments in non-terminal states on a staggered interval. Do not poll everything on every tick — tracking endpoints are the ones most likely to throttle you.

Keep a translation layer for statuses. The API returns status codes/strings in Ukrainian or as opaque numeric codes depending on the endpoint. Map them to your own enum (Created, InTransit, Arrived, Delivered, Returned) at the boundary, and keep the raw payload in a JSON column for forensics.

Validate before you call. Form requests for the shipment payload (recipient name length, phone format +380XXXXXXXXX, weight > 0, declared value ≥ 0) save you a round trip and produce far better user errors than the API's field messages.

Log the correlation, not the payload. Log request ID, waybill barcode, endpoint, and duration at info; log the full request/response at debug behind a dedicated channel. Shipment payloads contain names, phones, and addresses — treat them as PII and keep them out of your default log stack.

Gotchas and Tips

  • Config cache is the number one footgun. config/ukrposhta.php is read at boot. Changing UKRPOSHTA_TOKEN in .env on a server that has run config:cache does nothing until you re-cache. Add php artisan config:cache to your deploy script and stop debugging the token.
  • HTTP 200 does not mean success. Postal APIs commonly return a 200 with an error body, or a business-level error code inside a successful envelope. Always inspect the decoded body for an error indicator before treating a call as done — a bare $response->successful() check will silently produce waybills that were never created.
  • Set explicit timeouts. Laravel's default HTTP timeout is 30s. For a checkout flow that is unacceptable; set connect_timeout to a couple of seconds and an overall timeout of 5–10s in config, and catch ConnectionException distinctly from a validation rejection so you can retry the former and surface the latter.
  • Units and encoding. Weight is typically expected in grams while your UI probably collects kilograms, and dimensions in millimetres vs centimetres. Encode a single conversion in a DTO. Separately, ensure the request body is JSON-encoded as UTF-8 (JSON_UNESCAPED_UNICODE is optional but handy when diffing logs) — Cyrillic names mangled into \uXXXX are a common "why is the recipient's name wrong" report.
  • Phone and address normalisation. Strip spaces/dashes, enforce the +380 prefix, and trim trailing whitespace on street names. Most rejected waybills are rejected on address formatting, not on anything the API documents well.
  • Sandbox vs production base URL. The endpoint lives in config but is easy to leave pointing at production while testing. Assert the environment in a local-only guard, or default the base URL to the test host when APP_ENV !== 'production'.
  • Don't rely on 0.x-era method names. If you have code from an early version, check the facade method signatures against the 1.x source before assuming they survived; the changelog for 1.0.3 is a bare compare link, so treat 1.0.2 → 1.0.3 as a patch you can take blindly, but do not extend that assumption to a future 1.1/2.0.
  • Extension points. If the client is macroable, prefer Ukrposhta::macro('...') or a small subclass for one-off endpoints (e.g. an internal reporting call) over forking. If it is not macroable, bind your own decorator around it — never patch vendor/.
  • Debugging a bad response. Reach for Http::globalMiddleware or the package's config option to dump the raw request/response once, or hit the endpoint with curl and the same bearer token. If curl works and the package fails, the difference is almost always the base URL, a missing Content-Type: application/json, or an un-normalised token (leading Bearer pasted into the .env value is a classic).
  • Upgrade hygiene. Because release notes are sparse, pin ^1.0 in composer.json, run your recorded-fixture tests after every bump, and read the diff of the facade/client class rather than the changelog when moving between minor versions.
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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox