---
title: "Cart — Shops for Laravel | Roundly"
description: "A persistent cart for customers or guests — add variants, price it with or without a coupon, update lines and validate quantities."
url: https://roundly-consulting.com/open-source/docs/shops-for-laravel/cart
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Shops for Laravel](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)

# Cart

A Cart is a persistent basket with an optional polymorphic owner (a user) or a guest token, plus its own currency. Add variants and read its price through the same engine orders use:

```php
use Illuminate\Support\Str;
use RoundlyConsulting\Shops\Cart\Cart;
use RoundlyConsulting\Shops\Facades\Shops;

// A signed-in customer's cart:
$cart = Cart::create([
    'currency' => 'EUR',
    'owner_type' => $user->getMorphClass(),
    'owner_id' => $user->id,
]);

// A guest cart, found again by its token (the package ships no cart-merge logic):
$guest = Cart::create(['currency' => 'EUR', 'token' => (string) Str::uuid()]);
$guest = Cart::where('token', $token)->first();

$item = Shops::cart($cart)->add($variant, 2);   // snapshots name/sku/price/tax class; same variant increments
$cart->add($variant);                          // the same, as a model method

Shops::cart($cart)->update($item, 3);          // set a line's quantity
Shops::cart($cart)->update($item, 0);          // 0 removes the line (returns null)
Shops::cart($cart)->remove($item);
Shops::cart($cart)->clear();                   // every line; the cart itself stays

$cart->items;                                  // Collection<CartItem>
Shops::cart($cart)->subtotal();                // Money — sum of line totals, before discount
Shops::cart($cart)->price();                   // Price DTO — using the stored coupon_code
Shops::cart($cart)->price('WELCOME10');        // Price DTO — preview another code
$cart->update(['coupon_code' => 'WELCOME10']);

$cart->owner;                                  // ?Model
$cart->isGuest();                              // true when owner_id is null
```

- add() snapshots the variant’s name (or the product name when the variant has none), SKU, price and tax class. Adding the same variant again increments the existing line. $cart->add() is the same call as a model method.
- The cart row is locked while a line is added, so a double-clicked add lands on one line.
- A variant priced in another currency throws CurrencyMismatch and the cart is left unchanged.
- update() and remove() refuse a line of another cart with ForeignItemException before anything is written.
- price() uses shops.pricing.price\_type, the cart’s shop for tax lookup, no country, zero shipping and the discount from the bound DiscountResolver. It never records a redemption.
- add(), update() and remove() drop a loaded items relation, so the next price() or subtotal() reads the change.

## Cart actions

The handle calls these actions, all under RoundlyConsulting\\Shops\\Actions\\Cart. The cart always comes first, and the foreign-line check lives in the actions, so the action form enforces it too:

| Action | Signature | Behaviour |
| --- | --- | --- |
| `AddToCartAction` | `execute(Cart $cart, ProductVariant $variant, int $quantity = 1): CartItem` | Behind cart()->add() and $cart->add(); runs under the cart’s row lock, so a double-clicked add lands on one line. |
| `UpdateCartItemAction` | `execute(Cart $cart, CartItem $item, int $quantity): ?CartItem` | Sets the quantity; 0 deletes the line and returns null; negative or over the maximum throws; another cart’s line throws ForeignItemException. |
| `RemoveFromCartAction` | `execute(Cart $cart, CartItem $item): void` | Soft-deletes the line; another cart’s line throws ForeignItemException. |
| `ClearCartAction` | `execute(Cart $cart): Cart` | Soft-deletes every line and returns the refreshed cart. |

```php
use RoundlyConsulting\Shops\Actions\Cart\AddToCartAction;
use RoundlyConsulting\Shops\Actions\Cart\ClearCartAction;
use RoundlyConsulting\Shops\Actions\Cart\RemoveFromCartAction;
use RoundlyConsulting\Shops\Actions\Cart\UpdateCartItemAction;

$item = app(AddToCartAction::class)->execute($cart, $variant, 2);   // CartItem

app(UpdateCartItemAction::class)->execute($cart, $item, 3);   // the cart comes first
app(UpdateCartItemAction::class)->execute($cart, $item, 0);   // 0 removes the line (returns null)
app(RemoveFromCartAction::class)->execute($cart, $item);      // or remove it explicitly
app(ClearCartAction::class)->execute($cart);                  // remove every line, returns the fresh cart

Shops::cart($otherCart)->update($item, 2);                    // ForeignItemException — not that cart's line
```

## Quantities

A quantity is a whole number of items from 1 to 32 767 (Support\\Quantity::MAX, the range of the quantity columns). Anything else — zero or negative in add() or at checkout, negative in update(), a fraction, or a line that would grow past the maximum — throws InvalidQuantityException before anything is written. CartItem and order Item guard quantity on every write too:

```php
use RoundlyConsulting\Shops\Support\Quantity;

Quantity::MAX;                            // 32767 — the range of the quantity columns

Shops::cart($cart)->add($variant, 0);     // InvalidQuantityException
Shops::cart($cart)->update($item, -1);    // InvalidQuantityException
$item->update(['quantity' => '3']);       // integer strings (request input) are accepted
$item->update(['quantity' => 1.5]);       // InvalidQuantityException
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)
