spatie/spatie-price-api
A small Laravel package that retrieves product prices from the spatie.be API, used on promotional sites for Spatie's own products. Open source but not intended for third-party use.
Architecture fit
This is a thin HTTP client plus a Blade/Alpine helper, not a pricing platform. The server-side surface is essentially a single static call — App\Support\SpatiePrices\SpatiePriceApi::getPriceForPurchasable() — and since 1.7.0 a SpatiePriceApi::scripts() helper that registers the spatiePrice(id) and spatieBundlePrice(id) Alpine components and drives a discount countdown in the browser. The footprint is small enough that the question is not "does it fit our architecture?" but "does the pattern fit?" The pattern — thin client → price payload → Blade helper → Alpine component → countdown — maps cleanly onto a Laravel + Blade/Livewire + Alpine front end.
The problem is the dependency, not the shape. The package hard-couples a business-critical value (price) to a host we do not control (spatie.be), with no provider abstraction, no local price store, and no fallback dataset. In 1.7.0 the resolution boundary also moves from our server to the visitor's browser. That is correct for promotional display and wrong for anything authoritative: a client-side fetch and a client clock are both untrusted, so expiry, eligibility, and discount application must still be re-validated server-side at cart and checkout.
Integration feasibility
Composer-installable, MIT, no heavy transitive dependencies, tested in CI. Effort for a Laravel app is minutes. Feasibility is high and relevance is low: the README states plainly that the package is not intended for third-party use, the backing endpoint is undocumented and unversioned, Packagist shows zero dependents, and the release cadence tracks Spatie's own catalogue and Laravel support matrix. Adopting it means pinning our promotional pricing to someone else's internal contract with no migration path if the response shape changes.
The realistic integration is a reference-implementation read (~half a day), followed by an internal equivalent (~2 engineer-days for a scoped spike) against a pricing source we control. The Alpine components are small; re-implementing them is cheaper than the governance overhead of carrying an unsupported dependency.
Technical risk
scripts() injects JS. Verify against Vite entry points and our CSP before assuming inline injection is acceptable; on a Vue/React/Inertia stack the components would be rewritten anyway.Key questions
spatie.be? If not (expected), this is a pattern study, not an integration.Stack fit
Strong fit for the shape, conditional fit for the package. Laravel 10–13 support, PHP 7.4+ history, Blade + Alpine components that cost nothing to evaluate if we are already on Livewire 3. Wherever we run Vue/React/Inertia, the front-end half is not reusable and only the server-side client pattern transfers.
Migration path
There is no meaningful migration path away from this package — that is the core objection. It has no provider abstraction; swapping in our own API means rewriting the class. We should therefore never reach a state where migration is required:
composer require the package into a production service.Compatibility
Composer/PHP/Laravel constraints are satisfied by any modern stack. The real compatibility questions are operational: CSP and the asset pipeline for scripts(), presence of Alpine, and whether the chosen pricing source returns regional/localized values — the Alpine components inherit whatever the API returns, so per-region display needs no extra front-end work, provided a server-side source backs it.
Sequencing
Interface and DTO first (so the source is swappable), then the server-rendered price in initial HTML, then the Alpine enhancement and countdown, then measurement. Building the client component first inverts the dependency and bakes in the upstream package's core mistake.
Maintenance
As a dependency: near-zero code to maintain but non-trivial risk to carry — single maintainer, a cadence driven by Laravel version bumps, an undocumented upstream contract, and no dependents to surface breakage early. We would be the only affected party and would not be consulted. Built internally: ~50–100 lines to own, plus an interface, cache, and DTO — well within one team's capacity and straightforward to test.
Support
Upstream support is best-effort from a vendor using the package on its own sites. No SLA, no deprecation policy, no channel for a third-party consumer to raise an issue that gets prioritised. Internal ownership is the only defensible support model for a value displayed on a page we sell from.
Scaling
Two distinct load profiles. Server-side, each call is a synchronous outbound request on the render path — so it needs a cache (short TTL per purchasable/region) and a circuit breaker, otherwise page latency and availability inherit the upstream's. Client-side, every visitor issues their own fetch: that traffic comes from end-user browsers, not our servers, so it bypasses our CDN and shared cache entirely, scales linearly with sessions, and is subject to per-user rate limiting and ad-blockers. Neither profile is addressed by the package.
Failure modes
Upstream 5xx or timeout → missing price, or an exception on the render path if uncaught. Schema drift → silently rendered blanks or wrong numbers. Alpine not loaded or blocked by CSP → price never appears. Stale CDN/proxy cache → price disagreeing with checkout at the moment of purchase. Countdown expiry on a client clock → urgency shown on an offer that has already ended (or dismissed on one still live). The common thread is that every failure is silent and visual rather than an error we would alert on; the only reliable detector is the checkout total disagreeing with the page.
Ramp-up
Trivial in code terms — one method, one helper — which is precisely the trap: a half-day to integrate, and then an indefinite tail of undocumented-contract risk. Budget the ramp-up against the internal pattern instead: roughly two days for a spike plus a short review with whoever owns pricing and checkout integrity. The deliverable worth keeping is the interface, the cached DTO, and the server-rendered fallback — not the dependency.
How can I help you explore Laravel packages today?