shamarkellman / flex-cart
A flexible shopping cart package for Laravel 12
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/collections: ^11.0||^12.0
- illuminate/contracts: ^11.0||^12.0
- illuminate/database: ^11.0||^12.0
- illuminate/support: ^11.0||^12.0
- moneyphp/money: ^4.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
This package is auto-updated.
Last update: 2026-03-31 17:37:39 UTC
README
FlexCart is a flexible shopping cart package for Laravel 12+ that provides comprehensive cart functionality including multiple storage backends, tax calculations, shipping management, coupon/discounts, and Money PHP integration.
Features
- Multiple storage backends (session and database)
- Flexible coupon/discount system with multiple discount types
- Tax calculations with configurable rates
- Shipping address and cost management
- Comprehensive event system for cart lifecycle hooks
- Money PHP integration for accurate financial calculations
- Custom exception handling with detailed error messages
Installation
You can install the package via composer:
composer require shamarkellman/flex-cart
You can publish and run the migrations with:
php artisan vendor:publish --tag="flex-cart-migrations"
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="flex-cart-config"
Usage
Basic Cart Operations
use ShamarKellman\FlexCart\Facades\FlexCart; use Money\Money; use Money\Currency; // Add product to cart $product = Product::find(1); $cartItem = FlexCart::addItem($product, 2); // Add item with options (e.g., size, color) $cartItemWithOptions = FlexCart::addItem($product, 1, [ 'size' => 'large', 'color' => 'blue' ]); // Update quantity FlexCart::updateItem($cartItem->id, 3); // Remove specific item FlexCart::removeItem($cartItem->id); // Clear entire cart FlexCart::clear();
Cart Information
// Get all cart items $items = FlexCart::items(); // Get specific item $item = FlexCart::getItem($itemId); // Count total items (sum of quantities) $totalItems = FlexCart::count(); // Check if cart is empty $isEmpty = FlexCart::isEmpty(); // Get cart totals $subtotal = FlexCart::subtotal(); // Money object $tax = FlexCart::tax(); // Money object $shipping = FlexCart::shippingCost(); // Money object $total = FlexCart::total(); // Money object // Get raw amounts echo $total->getAmount(); // '10000' (in cents) echo $total->getCurrency()->getCode(); // 'USD'
Coupon System
FlexCart includes a comprehensive coupon/discount system supporting multiple discount types.
Adding Coupons
use ShamarKellman\FlexCart\Facades\FlexCart; use ShamarKellman\FlexCart\Models\Coupon; // Add a coupon to cart $coupon = Coupon::where('code', 'SAVE20')->first(); $result = FlexCart::addCoupon($coupon); // Check if coupon was added successfully if ($result) { echo "Coupon applied!"; } // Check if cart has a specific coupon $hasCoupon = FlexCart::hasCoupon('SAVE20'); // Get coupon details $coupon = FlexCart::getCoupon('SAVE20');
Removing Coupons
// Remove a specific coupon FlexCart::removeCoupon('SAVE20'); // Clear all coupons FlexCart::clearCoupons();
Getting Discounts
// Get general discount (products) $generalDiscount = FlexCart::generalDiscount(); // Get shipping discount $shippingDiscount = FlexCart::shippingDiscount(); // Get total discount $totalDiscount = FlexCart::totalDiscount(); // Get subtotal after discounts $subtotalWithCoupons = FlexCart::subtotalWithCoupons(); // Get final total (subtotal + shipping - discounts) $total = FlexCart::total();
Coupon Model
Create coupons in your database using the CouponFactory:
use ShamarKellman\FlexCart\Models\Coupon; // Fixed amount coupon ($20 off) $coupon = Coupon::factory()->fixedAmount(20)->create([ 'code' => 'SAVE20', 'is_stackable' => true, ]); // Percentage coupon (10% off) $coupon = Coupon::factory()->percentage(10)->create([ 'code' => 'PERCENT10', 'is_stackable' => false, ]); // Free shipping coupon $coupon = Coupon::factory()->shipping(0)->create([ 'code' => 'FREESHIP', ]); // Coupon with restrictions $coupon = Coupon::factory() ->withMinimumAmount(50) // $50 minimum ->withUsageLimit(100) // 100 uses max ->expiresInDays(30) // Expires in 30 days ->create([ 'code' => 'SPECIAL', ]);
Coupon Types
| Type | Description | Value Format |
|---|---|---|
TYPE_FIXED_AMOUNT |
Fixed discount on order total | Amount in cents |
TYPE_PERCENTAGE |
Percentage off order total | Percentage (0-100) |
TYPE_SHIPPING |
Free or discounted shipping | Amount in cents |
Coupon Features
- Stackable coupons: Allow multiple coupons to be applied
- Usage limits: Limit total number of uses
- Minimum amount: Require minimum order total
- Product-specific: Apply to specific products only
- Exclusions: Exclude specific products from discount
- Expiration: Set expiration dates
- Activation dates: Set when coupon becomes active
Coupon Events
use ShamarKellman\FlexCart\Events\CouponApplied; use ShamarKellman\FlexCart\Events\CouponRemoved; use ShamarKellman\FlexCart\Events\CouponExpired; Event::listen(CouponApplied::class, function ($event) { $coupon = $event->getCoupon(); logger()->info('Coupon applied', ['code' => $coupon->getCode()]); }); Event::listen(CouponRemoved::class, function ($event) { $coupon = $event->getCoupon(); logger()->info('Coupon removed', ['code' => $coupon->getCode()]); }); Event::listen(CouponExpired::class, function ($event) { $coupon = $event->getCoupon(); logger()->info('Coupon expired', ['code' => $coupon->getCode()]); });
Shipping Management
// Set shipping address FlexCart::setShippingAddress([ 'first_name' => 'John', 'last_name' => 'Doe', 'company' => 'Acme Corp', // optional 'address_line_1' => '123 Main St', 'address_line_2' => 'Apt 4B', // optional 'city' => 'New York', 'state' => 'NY', 'postal_code' => '10001', 'country' => 'US', 'phone' => '+1-555-0123', // optional 'email' => 'john@example.com', // optional ]); // Set shipping cost FlexCart::setShippingCost(new Money(1000, new Currency('USD'))); // $10.00 // Get shipping details $shippingDetail = FlexCart::getShippingDetail(); echo $shippingDetail->first_name; // 'John' echo $shippingDetail->country; // 'US'
Cart Item Operations
$cartItem = FlexCart::addItem($product, 2); // Get item details $quantity = $cartItem->getQuantity(); $unitPrice = $cartItem->getUnitPrice(); $totalPrice = $cartItem->getTotal(); $taxAmount = $cartItem->getTaxAmount(); $options = $cartItem->getOptions(); $buyableProduct = $cartItem->getBuyable(); // Modify item directly $cartItem->setQuantity(5); $cartItem->setOptions(['size' => 'XL', 'gift_wrap' => true]);
Working with Different Currencies
// Set default currency in config/flex-cart.php 'default_currency' => 'EUR', // Or change currency dynamically FlexCart::setShippingCost(new Money(1000, new Currency('EUR')));
Advanced Usage Examples
// Check if item exists before adding if (!FlexCart::getItem($productId)) { FlexCart::addItem($product, 1); } // Add multiple items at once $products = [ ['product' => $product1, 'quantity' => 2, 'options' => ['size' => 'M']], ['product' => $product2, 'quantity' => 1, 'options' => ['color' => 'red']], ]; foreach ($products as $item) { FlexCart::addItem($item['product'], $item['quantity'], $item['options'] ?? []); } // Calculate totals with tax (if tax rate is configured) $subtotal = FlexCart::subtotal(); $taxRate = config('flex-cart.tax_rate', 0); $totalWithTax = FlexCart::total(); // Format money for display $formatter = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY); echo $formatter->formatCurrency($total->getAmount() / 100, $total->getCurrency()->getCode()); // Output: $100.00
Configuration
Environment Variables
# .env file CART_DEFAULT_CURRENCY=USD CART_TAX_RATE=8.25 CART_STORAGE_DRIVER=session
Configuration File
After publishing the config file (config/flex-cart.php):
<?php return [ // Default currency for all cart operations 'default_currency' => env('CART_DEFAULT_CURRENCY', 'USD'), // Tax rate as percentage (e.g., 8.25 for 8.25%) 'tax_rate' => env('CART_TAX_RATE', 0), // Storage configuration 'storage' => [ // Driver: 'session' or 'database' 'driver' => env('CART_STORAGE_DRIVER', 'session'), // Session key for session storage 'session_key' => 'shopping_cart', // Available storage drivers 'drivers' => [ 'session' => \ShamarKellman\FlexCart\Storage\SessionStorage::class, 'database' => \ShamarKellman\FlexCart\Storage\DatabaseStorage::class, ], ], // Custom model classes (if you want to extend them) 'models' => [ 'cart' => \ShamarKellman\FlexCart\Models\Cart::class, 'cart_item' => \ShamarKellman\FlexCart\Models\CartItem::class, 'shipping_detail' => \ShamarKellman\FlexCart\Models\ShippingDetail::class, ], ];
Making Models Buyable
To use your models with FlexCart, implement the BuyableInterface:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use ShamarKellman\FlexCart\Contracts\BuyableInterface; use Money\Money; use Money\Currency; class Product extends Model implements BuyableInterface { // ... existing model code public function getCartItemIdentifier(): int|string { return $this->id; } public function getCartItemName(): string { return $this->name; } public function getPrice(): Money { // Assuming price is stored in cents in database return new Money($this->price_cents, new Currency('USD')); // Or if stored as decimal: // return new Money((int) ($this->price * 100), new Currency('USD')); } public function getCartItemOptions(): array { return [ 'size' => $this->size, 'color' => $this->color, // Add any other relevant options ]; } }
API Reference
FlexCart Facade Methods
Cart Management
addItem(Model $buyable, int $quantity = 1, array $options = []): CartItemInterfaceupdateItem(int|string $itemId, int $quantity): boolremoveItem(int|string $itemId): boolclear(): void
Cart Information
items(): Collection<CartItemInterface>getItem(int|string $itemId): ?CartItemInterfacecount(): intisEmpty(): bool
Totals and Calculations
subtotal(): MoneysubtotalNet(): Moneytax(): MoneyshippingCost(): Moneytotal(): Money
Coupon Methods
addCoupon(CouponInterface $coupon): boolremoveCoupon(string $code): boolhasCoupon(string $code): boolgetCoupon(string $code): ?CouponInterfacegetCoupons(): CollectionclearCoupons(): voidgeneralDiscount(): MoneyshippingDiscount(): MoneytotalDiscount(): MoneysubtotalWithCoupons(): MoneycouponDiscount(): Money
Shipping
setShippingAddress(array $address): voidsetShippingCost(Money $cost): voidgetShippingDetail(): ?ShippingDetail
CartItemInterface Methods
getQuantity(): intgetUnitPrice(): MoneygetTotal(): MoneygetTaxAmount(): MoneygetOptions(): arraysetQuantity(int $quantity): voidsetOptions(array $options): voidgetBuyable(): Model
BuyableInterface Methods
getCartItemIdentifier(): int|stringgetCartItemName(): stringgetPrice(): MoneygetCartItemOptions(): array
Events
FlexCart dispatches events for various cart operations, allowing you to hook into cart lifecycle events:
Cart Events
ShamarKellman\FlexCart\Events\ItemAddedToCart
Fired when an item is added to the cart for the first time.
Event::listen(ItemAddedToCart::class, function ($event) { $cartItem = $event->getCartItem(); $quantity = $event->getAddedQuantity(); $buyable = $event->getBuyable(); $options = $event->getOptions(); });
ShamarKellman\FlexCart\Events\QuantityUpdated
Fired when an item's quantity is modified.
Event::listen(QuantityUpdated::class, function ($event) { $cartItem = $event->getCartItem(); $oldQuantity = $event->getOldQuantity(); $newQuantity = $event->getNewQuantity(); $isIncrease = $event->isIncrease(); $isDecrease = $event->isDecrease(); });
ShamarKellman\FlexCart\Events\ItemRemovedFromCart
Fired when an item is removed from the cart.
Event::listen(ItemRemovedFromCart::class, function ($event) { $cartItem = $event->getCartItem(); $removedQuantity = $event->getRemovedQuantity(); $unitPrice = $event->getUnitPrice(); });
ShamarKellman\FlexCart\Events\CartCleared
Fired when the entire cart is cleared.
Event::listen(CartCleared::class, function ($event) { $clearedItems = $event->getClearedItems(); $itemCount = $event->getClearedItemCount(); $totalQuantity = $event->getClearedTotalQuantity(); $subtotal = $event->getClearedSubtotal(); });
ShamarKellman\FlexCart\Events\ShippingCostSet
Fired when shipping cost is set or changed.
Event::listen(ShippingCostSet::class, function ($event) { $oldCost = $event->getOldCost(); $newCost = $event->getNewCost(); $difference = $event->getDifference(); $hasIncreased = $event->hasIncreased(); $hasDecreased = $event->hasDecreased(); });
ShamarKellman\FlexCart\Events\ShippingAddressSet
Fired when shipping address is set or changed.
Event::listen(ShippingAddressSet::class, function ($event) { $oldAddress = $event->getOldAddress(); $newAddress = $event->getNewAddress(); $isFirstAddress = $event->isFirstAddress(); $hasChanged = $event->hasChanged(); $country = $event->getCountry(); $postalCode = $event->getPostalCode(); });
Coupon Events
ShamarKellman\FlexCart\Events\CouponApplied
Fired when a coupon is applied to the cart.
ShamarKellman\FlexCart\Events\CouponRemoved
Fired when a coupon is removed from the cart.
ShamarKellman\FlexCart\Events\CouponExpired
Fired when a coupon expires.
Storage Backends
Session Storage (Default)
- Stores cart data in Laravel sessions
- Perfect for guest carts
- Automatically cleaned when session expires
- No database overhead
Database Storage
- Persists carts across sessions
- Supports user authentication integration
- Enables cart analytics and recovery
- Requires published migrations
To use database storage:
// In config/flex-cart.php 'storage' => [ 'driver' => 'database', // ... ],
Don't forget to publish and run the migrations:
php artisan vendor:publish --tag="flex-cart-migrations"
php artisan migrate
Testing
composer test
Troubleshooting
Common Issues
"Buyable model must implement BuyableInterface"
Make sure your model implements the BuyableInterface and all required methods.
Cart items not persisting
Check your storage driver configuration. For database storage, ensure migrations are published and run.
Tax calculations seem incorrect
Verify your tax rate configuration. The rate should be a percentage (e.g., 8.25 for 8.25%).
Money currency mismatch
Ensure all Money objects use the same currency, or configure the default currency properly.
Debug Mode
To debug cart operations, you can temporarily access the cart instance:
$cart = app(\ShamarKellman\FlexCart\FlexCart::class); dd($cart->items(), $cart->total());
Performance Considerations
- Use session storage for guest carts to reduce database load
- Implement cart cleanup jobs for abandoned carts
- Consider Redis for session storage in production
- Add indexes to the database tables (included in migrations)
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.