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

Shop Client Laravel Package

paloma/shop-client

A PHP client for integrating Laravel applications with the Paloma shop API. It provides simple methods for product, cart, order, and customer operations, handling requests and responses so you can add storefront features quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin HTTP client for the Paloma middleware shop API. Treat it as glue code, not a framework — the integration surface is small and the value is in how you wrap it.

Minimal steps:

  1. composer require paloma/shop-client:^3.5 — the package is on a 3.x line with frequent patch releases (v3.5.4 lands right after v3.5.3), so a caret constraint is safe and gets you bug fixes. Read the compare link for each bump before composer update; if you have customized the client, still commit the lockfile so upgrades are deliberate.
  2. Publish config (if a service provider is present): php artisan vendor:publish --tag=shop-client-config. If auto-discovery is absent, register the provider manually in bootstrap/providers.php.
  3. Put credentials in .env — base URL, API key/token, and any tenant or shop identifier — and reference them only from config/shop-client.php. Never call env() outside config files; it returns null under php artisan config:cache.
  4. php artisan config:clear and run a single read call in php artisan tinker to confirm connectivity before writing any application code.

Where to look first, in order: the src/ tree for the client class constructor signature (it tells you what config keys actually exist, which is more reliable than the README), then config/, then tests/ for real request/response fixtures. If the README documents a method the src/ tree does not implement, believe src/.

First use case: a read-only health check — and since v3.5.4 adds customer custom data, make it two calls. Fetch one product or order by ID for the baseline, then fetch one customer and dd() the raw response. That validates authentication, base URL, and — critically — shows you the exact shape of the new custom-data field (object vs. JSON string, absent vs. empty array) before you build a DTO around it.

Implementation Patterns

Inject the client; do not reach for it globally. Resolve via constructor injection and let the container supply it. Facades are convenient in controllers but make the client impossible to stub in a service test without Facade::shouldReceive, which couples tests to the vendor API surface.

Wrap it in one adapter. Create app/Services/Shop/ShopGateway.php that owns the client and exposes domain methods (findProduct(string $sku): ProductData, findCustomer(string $id): CustomerData, listOrders(OrderQuery $q): Paginator). Map responses into your own DTOs at that boundary. This is the single most valuable pattern with a middleware dependency: when the API shape shifts, you rewrite one class instead of grepping 40 call sites.

Normalize custom data into a first-class value at the boundary. v3.5.4's Add customer custom data almost certainly means a free-form key/value bag on the customer payload. Do not let that bag reach your domain. Map it in the gateway to a typed accessor — CustomerData::custom(string $key, mixed $default = null) backed by an array property, or a small CustomData value object with ->get()/->has() — and document the keys your app actually uses in one class constant. Everyone downstream then codes against $customer->custom('loyalty_tier') rather than reaching into $customer->raw['custom_data']['loyalty_tier'] ?? null.

Never trust the bag's types. Custom data is caller-supplied by nature. Cast at ingestion: a loyalty tier that the API returns as "3" today and 3 tomorrow will break a strict match. Normalize strings/ints/bools explicitly in the DTO constructor and reject or default unknown structures there.

Persist or fetch, pick one deliberately. If custom data is read on every page render, Cache::remember() it alongside the customer. If your own app mutates custom data (e.g. writing a preferred_locale set by a local form), decide whether the shop API or your database is the source of truth — and if it's both, write through the API in a queued job and purge the cache key on success, not on dispatch.

Push writes onto the queue. Any mutation — order creation, status updates, customer custom-data updates — belongs in a job with $tries, backoff(), and failed() handling. Network calls to third-party middleware are exactly what retries were built for, and synchronous writes in a web request will bite you the first time the API is slow.

Cache reads, aggressively. Catalog and reference data are ideal for Cache::remember() with a 5–15 minute TTL and an explicit purge on webhook receipt. Shop data changes far less often than pages are rendered.

Test against fakes, not the network. Use Laravel's Http::fake() with a response fixture captured from your tinker call, or inject a Guzzle MockHandler if the client exposes its handler stack. Add a fixture for a customer with custom data and one without — the absent case is the one that breaks in production. Assert on your DTO, never on the raw vendor payload, so tests stay stable across upgrades like v3.5.4.

Centralize pagination. Whatever the API uses (cursor, page/limit, Link header), normalize it to a LengthAwarePaginator inside the gateway so controllers and Blade views never learn the vendor's paging dialect.

Multi-tenant binding. If you serve multiple shops, do not mutate a singleton's config at runtime. Register the client as a scoped binding keyed by tenant, or instantiate it explicitly per tenant inside the gateway.

Gotchas and Tips

Upgrade cadence beat documentation. The changelog for v3.5.4 is a single line — "Add customer custom data" — with no migration notes, no example payload, and no mention of the key name or type. This is typical of the package and means two things: (a) patch releases are low-risk by semver but you should skim the diff link before bumping, and (b) for any new field, the src/ tree and a live tinker response are your only reliable spec. Never code custom-data key names from the release note alone.

Custom data is a new optional field, so guard every read. Existing fixtures, existing cached customer objects serialized before the upgrade, and customers who simply have no custom data will all yield a missing key. Use ?? [] / a has() check rather than assuming presence — the first Attempt to read property on null from a Blade template is the most likely failure mode of this release.

Beware stale cached payloads across the upgrade. If you cache raw API responses or serialized DTOs, entries written before v3.5.4 won't carry the new field. Bump a cache-key version prefix (e.g. shop:customer:v2:{id}) when you deploy the upgrade rather than waiting for TTLs to expire.

Set timeouts explicitly. Client libraries frequently default to Guzzle's timeout: 0 (wait forever). Configure connect_timeout around 5s and timeout around 15–30s in the published config; a hung shop API should fail fast, not pin PHP-FPM workers.

Retries belong in middleware, not in a loop. If the client exposes a handler stack, push a retry middleware with exponential backoff and jitter, and only for idempotent GETs and 5xx/429 responses. Retrying a POST on a timeout can double-create orders — or double-append to custom data if the API treats it as additive.

Distinguish transport failures from API errors. A ConnectionException (DNS, TLS, timeout) is an infrastructure problem — alert on it. A 4xx with a JSON error body is a business problem — surface it to the user. Collapsing both into one catch (Exception $e) is the most common integration mistake and makes on-call debugging miserable.

Log requests, redact auth — and redact custom data. Guzzle's Middleware::log() is useful in local only. Strip Authorization and any API-key header, and be aware that customer custom data is a magnet for PII (notes, preferences, free-text tags). Treat request and response bodies as sensitive; log field names, not values.

Config caching surprises. env() calls buried in the package's own source (rather than its config file) will silently return null in production. If credentials appear missing only after deployment, this is almost always the cause — cache the config locally and re-test.

Extension points to check for: a configurable Guzzle handler stack (best case — lets you add logging, retries, and metrics without forking), a macroable client trait, or an injectable HTTP client interface. If none exist, keep your gateway wrapper and consider vendoring the package (repositories + path in composer.json) so you can patch it without waiting on an upstream release.

Watch for silent type drift. Untyped or array-shaped responses mean a renamed API field returns null rather than throwing — and a schemaless custom-data bag makes that risk permanent rather than transitional. Validate the decoded payload in your gateway and fail loudly with a domain exception for required keys, defaulting only for genuinely optional ones. You want a clear error at the boundary, not a confusing null three layers up in a Blade template.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route