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

Nova Poshta Laravel Package

grisaia/nova-poshta

Laravel package for Nova Poshta integration: look up cities and warehouses, calculate shipping rates, create waybills, and track parcels through the Nova Poshta API. Simple configuration and framework-friendly helpers make delivery workflows easy to add to any Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure

composer require grisaia/nova-poshta

Get an API key from the Nova Poshta business cabinet (Settings → Security → API) and keep it out of version control:

NOVA_POSHTA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOVA_POSHTA_LANGUAGE=ua

Look first at src/ — this is a thin, low-traffic package, so the source is the real documentation. Expect a single client class wrapping the v2 JSON endpoint (https://api.novaposhta.ua/v2.0/json/) with modelName / calledMethod / methodProperties semantics carried straight over from the official API. The README will show the constructor signature (key, language, possibly a base URL or HTTP client override) — confirm that before wiring anything else.

First use case: address autocomplete

The highest-value, lowest-risk first integration is city and warehouse lookup. Build a search endpoint your checkout can hit:

Route::get('/np/cities', function (NovaPoshta $np) {
    return $np->address()->getCities(request('q'));
});

Route::get('/np/warehouses', function (NovaPoshta $np) {
    return $np->address()->getWarehouses(request('city_ref'), request('q'));
});

Get this working end-to-end (key accepted, Ukrainian labels coming back, results cached) before touching document creation. It validates credentials without creating real shipping documents — a much cheaper way to learn the client's response shape.

Implementation Patterns

Wrap the client, don't sprinkle it

Register it as a singleton and depend on your own interface, not the package class:

$this->app->singleton(NovaPoshta::class, fn () => new NovaPoshta(
    config('services.nova_poshta.key'),
    config('services.nova_poshta.language', 'ua'),
));

Then put a NovaPoshtaGateway in front of it. The package is a 1-star, single-maintainer dependency; the wrapper is your insurance policy if it stalls or you need to swap clients.

Cache the address catalogue aggressively

Address.getCities and Address.getWarehouses return tens of thousands of rows and are re-fetched constantly by autocomplete. Cache per query string for 12–24 hours, and cache the full warehouse list per city ref:

Cache::remember("np.warehouses.{$cityRef}", now()->addHours(12), fn () => ...);

Filter server-side where the API supports it (FindByString, Limit, Page) rather than pulling everything and filtering in PHP.

Order-to-waybill pipeline

The realistic flow is: settlement search → warehouse/street pick → price and delivery-date quote → create counterparty + contact person (recipient) → create internet document → persist the Ref → poll tracking.

Quote before you commit — InternetDocument.getDocumentPrice and getDocumentDeliveryDate are read-only and safe to call on every cart change (debounce them; they're not free).

Store refs, not labels

Every city, warehouse, counterparty, and document has a GUID Ref. Persist the Ref on your order, and resolve the human-readable description at render time. Names get retranslated (ua vs ru) and renamed; refs don't.

Handle results as data, not exceptions

The v2 API returns HTTP 200 with {"success": false, "errors": [...]} on failure. Whatever the package surfaces (array, DTO, or exception), normalize it into one branch in your gateway:

$result = $this->np->internetDocument()->save($payload);

if (! ($result['success'] ?? false)) {
    Log::warning('NP save failed', ['errors' => $result['errors'] ?? [], 'payload' => $payload]);
    throw new NovaPoshtaException($result['errors'][0] ?? 'Unknown error');
}

Push tracking to the queue

TrackingDocument.getStatusDocuments accepts batches of document refs (up to ~100). Run a scheduled job every 15–30 minutes for open shipments only, and stop polling once status is terminal (delivered/refused/returned). Never call tracking on page render.

Test against a fake transport

Point the client's HTTP layer (Guzzle handler stack or Laravel Http::fake, depending on what the package uses) at recorded fixtures. Save real responses from a sandbox key into tests/fixtures/ — that's the only reliable schema documentation you'll get.

Gotchas and Tips

  • HTTP 200 always. Do not let Guzzle's status-code handling lull you; every business error is in the body. Assert success === true explicitly.
  • Keys are environment-specific and rate-limited. Nova Poshta throttles aggressively per key. Cache reads, debounce writes, and don't share one key across staging and production — sandbox and live documents are not interchangeable.
  • Language changes the payload. Switching ua ↔ ru alters every Description field. Pick one, store it, and don't mix them in cached responses (include language in cache keys if you ever support both).
  • Sender setup is a prerequisite. InternetDocument.save needs the sender's counterparty ref, contact person ref, city ref, and warehouse ref, plus DateTime in dd.mm.yyyy, weight in kilograms, and declared cost in UAH. Missing a ref produces a vague error, not a field-level one — validate your sender config once at boot with a Counterparty.getCounterparties call rather than discovering it at checkout.
  • Recipient creation is two calls. A new recipient needs Counterparty.save (with CounterpartyType: PrivatePerson) and ContactPerson.save before the waybill. Reuse by phone number where possible to avoid duplicate counterparties piling up in your account.
  • Warehouse refs migrate. Nova Poshta renumbers and decommissions branches; a stored warehouse ref on an old order can go stale. Resolve against the cached catalogue and fall back gracefully.
  • Ref vs Ref casing. The API mixes Ref, Refs, and ref in different methods. Copy field names verbatim from a recorded response.
  • Log the raw exchange. Log modelName, calledMethod, methodProperties (redact nothing but the key), and the response. When a waybill fails, the support team at Nova Poshta will ask for exactly this.
  • Pin the version and audit before upgrading. Single maintainer, near-zero dependents — treat it as vendored code. Read composer.lock diffs on updates, and keep the option of forking the client (it's MIT) if a method is missing; adding a new calledMethod is a one-line change in most implementations of this shape.
  • Extension point: if the client exposes a magic __call or a model() accessor, that's your escape hatch for methods the package author never wrapped — use it inside your gateway only, so the untyped path stays contained.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log