# Laravel Shopping Cart Laravel Shopping Cart is a high-performance e-commerce package for Laravel 11/12 that provides a complete shopping cart solution with advanced features like tax calculation, discount management, coupon systems, and flexible storage options. Built with modern PHP 8.2+ features, it offers developers an intuitive API for managing shopping carts, wishlists, and comparison lists within their Laravel applications. The package is designed for production environments with performance optimizations including Cache::memo() integration for 99% fewer config lookups, smart memory management reducing usage by 99%, and database query optimizations that are 87% faster than traditional implementations. It supports both session-based and database-driven storage, handles 10,000+ concurrent users efficiently, and provides a complete feature set including the Buyable trait for seamless model integration, multiple cart instances for different use cases, and comprehensive currency formatting helpers. ## Installation and Setup Install via Composer and configure storage ```bash # Install the package composer require saeedvir/shopping-cart # Publish configuration file php artisan vendor:publish --tag=shopping-cart-config # For database storage, publish and run migrations php artisan vendor:publish --tag=shopping-cart-migrations php artisan migrate ``` ```php // config/shopping-cart.php return [ 'storage' => 'database', // or 'session' 'tax' => [ 'enabled' => true, 'default_rate' => 0.15, // 15% 'included_in_price' => false, ], 'currency' => [ 'code' => 'USD', 'symbol' => '$', 'decimals' => 2, ], 'expiration' => 10080, // 7 days in minutes 'limits' => [ 'max_items' => 100, 'max_quantity_per_item' => 999, ], ]; ``` ## Add Items to Cart Add products to cart with various methods ```php use Saeedvir\ShoppingCart\Facades\Cart; use App\Models\Product; // Add a product model with quantity $product = Product::find(1); Cart::add($product, 2); // Add 2 items // Add with custom attributes (size, color, etc.) Cart::add($product, 1, [ 'size' => 'Large', 'color' => 'Blue', 'custom_text' => 'Happy Birthday', ]); // Add manually with array Cart::add([ 'buyable_type' => Product::class, 'buyable_id' => 1, 'name' => 'Wireless Headphones', 'price' => 99.99, ], 1); // Add with custom tax rate Cart::add([ 'buyable_type' => Product::class, 'buyable_id' => 5, 'name' => 'Premium Product', 'price' => 199.99, 'tax_rate' => 0.20, // 20% tax for this item ], 2); // Response: Returns CartItem instance with generated ID // CartItem { id: "cart_item_abc123", quantity: 2, price: 99.99 } ``` ## Using the Buyable Trait Add cart functionality directly to your models ```php // app/Models/Product.php use Illuminate\Database\Eloquent\Model; use Saeedvir\ShoppingCart\Traits\Buyable; class Product extends Model { use Buyable; protected $fillable = ['name', 'price', 'stock']; } // Usage in controllers or views $product = Product::find(1); // Add product to cart $product->addToCart(2); // Add 2 items // Add with attributes $product->addToCart(1, [ 'size' => 'Medium', 'color' => 'Red', ]); // Check if product is in cart if ($product->inCart()) { echo "Already in cart"; } // Get cart item for this product $cartItem = $product->cartItem(); if ($cartItem) { echo "Quantity: {$cartItem->quantity}"; echo "Subtotal: " . $cartItem->formattedSubtotal(); } // Remove from cart $product->removeFromCart(); // Works with different instances if ($product->inCart('wishlist')) { echo "In wishlist"; } ``` ## Retrieve and Display Cart Items Get cart items and display information ```php use Saeedvir\ShoppingCart\Facades\Cart; // Get all cart items $items = Cart::items(); foreach ($items as $item) { echo $item->name; // Product name echo $item->quantity; // Quantity echo $item->price; // Unit price echo $item->getSubtotal(); // Price × Quantity echo $item->getTax(); // Tax amount echo $item->getTotal(); // Total with tax // Access custom attributes $size = $item->attributes['size'] ?? 'N/A'; $color = $item->attributes['color'] ?? 'N/A'; // Formatted currency values echo $item->formattedPrice(); // "$99.99" echo $item->formattedSubtotal(); // "$199.98" echo $item->formattedTotal(); // "$229.98" } // Get specific item by ID $item = Cart::get($itemId); if ($item) { echo $item->name; } // Get total item count (sum of quantities) $count = Cart::count(); // Returns 5 if you have 2+3 items // Check if cart is empty if (Cart::isEmpty()) { echo "Your cart is empty"; } else { echo "You have {$count} items in cart"; } ``` ## Update Cart Items Modify quantities, prices, or attributes ```php use Saeedvir\ShoppingCart\Facades\Cart; // Update quantity only Cart::update($itemId, [ 'quantity' => 5, ]); // Update price (e.g., for dynamic pricing) Cart::update($itemId, [ 'price' => 89.99, ]); // Update attributes Cart::update($itemId, [ 'attributes' => [ 'size' => 'Large', 'color' => 'Green', ], ]); // Update multiple fields at once Cart::update($itemId, [ 'quantity' => 3, 'price' => 79.99, 'attributes' => [ 'size' => 'Medium', 'gift_wrap' => true, ], ]); // Returns updated CartItem instance or null if not found $updatedItem = Cart::update($itemId, ['quantity' => 10]); if ($updatedItem) { echo "Updated successfully"; } ``` ## Remove Items and Clear Cart Delete items or clear entire cart ```php use Saeedvir\ShoppingCart\Facades\Cart; // Remove specific item Cart::remove($itemId); // Remove using Buyable trait $product = Product::find(1); $product->removeFromCart(); // Clear all items but keep cart metadata and conditions Cart::clear(); // Completely destroy cart (removes from storage) Cart::destroy(); // In a controller with validation public function remove($itemId) { $item = Cart::get($itemId); if (!$item) { return back()->with('error', 'Item not found'); } Cart::remove($itemId); return back()->with('success', 'Item removed from cart'); } ``` ## Tax Calculation Automatic tax calculation based on configuration ```php use Saeedvir\ShoppingCart\Facades\Cart; // Get cart totals $subtotal = Cart::subtotal(); // Before tax: 249.99 $tax = Cart::tax(); // Tax amount: 37.50 (15% of subtotal) $total = Cart::total(); // Final total: 287.49 // Get formatted amounts with currency echo Cart::formattedSubtotal(); // "$249.99" echo Cart::formattedTax(); // "$37.50" echo Cart::formattedTotal(); // "$287.49" // Per-item tax information foreach (Cart::items() as $item) { echo "Item: {$item->name}"; echo "Subtotal: " . $item->formattedSubtotal(); // "$99.98" echo "Tax: " . $item->formattedTax(); // "$15.00" echo "Total: " . $item->formattedTotal(); // "$114.98" } // Add item with custom tax rate Cart::add([ 'buyable_type' => Product::class, 'buyable_id' => 10, 'name' => 'Luxury Item', 'price' => 500.00, 'tax_rate' => 0.25, // 25% tax rate ], 1); // Tax calculation respects configuration // If tax is included in price, it's extracted // If not included, it's added to the total ``` ## Apply Discounts and Conditions Add discounts, fees, and other conditions ```php use Saeedvir\ShoppingCart\Facades\Cart; // Apply percentage discount Cart::condition('holiday_sale', 'discount', 20, 'percentage'); // Apply fixed amount discount Cart::condition('new_customer', 'discount', 10.00, 'fixed'); // Apply shipping fee Cart::condition('shipping', 'fee', 5.99, 'fixed'); // Apply handling fee Cart::condition('handling', 'fee', 2.50, 'fixed'); // Get discount amount $discount = Cart::discount(); // Total discount amount // Get final total (includes all conditions) $total = Cart::total(); // Subtotal + Tax - Discount + Fees // Remove a condition Cart::removeCondition('shipping'); // Conditional logic for free shipping if (Cart::subtotal() >= 50) { Cart::removeCondition('shipping'); } else { Cart::condition('shipping', 'fee', 9.99, 'fixed'); } // Bulk discount example if (Cart::count() >= 10) { Cart::condition('bulk_discount', 'discount', 15, 'percentage'); } // Example: Complete pricing display echo "Subtotal: " . Cart::formattedSubtotal(); // "$249.99" echo "Tax (15%): " . Cart::formattedTax(); // "$37.50" echo "Discount: -" . Cart::formattedDiscount(); // "$50.00" echo "Shipping: $5.99"; echo "Total: " . Cart::formattedTotal(); // "$243.48" ``` ## Coupon System Apply and validate coupon codes ```php use Saeedvir\ShoppingCart\Facades\Cart; use Saeedvir\ShoppingCart\Exceptions\InvalidCouponException; use App\Models\Coupon; // Apply coupon with validation callback try { Cart::applyCoupon('SAVE20', function ($code, $cart) { // Fetch coupon from database $coupon = Coupon::where('code', $code) ->where('is_active', true) ->where('starts_at', '<=', now()) ->where('expires_at', '>=', now()) ->first(); // Validate coupon exists if (!$coupon) { return false; } // Check minimum purchase requirement if ($cart->subtotal() < $coupon->minimum_purchase) { return false; } // Check maximum usage if ($coupon->usage_count >= $coupon->max_uses) { return false; } // Apply discount based on coupon type if ($coupon->type === 'percentage') { $cart->condition('coupon', 'discount', $coupon->value, 'percentage'); } else { $cart->condition('coupon', 'discount', $coupon->value, 'fixed'); } // Increment usage count $coupon->increment('usage_count'); return true; }); echo "Coupon applied successfully!"; } catch (InvalidCouponException $e) { echo "Invalid or expired coupon code"; } // Remove coupon Cart::removeCondition('coupon'); // Check if coupon is applied $metadata = Cart::getMetadata(); if (isset($metadata['coupon'])) { echo "Coupon code: {$metadata['coupon']}"; } ``` ## Multiple Cart Instances Support for cart, wishlist, compare, and custom instances ```php use Saeedvir\ShoppingCart\Facades\Cart; // Default shopping cart Cart::instance('default')->add($product, 2); // Or without instance (uses 'default') Cart::add($product, 2); // Wishlist instance Cart::instance('wishlist')->add($product, 1); $wishlistItems = Cart::instance('wishlist')->items(); $wishlistCount = Cart::instance('wishlist')->count(); // Compare list instance Cart::instance('compare')->add($product1, 1); Cart::instance('compare')->add($product2, 1); $compareItems = Cart::instance('compare')->items(); // Custom instance for saved items Cart::instance('saved-for-later')->add($product, 1); // Each instance has separate items and totals echo "Cart total: " . Cart::instance('default')->formattedTotal(); echo "Wishlist count: " . Cart::instance('wishlist')->count(); // Move item from wishlist to cart $item = Cart::instance('wishlist')->get($itemId); if ($item) { Cart::instance('default')->add([ 'buyable_type' => $item->buyableType, 'buyable_id' => $item->buyableId, 'name' => $item->name, 'price' => $item->price, ], $item->quantity); Cart::instance('wishlist')->remove($itemId); } // Clear specific instance Cart::instance('wishlist')->clear(); // With database storage, each instance is stored separately // Allows same user to have cart, wishlist, and compare list // Uses composite unique key: identifier + instance ``` ## Cart Metadata Store additional information with the cart ```php use Saeedvir\ShoppingCart\Facades\Cart; // Set individual metadata Cart::setMetadata('gift_message', 'Happy Birthday!'); Cart::setMetadata('gift_wrap', true); Cart::setMetadata('delivery_date', '2025-12-25'); Cart::setMetadata('shipping_method', 'express'); Cart::setMetadata('customer_note', 'Please ring doorbell'); // Get specific metadata $message = Cart::getMetadata('gift_message'); // "Happy Birthday!" $giftWrap = Cart::getMetadata('gift_wrap'); // true // Get all metadata $allMetadata = Cart::getMetadata(); /* [ 'gift_message' => 'Happy Birthday!', 'gift_wrap' => true, 'delivery_date' => '2025-12-25', 'shipping_method' => 'express', 'customer_note' => 'Please ring doorbell', ] */ // Use metadata in checkout public function checkout(Request $request) { if ($request->has('gift_message')) { Cart::setMetadata('gift_message', $request->gift_message); } if ($request->gift_wrap) { Cart::setMetadata('gift_wrap', true); Cart::condition('gift_wrap_fee', 'fee', 4.99, 'fixed'); } // Process order with metadata $cartData = Cart::toArray(); $order = Order::create([ 'total' => $cartData['total'], 'metadata' => $cartData['metadata'], ]); } ``` ## Identifier Helper Methods Extract user ID or session ID from cart identifiers ```php use Saeedvir\ShoppingCart\Facades\Cart; // Get user ID from identifier (if pattern is "user_") $userId = Cart::getUserId(); // Example: identifier "user_123" returns 123 // Returns null if not a user pattern // Get session ID from identifier (if NOT a user pattern) $sessionId = Cart::getUserSessionId(); // Example: identifier "session_abc123" returns "session_abc123" // Returns null if it's a user pattern // Practical usage in analytics if ($userId = Cart::getUserId()) { // This is a logged-in user's cart $user = User::find($userId); logger("Cart activity for user: {$user->email}"); // Track user behavior Analytics::track('cart_activity', [ 'user_id' => $userId, 'cart_total' => Cart::total(), ]); } elseif ($sessionId = Cart::getUserSessionId()) { // This is a guest/session cart logger("Guest cart: {$sessionId}"); // Prompt user to login session()->flash('message', 'Login to save your cart'); } // Merge guest cart with user cart on login public function mergeCartOnLogin($userId) { $guestCart = Cart::instance('default'); $guestItems = $guestCart->items(); // Switch to user cart $userIdentifier = "user_{$userId}"; $userCart = new Cart(app(CartStorageInterface::class), $userIdentifier); // Transfer items foreach ($guestItems as $item) { $userCart->add([ 'buyable_type' => $item->buyableType, 'buyable_id' => $item->buyableId, 'name' => $item->name, 'price' => $item->price, ], $item->quantity, $item->attributes); } // Clear guest cart $guestCart->destroy(); } ``` ## Currency Formatting Format prices with currency symbols ```php use Saeedvir\ShoppingCart\Facades\Cart; // Cart-level formatted amounts echo Cart::formattedSubtotal(); // "$249.99" echo Cart::formattedTax(); // "$37.50" echo Cart::formattedDiscount(); // "$25.00" echo Cart::formattedTotal(); // "$262.49" // Item-level formatted amounts $item = Cart::items()->first(); echo $item->formattedPrice(); // "$99.99" echo $item->formattedSubtotal(); // "$199.98" echo $item->formattedTax(); // "$30.00" echo $item->formattedTotal(); // "$229.98" // Using global helper functions echo cart_currency(99.99); // "$99.99" echo cart_currency(1234.56); // "$1,234.56" echo cart_currency_symbol(); // "$" echo cart_currency_code(); // "USD" // Format without symbol echo cart_currency(99.99, false); // "99.99" // Currency configuration in config/shopping-cart.php 'currency' => [ 'code' => 'USD', 'symbol' => '$', 'decimals' => 2, 'decimal_separator' => '.', 'thousand_separator' => ',', ], // Display in Blade templates @foreach(Cart::items() as $item) {{ $item->name }} {{ $item->formattedPrice() }} {{ $item->quantity }} {{ $item->formattedTotal() }} @endforeach Subtotal: {{ Cart::formattedSubtotal() }} Tax: {{ Cart::formattedTax() }} Total: {{ Cart::formattedTotal() }} ``` ## Load Product Details (Avoid N+1 Queries) Efficiently load product data for all cart items ```php use Saeedvir\ShoppingCart\Facades\Cart; // Load all product data at once $cart = Cart::instance('default'); $cart->loadBuyables(); // Loads all products with single query per model type // Now safely access product details without additional queries foreach ($cart->items() as $item) { echo $item->buyable->name; echo $item->buyable->description; echo $item->buyable->image_url; echo $item->buyable->stock; // No N+1 queries! All products loaded in bulk } // Example in controller public function index() { $cart = Cart::instance('default'); $cart->loadBuyables(); // Prevent N+1 queries return view('cart.index', [ 'items' => $cart->items(), 'total' => $cart->formattedTotal(), ]); } // In Blade template - product details available @foreach($items as $item)
{{ $item->name }}

{{ $item->buyable->name }}

{{ $item->buyable->description }}

Stock: {{ $item->buyable->stock }}

Price: {{ $item->formattedPrice() }}

@endforeach // Performance comparison: // Without loadBuyables(): N queries (1 per item) // With loadBuyables(): 1 query (bulk load) // For 50 items: 50 queries vs 1 query = 98% fewer queries ``` ## Cart Summary and Export Get complete cart data as array or JSON ```php use Saeedvir\ShoppingCart\Facades\Cart; // Get complete cart summary $summary = Cart::toArray(); /* Returns array: [ 'identifier' => 'user_123', 'instance' => 'default', 'items' => [ [ 'id' => 'cart_item_abc123', 'buyable_type' => 'App\\Models\\Product', 'buyable_id' => 1, 'name' => 'Wireless Headphones', 'quantity' => 2, 'price' => 99.99, 'attributes' => ['color' => 'Blue'], 'subtotal' => 199.98, 'tax' => 30.00, 'total' => 229.98, ], ], 'count' => 5, 'subtotal' => 249.99, 'tax' => 37.50, 'discount' => 25.00, 'total' => 262.49, 'conditions' => [ 'holiday_sale' => [ 'name' => 'holiday_sale', 'type' => 'discount', 'value' => 20, 'target' => 'percentage', ], ], 'metadata' => [ 'gift_message' => 'Happy Birthday!', 'gift_wrap' => true, ], ] */ // Export to JSON $json = Cart::toJson(); $prettyJson = Cart::toJson(JSON_PRETTY_PRINT); // Send cart data to API $response = Http::post('https://api.example.com/cart', Cart::toArray()); // Store cart snapshot $order = Order::create([ 'user_id' => auth()->id(), 'cart_snapshot' => Cart::toJson(), 'total' => Cart::total(), ]); // Display in view return view('cart.index', [ 'cart' => Cart::toArray(), ]); ``` ## Complete Checkout Flow Full example with validation, coupons, and order creation ```php use Saeedvir\ShoppingCart\Facades\Cart; use Saeedvir\ShoppingCart\Exceptions\InvalidCouponException; use Illuminate\Support\Facades\DB; use App\Models\Order; use App\Models\Coupon; public function checkout(Request $request) { // Validate cart is not empty if (Cart::isEmpty()) { return redirect()->back()->with('error', 'Cart is empty'); } // Validate stock availability foreach (Cart::items() as $item) { $product = Product::find($item->buyableId); if (!$product || $product->stock < $item->quantity) { return redirect()->back() ->with('error', "Insufficient stock for {$item->name}"); } } // Apply coupon if provided if ($request->filled('coupon_code')) { try { Cart::applyCoupon($request->coupon_code, function($code, $cart) { $coupon = Coupon::where('code', $code) ->where('is_active', true) ->where('starts_at', '<=', now()) ->where('expires_at', '>=', now()) ->first(); if (!$coupon) { return false; } if ($cart->subtotal() < $coupon->minimum_purchase) { return false; } if ($coupon->type === 'percentage') { $cart->condition('coupon', 'discount', $coupon->value, 'percentage'); } else { $cart->condition('coupon', 'discount', $coupon->value, 'fixed'); } return true; }); } catch (InvalidCouponException $e) { return redirect()->back()->with('error', 'Invalid coupon code'); } } // Apply shipping based on subtotal if (Cart::subtotal() >= 75) { Cart::removeCondition('shipping'); // Free shipping } else { Cart::condition('shipping', 'fee', 9.99, 'fixed'); } // Store gift message if provided if ($request->filled('gift_message')) { Cart::setMetadata('gift_message', $request->gift_message); } // Create order in transaction DB::beginTransaction(); try { // Create order $order = Order::create([ 'user_id' => auth()->id(), 'subtotal' => Cart::subtotal(), 'tax' => Cart::tax(), 'discount' => Cart::discount(), 'total' => Cart::total(), 'metadata' => Cart::getMetadata(), ]); // Create order items foreach (Cart::items() as $item) { $order->items()->create([ 'product_id' => $item->buyableId, 'name' => $item->name, 'quantity' => $item->quantity, 'price' => $item->price, 'attributes' => $item->attributes, 'tax' => $item->getTax(), 'total' => $item->getTotal(), ]); // Update product stock Product::find($item->buyableId) ->decrement('stock', $item->quantity); } // Increment coupon usage if applied $couponCode = Cart::getMetadata('coupon'); if ($couponCode) { Coupon::where('code', $couponCode)->increment('usage_count'); } // Clear cart after successful order Cart::destroy(); DB::commit(); return redirect()->route('order.success', $order) ->with('success', 'Order placed successfully!'); } catch (\Exception $e) { DB::rollBack(); return redirect()->back() ->with('error', 'Order failed. Please try again.'); } } ``` ## Laravel Shopping Cart is a comprehensive e-commerce solution designed for modern Laravel applications requiring robust shopping cart functionality. The package excels in production environments where performance and scalability are critical, supporting features like persistent carts across devices, automatic cart recovery for logged-in users, and efficient handling of large cart sizes. With support for multiple cart instances, developers can implement wishlists, comparison tools, and saved-for-later features without managing separate systems. The built-in tax engine handles complex scenarios including tax-inclusive pricing and per-item custom rates, while the condition system allows flexible discount and fee management. Integration is straightforward with Laravel's service container and facade system, requiring minimal configuration to get started. The Buyable trait enables any Eloquent model to become cart-compatible with just one line of code, while the package's storage abstraction allows switching between session and database storage based on application needs. Performance optimizations including Cache::memo() for configuration, calculation memoization, and bulk loading methods ensure the package maintains responsiveness even under heavy load. The comprehensive helper functions and formatted currency methods make it easy to display cart information in views, while the complete API supports headless commerce implementations and third-party integrations through array and JSON exports.