Carts hold a collection of purchasable items (typically product variants) that a customer intends to order. They belong to users (which relate to customers) and support a single currency each.
Cart prices are dynamically calculated and are not stored in the database. Once a cart is converted to an order, the prices are persisted on the order instead.
A cart can be assigned an explicit tax zone, which lets line totals be calculated tax-inclusive before a shipping address is captured. This is typically wired up in middleware that infers the customer’s tax zone from their IP address.
use Lunar\Models\Cart;use Lunar\Models\TaxZone;$cart->setTaxZone(TaxZone::find(1));$cart->setTaxZone(null); // Clear the explicit tax zone.
setTaxZone triggers a recalculation by default. Pass false as the second argument to defer the recalculation.
When setShippingAddress is called on a cart that already has an explicit tax zone, the zone is cleared so that address-based resolution can take over. Pass clearTaxZone: false to keep the explicit zone in place.
When adding, updating, or removing items, a series of validation actions are run (defined in config/lunar/cart.php). These throw a CartException on failure.
The primary exception for cart validation failures. It is thrown by the validation pipeline when adding, updating, or removing cart lines, or when creating an order. It wraps a MessageBag containing one or more error messages.
use Lunar\Exceptions\Carts\CartException;try { $cart->add($purchasable, quantity: 500);} catch (CartException $e) { $e->getMessage(); // Summary of the first error $e->errors(); // Returns the full MessageBag}
When creating an order, the ValidateCartForOrderCreation validator may throw a CartException with one of the following messages:
Error Key
Cause
carts.order_exists
The cart already has a completed order.
carts.billing_missing
No billing address has been set on the cart.
carts.billing_incomplete
The billing address is missing required fields (country_id, first_name, line_one, city, postcode).
carts.shipping_missing
The cart contains shippable items but has no shipping address.
carts.shipping_incomplete
The shipping address is missing required fields.
carts.shipping_option_missing
The cart contains shippable items but no shipping option has been selected.
Thrown when attempting to add a purchasable to the cart with a quantity of zero or less.
use Lunar\Exceptions\InvalidCartLineQuantityException;try { $cart->add($purchasable, quantity: 0);} catch (InvalidCartLineQuantityException $e) { // Quantity must be at least 1}
Thrown when creating or updating a cart line with a model that does not implement the Lunar\Base\Purchasable interface. This is checked automatically by the CartLineObserver.
Thrown when attempting to remove a cart line that does not belong to the specified cart.
use Lunar\Exceptions\CartLineIdMismatchException;try { $cart->remove($cartLineId);} catch (CartLineIdMismatchException $e) { // The cart line ID does not belong to this cart}
Each cart can have a shipping and billing address, represented by the CartAddress model. These addresses are used when calculating tax breakdowns and shipping costs.
An Address model or a CartAddress model can also be passed:
use Lunar\Models\Address;$shippingAddress = Address::first();$cart->setShippingAddress($shippingAddress);// Use the same address for billing$cart->setBillingAddress($cart->shippingAddress);
Retrieve addresses via the properties:
$cart->shippingAddress;$cart->billingAddress;
During a cart’s early lifetime, address information may not yet be available. Some countries don’t display tax until checkout. The address-based tax calculation is designed to handle this: the addresses can be set when they become available.
It may be useful to show an estimated shipping cost before a full address is provided. The getEstimatedShipping method returns the cheapest available shipping option based on partial address data.
A user can be associated directly on the cart model. The policy parameter controls how the association behaves when the user already has an existing cart: merge combines the carts, while override replaces the existing one.
use Lunar\Facades\CartSession;$cart = CartSession::current();// Or via dependency injectionuse Lunar\Base\CartSessionInterface;public function __construct( protected CartSessionInterface $cartSession) { // ...}
When current() is called, the behavior depends on the auto_create config. With auto_create set to false (default), null is returned if no cart exists, preventing unnecessary database records.
use Lunar\Facades\CartSession;// Associate a cart with a user and set it as the session cart// The third argument is the auth policy: 'merge' or 'override'CartSession::associate($cart, $user, 'merge');
When a user logs in, Lunar automatically listens to authentication events and handles cart association. If the user had a guest cart, it will be merged with (or override) any existing cart on their account, depending on the auth_policy config.
Carts are dynamic: items, quantities, and prices can change at any moment. To detect whether a cart has been modified (e.g. on a different browser tab during checkout), use the fingerprint system:
use Lunar\Exceptions\FingerprintMismatchException;$fingerprint = $cart->fingerprint();try { $cart->checkFingerprint('previously_stored_fingerprint');} catch (FingerprintMismatchException $e) { // Cart has changed, refresh it}
The fingerprint generator class can be customized in config/lunar/cart.php:
Over time, unused carts accumulate in the database. Lunar can automatically prune carts that have no associated order.Enable pruning in config/lunar/cart.php:
return [ // ... 'prune_tables' => [ 'enabled' => false, // Set to true to enable 'pipelines' => [ Lunar\Pipelines\CartPrune\PruneAfter::class, Lunar\Pipelines\CartPrune\WithoutOrders::class, Lunar\Pipelines\CartPrune\WhereNotMerged::class, ], 'prune_interval' => 90, // days ],];
Option
Description
Default
enabled
Whether automatic pruning is active.
false
prune_interval
Number of days to retain carts before pruning.
90
pipelines
Pipeline classes that determine which carts are eligible for removal.