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.
Architecture fit. grisaia/nova-poshta is a PHP client for Nova Poshta's API v2 — a JSON-RPC-style HTTP API (POST with modelName / calledMethod / methodProperties) covering address/reference data, pricing, waybill (InternetDocument) creation, tracking, and counterparty management. Architecturally this is a leaf dependency: it sits behind your order/fulfilment domain and touches nothing else. That is favourable — it means the blast radius of adopting it is bounded, and it does not dictate your domain model. However, the vendor name suggests a Laravel-oriented package while the description describes a plain PHP implementation; the first technical question is whether it is genuinely framework-agnostic (PSR-18/PSR-7 HTTP client, no Illuminate coupling) or a Laravel-only wrapper. If it is Laravel-only, it must still be confined to an infrastructure adapter so your checkout, pricing, and fulfilment services never call it directly.
Integration feasibility. Low-to-moderate effort for the happy path: composer require, an API key in env, and a handful of calls (city search, warehouse list, getDocumentPrice, InternetDocument/save, TrackingDocument/getStatusDocuments). The real work is not the client — it is the surrounding contract: caching the warehouse/city reference data (Nova Poshta has tens of thousands of warehouses, paginated at ~500 per call), mapping your order/address model to NP's Counterparty + ContactRecipient + City/Warehouse reference keys, and handling the API's error semantics. A competent Laravel engineer can produce a working proof of concept in one to two days; production hardening is one to two weeks.
Technical risk. This is the dominant concern and it is supply-chain, not code. One star, zero dependents, and a single-maintainer vendor is a bus-factor-1 dependency with no community support surface. Concretely:
dev-main, or a mis-scraped registry entry. Confirm that a tagged, semver'd release exists and that composer.json declares sane PHP and Laravel version constraints.call($model, $method, $props) passthrough. A passthrough gives full coverage but zero type safety and no guard against typos in methodProperties; typed methods are nicer but risk being incomplete for your flows (returns, printDocument/marking labels, getTimeIntervals, cargo types).{"success": false, "errors": [...]}. If the package does not convert this into an exception, every call site will silently succeed on failure. This is the single most important thing to verify in the source before adoption.InternetDocument/save creates a real waybill. A retried request after a timeout can produce a duplicate shipment and a real billing event. The package almost certainly does not solve this; your adapter must.Key questions. (1) Is it PSR-18/PSR-7 based, or hard-wired to Guzzle/cURL? (2) Does it ship a Laravel service provider, publishable config, and facade — and are those optional? (3) Are success: false payloads raised as exceptions with the raw errors[] preserved? (4) Is the base URI and HTTP client injectable for sandbox and test doubles? (5) What is the actual endpoint coverage versus the v2 method list you need? (6) Are PHP/Laravel version constraints compatible with our runtime, and is there a tagged release? (7) Is the maintainer responsive — check issue/PR response times, or assume no response.
Stack fit. Assume Laravel on PHP 8.2+ with Composer. The package fits as an infrastructure adapter, not a domain service. Recommended layering: App\Domain\Shipping (your interfaces: RateQuote, WarehouseLocator, ShipmentCreator, ShipmentTracker) → App\Infrastructure\NovaPoshta\* (thin classes that are the only place grisaia/nova-poshta is imported) → the package → NP API. Bind the domain interfaces in a service provider. This costs perhaps a day and buys you the option to swap the package for an in-house client at any time without touching business logic — which, given the adoption signals, is the responsible default. Treat the package as replaceable and presumed temporary.
Migration path. There is nothing to migrate from the package; the migration concern is the reverse — an exit path. Design the adapter so that (a) only the adapter knows about NP's modelName/calledMethod naming, (b) NP Ref GUIDs are stored in your own schema (shipping_shipments.provider_ref, warehouses.provider_ref) rather than leaking into domain entities, and (c) all outbound calls go through one NovaPoshtaClient wrapper where you own retries, timeouts, and logging. If the package is abandoned, replacing the wrapper's internals is a contained task (roughly 300–500 LOC) rather than a refactor.
Compatibility. Pin the dependency to an exact or ~ constraint and commit composer.lock. Because there is one maintainer and no dependents, a breaking change in a patch release is plausible and no one will file the issue for you. Add a Composer conflict/version audit step in CI and an automated dependency-update PR that a human reviews — do not auto-merge updates to this package. Verify Laravel version constraints explicitly; if it only declares illuminate/support: ^10, it will not install on a newer skeleton, and the vendor will not patch it promptly.
Sequencing. Phase 0 — spike (1–2 days): read the source with the seven key questions in hand, run getCities and getWarehouses against the sandbox, confirm the error model. Gate: if success: false is not surfaced, stop and write the client in-house — that alone justifies the ~400 LOC. Phase 1 — reference data: warehouse/city sync command into a local table, scheduled refresh, search endpoint for the storefront. Phase 2 — pricing: getDocumentPrice plus getDocumentDeliveryDate at checkout, cached per (city, warehouse, weight, cost) tuple for a short TTL. Phase 3 — waybill creation: Counterparty/save (third-party or recipient), InternetDocument/save, persist the returned Ref and IntDocNumber, and emit a label print job. Phase 4 — tracking: scheduled polling of TrackingDocument/getStatusDocuments in batches (Nova Poshta does not push status webhooks), mapped to your own shipment status enum, with outbound notification hooks. Phase 5 — returns and edge flows. Ship phases 1–2 behind a feature flag and keep a manual-entry fallback for phase 3 until you have a full billing cycle of clean data.
Maintenance. Assume you own this code, not the vendor. Budget a small but real recurring cost: a monthly dependency review, a quarterly check that NP has not changed or versioned the v2 endpoints, and an owner on the fulfilment team. The package's own surface is unlikely to change, but your usage of it will, and NP's reference data (warehouse closures, new Ref keys, city renames) changes continuously — that is an operational data problem, not a library problem, and it belongs to you either way.
Support. Effectively zero external support: no community, no dependents to crib answers from, no guaranteed issue response. Internal documentation becomes mandatory — a short ADR recording why this package was chosen, the exact API methods used, sandbox credentials location, and the replacement criteria that would trigger building in-house. Also record the NP-side escalation path (account manager, API support) since the library vendor will not help with 4xx/auth/billing issues.
Scaling. NP API v2 throughput is fine for typical Ukrainian e-commerce volumes (thousands of orders/day) but the API is not designed for chatty per-request use from a storefront. The scaling plan is therefore mostly caching and batching: serve city/warehouse search from a local indexed table (never hit NP from a customer keystroke), batch tracking lookups (the tracking method accepts multiple documents per call), queue waybill creation so a slow NP response does not block checkout, and place a circuit breaker in front of outbound calls. Reference data sync is the heaviest operation — run it on a schedule in a queue worker with pagination, never inline.
Failure modes. Rank them and instrument accordingly: (1) HTTP 200 with success: false — silent failure, the most dangerous, must be an exception and a metric; (2) timeout after InternetDocument/save — ambiguous outcome causing duplicate waybills; mitigate with an idempotency key derived from your order ID, a pre-create lookup, and an alert on Ref count mismatch per order; (3) stale reference data — customers selecting a closed warehouse; mitigate with TTL plus validation at waybill creation; (4) auth/key rotation — a dead key looks like an outage; alert on auth-specific error codes; (5) rate limiting or maintenance windows — degrade to "delivery options unavailable, retry later" rather than failing the order; (6) deprecation of API v2 by Nova Poshta — the highest-impact long-tail risk, worth a quarterly check and the reason the adapter boundary exists.
Ramp-up. For a PHP/Laravel engineer: 2–4 hours to read the package source and the NP v2 docs, 1–2 days to a working sandbox integration, 1–2 weeks to production quality including caching, queueing, retries, observability, and tests. Testing story matters more than usual — verify the HTTP client is injectable so you can test against recorded NP fixtures rather than a live sandbox in CI; if it is not injectable, that is another point in favour of writing the client yourself. The team-learning cost is low; the organizational cost is in accepting permanent ownership of a barely-adopted dependency, which is why the recommendation is adopt behind a strict adapter, with an explicit, pre-agreed trigger for replacing it rather than adopting it as trusted infrastructure.
How can I help you explore Laravel packages today?