setono/post-nord-php-sdk
A PHP SDK for integrating PostNord shipping services into your application. Provides convenient access to PostNord APIs for creating shipments, generating labels, and tracking parcels, with a clean, framework-friendly interface for Laravel and other PHP projects.
The Setono PostNord PHP SDK wraps PostNord's shipping and logistics APIs (shipment booking, label generation, tracking, and returns) in a typed PHP client. It is not a Laravel-first package — there is no service provider or facade — so you wire it up yourself inside config/services.php and a small binding in a service provider.
Minimal setup:
composer require setono/post-nord-php-sdk:2.0.0-alpha.3 — pin the exact alpha. Alphas in a 2.0.0 line are pre-stable, and between alpha tags the public API can shift without a deprecation path.config/services.php:
'postnord' => [
'api_key' => env('POSTNORD_API_KEY'),
'customer_number'=> env('POSTNORD_CUSTOMER_NUMBER'),
'base_uri' => env('POSTNORD_BASE_URI', 'https://atapi2.postnord.com/rest'),
],
AppServiceProvider::register() so you reuse the HTTP client and don't rebuild it per request.Client / ClientFactory and the Model (DTO) directory. The DTOs are the real API surface — they tell you required vs. optional fields faster than the docs do.Wrap, don't sprinkle. Keep the SDK out of controllers and jobs. Create an App\Shipping\PostNord\PostNordGateway class that accepts your DTOs, maps them to SDK models, and returns your own result objects. When the SDK breaks (and in alpha it will), you change one class.
Bind via factory, not direct instantiation. Register a singleton client in a dedicated provider:
$this->app->singleton(PostNord::class, fn ($app) => new PostNord(
apiKey: config('services.postnord.api_key'),
customerNumber: config('services.postnord.customer_number'),
baseUri: config('services.postnord.base_uri'),
));
Queue label generation. Booking + PDF retrieval are two HTTP round-trips with a binary payload. Run them in a queued job (ShouldQueue), store the resulting PDF on a disk (Storage::disk('s3')->put(...)), and persist only the path plus the shipment ID. Never hold a PDF blob in a job payload or a DB column.
Persist the external identity immediately. Store PostNord's shipment/parcel ID as soon as the booking response returns, before attempting label retrieval. If label generation fails you can retry the label without double-booking a shipment.
Treat tracking as pull-primary, push-optional. The SDK exposes tracking lookups; PostNord also supports push notifications. Pull on a schedule (every 15–30 min for active shipments) as the reliable path, and layer webhook handling on top as an accelerator. Webhooks arrive out of order, so always reconcile against the pull API rather than trusting payload state.
Idempotency. PostNord's booking endpoints are not universally idempotent. Generate your own idempotency key (order ID + attempt) and persist it so a retried job doesn't create duplicate shipments — check "do I already have a shipment ID for this order?" before calling.
Testing. The SDK is HTTP-client based, so swap in a PSR-18 mock or a fake HTTP client and return recorded fixtures. Commit fixtures for at least: successful booking, validation error, auth failure, and label-ready. This is the only way to have fast tests without sandbox credentials.
"2.0.0-alpha.3" (exact) and read the diff on GitHub before bumping. Do not use ^2.0@dev.customer_number doesn't match the key's account, or you're pointing the same key at sandbox and production base URIs. Keep them paired in config per environment.base_uri silently sends sandbox traffic to production or vice versa.X-Request-Id on every call. That request ID is what PostNord support will ask for when you file a ticket.ShipmentBooked, LabelReady, TrackingUpdated) from the gateway so the rest of the app stays decoupled from the SDK, and so you can swap carriers later without touching listeners.How can I help you explore Laravel packages today?