treestoneit/shopping-cart
A flexible shopping cart package for Laravel with support for multiple cart instances, item conditions like tax and discounts, and session or database storage. Clean API for adding items, updating quantities, and calculating totals.
Install the package and publish its config/migrations:
composer require treestoneit/shopping-cart
php artisan vendor:publish --provider="Treestoneit\ShoppingCart\ShoppingCartServiceProvider"
php artisan migrate
Start with the package config to decide your storage driver. Use session for guest carts and database when you need carts to survive logout, device changes, or abandoned-cart jobs. Check the published config for the exact driver key and table names.
First useful flow: add a product, attach its Eloquent model, and read the cart back.
use Treestoneit\ShoppingCart\Facades\Cart;
Cart::add(
id: $product->id,
name: $product->name,
quantity: 1,
price: $product->price
)->associate($product);
$items = Cart::content();
$total = Cart::total();
Where to look first:
Cart facade/contract and its instance methods.Treat the package as the cart state layer, not the checkout authority. Recalculate prices, stock, discounts, and tax on the server at checkout.
Guest cart vs. authenticated cart
Use the session driver for anonymous users and the database driver for logged-in users. On login, merge the guest cart into the user cart, then clear the guest instance. Keep the merge logic in a dedicated service so it is testable and not buried in a controller.
// Pseudocode shape: verify exact package methods.
$guestItems = Cart::instance('guest')->content();
foreach ($guestItems as $item) {
Cart::instance('user-'.$user->id)->add(
$item->id,
$item->name,
$item->quantity,
$item->price
)->associate($item->model);
}
Cart::instance('guest')->destroy();
Multiple cart instances
Multiple instances are the main reason to choose this package over a simple session array. Use them deliberately:
Cart::instance('default')->add(...);
Cart::instance('wishlist')->add(...);
Cart::instance('compare')->add(...);
Keep instance names predictable and scoped. For user-specific carts, include the user ID in the instance name or use the package’s user/identifier binding if available. Do not let controllers invent instance names ad hoc; centralise them in constants or a cart service.
Attach Eloquent models
Attaching models is useful for rendering cart lines without re-querying every product. Use the associated model for display data such as image, slug, and current stock message. Do not trust the cart row as the source of truth for price or availability at checkout.
$item = Cart::add($product->id, $product->name, $qty, $product->price)
->associate($product);
$item->model->image_url;
At checkout, reload each product, validate stock, and compare the current price with the cart price. If they differ, either update the cart or fail with a clear message.
React to changes with events
Use package events for side effects: analytics, stock reservation, coupon recalculation, abandoned-cart signals, and audit logs.
Event::listen(CartItemAdded::class, function ($event) {
// Track add-to-cart analytics.
});
Event::listen(CartItemRemoved::class, function ($event) {
// Release any soft stock reservation.
});
Keep listeners small. If a listener updates the cart, guard against recursion. Prefer queued listeners for slow work such as CRM sync or analytics.
Tax and formatting
Use the package’s formatting helpers in Blade/API responses, but calculate the authoritative total in your checkout service. Confirm whether tax is configured as inclusive or exclusive, and whether rounding happens per item or on the total.
{{ Cart::total() }}
{{ Cart::tax() }}
For APIs, return integer minor units (cents) and formatted strings separately. Do not let the frontend compute totals from item prices.
Database persistence
If using the database driver:
Testing
Test cart flows with the driver you actually deploy. Use Event::fake() to assert add/update/remove events. Write explicit tests for guest-to-user merge, quantity updates, tax rounding, deleted products, and checkout recalculation.
Cart::destroy() may clear more than intended depending on API behaviour. Clear the specific instance, and test that wishlist/compare carts survive checkout.php artisan config:clear. A cached config is a common source of “why is it still using sessions?” confusion.CartService and CheckoutService around it. Extend via custom event listeners, formatter overrides, or a custom storage driver rather than editing vendor code.Cart::content() directly, then inspect the session payload or database cart tables. Confirm the package’s service provider is discovered, migrations have run, and the configured driver matches the environment.Overall, this package is a good fit for Laravel applications that need multiple cart instances and simple session/database persistence without building cart state from scratch. It is not a full checkout or promotions engine: keep pricing, tax, stock validation, and order creation in your own domain layer, and use the package for cart state, item association, and change events.
How can I help you explore Laravel packages today?