splash/shippingbo
Laravel integration for ShippingBo that wraps the ShippingBo API in a clean, configurable client. Manage orders, shipments, and logistics data from your PHP application with familiar Laravel conventions, simple setup, and straightforward syncing of fulfillment workflows.
This is a Splash connector bundle — a thin, framework-agnostic PHP client around the ShippingBo OpenAPI, packaged as a Symfony bundle that happens to be installable in a Laravel app. There is no Laravel service provider, no facade, no config publishing. Assume you are wiring it yourself.
Minimal path:
composer require splash/shippingbo — expect it to pull in splash/connector (the base HTTP/transport layer) and a PSR client such as Guzzle. If the dependency graph fights with your Laravel version, stop and check composer.json in the repo before going further; the "Embedded" suffix usually means the bundle is meant to be pulled into a host Splash application, not a standalone one.composer.json — required PHP version, Symfony bundle dependencies, transport library.src/ — find the main connector class (usually ShippingBo or ShippingBoConnector) and the Resources/ or Service/ directory. The resource classes are the API surface; everything else is plumbing.DependencyInjection/ or *.yaml file — this tells you the exact config keys the bundle expects (endpoint, credentials, timeout).$login/$password/$token parameters and any login() / auth() method. Call it once in tinker and dump the raw response before assuming the shape.dd() the hydrated DTOs, and confirm field names match ShippingBo's docs. Writes (order injection) are where you'll burn time debugging; do them second.Don't try to boot the Symfony bundle in Laravel. Instantiate the connector class directly with new inside a small wrapper service, or via a manual binding in a service provider. That's the whole integration.
Bind it once, inject everywhere. The connector is stateful (it holds an authenticated session/token). Register it as a singleton in a service provider, not a fresh instance per call:
$this->app->singleton(ShippingBo::class, fn () => new ShippingBo(
baseUrl: config('services.shippingbo.url'),
login: config('services.shippingbo.login'),
password: config('services.shippingbo.password'),
));
Put SHIPPINGBO_* keys in config/services.php, not config/app.php, and never in the repo.
Wrap it in a domain service. Never let the connector leak into controllers. One ShippingBoService with intention-revealing methods (pushOrder(Order $order), getStock(string $sku)) gives you a single seam to mock in tests and a single place to log request IDs.
Queue every write. Order creation, stock adjustments, shipment confirmations — all of these belong in a job with $tries, $backoff, and an idempotency key. ShippingBo is an external system on the other side of a network call; synchronous writes in a request cycle will bite you at the worst time. Dispatch with a dedicated queue so a slow fulfillment API doesn't starve your mail queue.
Map with explicit DTOs, not arrays. The connector returns hydrated objects (Splash convention) whose property names track the OpenAPI schema. Write a mapper class per entity — App\ShippingBo\Mappers\OrderMapper — that converts your Eloquent model into the payload the resource class expects. When ShippingBo changes a field name, you edit one mapper, not forty call sites.
Idempotent order push. Store the ShippingBo order reference on your local order row before/after the push, and check it first:
if ($order->shippingbo_ref) {
return; // already pushed
}
$response = $this->client->orders()->create($mapper->toPayload($order));
$order->update(['shippingbo_ref' => $response->reference]);
Stock sync as a scheduled command. Pull stock levels on a schedule (->hourly()), diff against local inventory, and write the delta. Don't mirror the whole catalog every run — page through results and use updatedSince/date filters if the resource exposes them.
Webhooks over polling where available. If ShippingBo offers webhook callbacks for shipment/status events, register a route, verify the signature (if any) in middleware, and queue the handling. Keep polling as a reconciliation job, not the primary path.
Multi-account. If you ship for several ShippingBo accounts, don't share the singleton — register a factory that returns a keyed connector per account, and pass the account through the job payload.
publishes(), no auto-discovery. If you see Extension/Configuration classes, they're for Symfony's DI container and are dead weight in Laravel — ignore them, instantiate directly.composer.json ("dev-main#abc1234" or a VCS repository entry). A composer update on a floating branch can silently change API signatures with no changelog to warn you.reconnect() on auth failure — check whether the base connector already retries once before you build it.release() on 429, and log the Retry-After header.Europe/Paris and either ISO 8601 or d/m/Y. Verify against a real sandbox response rather than trusting the schema; converting a date wrong produces silently misplaced shipments.$e->getPrevious() plus the raw body where you can capture it.curl against the sandbox. If that succeeds and the connector fails, it's the payload shape; if both fail, it's credentials or the endpoint.Http::fake() at the Guzzle layer or vcr-style recording) so a bump doesn't silently break you.ClientInterface. That's your hook for logging, retries, and mocking — swap it in the singleton binding rather than forking the package.How can I help you explore Laravel packages today?