offline-agency/laravel-cart
A Laravel shopping cart package for adding products, updating quantities, calculating totals, and managing cart data through a simple, framework-friendly API. Designed to integrate cleanly into Laravel applications.
Install the package and publish its assets:
composer require offline-agency/laravel-cart
php artisan vendor:publish --provider="OfflineAgency\LaravelCart\LaravelCartServiceProvider"
php artisan migrate
Work through the published config/cart.php first — it controls the storage driver, the buyable model/table, and (as of 4.1.0) the guest-transfer toggle. Then skim the package's Cart facade / manager class to see the mutation surface (add, update, remove, clear, content, total).
A minimal first use case is adding a purchasable model from a controller:
use OfflineAgency\LaravelCart\Facades\Cart;
public function add(Product $product)
{
Cart::add($product, quantity: 1);
return back();
}
Your Product needs to satisfy the package's buyable contract (identifier, name, price). If the package ships a Buyable interface or trait, use it; otherwise the config maps the model and columns explicitly.
Line-item mutations. Treat every cart change as a mutation on an identified line item (id/rowId), not on the underlying model. Add returns or exposes the line identifier, which you use for subsequent update/remove calls. Store that identifier in your UI (hidden input, Livewire property) so quantity changes are unambiguous when the same product is added with different options.
Atomic mutations (4.1.0). Mutations now run inside a transaction for the database driver. This fixes the common partial-write bug where a line item was inserted but the cart totals row was not updated. You still get to compose multi-step operations:
Cart::transaction(function () use ($product, $coupon) {
Cart::add($product, 2);
Cart::applyCoupon($coupon);
});
Or rely on the built-in per-mutation atomicity. Batch related writes inside one transaction (add + coupon + shipping) so a failure rolls back the whole change rather than leaving a half-updated cart.
Guest transfer (opt-in). 4.1.0 makes guest-cart-to-user-cart transfer opt-in. Enable it in config/cart.php and wire it to your login flow:
// config/cart.php
'guest_transfer' => true,
Event::listen(Login::class, fn ($event) => Cart::transferGuestCart($event->user));
Decide your merge strategy explicitly: replace the user's existing cart, append guest items, or take the max quantity per line. Most stores want "merge, then delete guest cart," but a subscription or B2B flow may want "guest cart wins."
Checkout handoff. Snapshot the cart into an order at checkout rather than reading it live — prices and options can change between cart view and payment. Persist line items with their price-at-time-of-add so a price update mid-session doesn't silently change the order total.
Testing. Use the package's array/in-memory driver (or the session driver in a test) so cart tests don't touch the DB. Assert on content() and total() rather than raw session payloads, so your tests survive a storage-driver swap.
guest_transfer => true and add a test that logs in with a non-empty guest cart.Cache::lock("cart:{$user->id}")) or a unique constraint on (cart_id, buyable_id, options_hash).ksort) before mutating.php artisan config:clear after editing cart.php; config:cache in production will mask config changes until you re-cache.CartUpdated, item added/removed) are the intended hooks. Prefer listening to events over subclassing the manager — subclassing breaks on minor releases like this one.Cart::content() and the active driver from config('cart.storage') first — most "cart is empty" reports are a driver mismatch between the web and queue/CLI contexts (no session in a job).How can I help you explore Laravel packages today?