From 7478f69bcf187987df4922e4e022fd22316cf197 Mon Sep 17 00:00:00 2001 From: "Fabian @ Blax Software" Date: Wed, 17 Dec 2025 09:24:42 +0100 Subject: [PATCH] I cart facade --- src/Facades/Cart.php | 1 + src/Models/Cart.php | 16 +++++- src/Models/Product.php | 20 ++++++- src/Services/CartService.php | 96 +++++++++++++++++++++++--------- tests/Feature/CartFacadeTest.php | 2 +- 5 files changed, 102 insertions(+), 33 deletions(-) diff --git a/src/Facades/Cart.php b/src/Facades/Cart.php index 8576cdb..07ab981 100644 --- a/src/Facades/Cart.php +++ b/src/Facades/Cart.php @@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Facade; * @method static \Blax\Shop\Models\CartItem|true remove(\Illuminate\Database\Eloquent\Model $product, int $quantity = 1, array $parameters = []) * @method static \Blax\Shop\Models\CartItem update(\Blax\Shop\Models\CartItem $cartItem, int $quantity) * @method static int clear(\Blax\Shop\Models\Cart|null $cart = null) + * @method static void clearSession() * @method static \Illuminate\Support\Collection|mixed checkout(\Blax\Shop\Models\Cart|null $cart = null) * @method static float total(\Blax\Shop\Models\Cart|null $cart = null) * @method static int itemCount(\Blax\Shop\Models\Cart|null $cart = null) diff --git a/src/Models/Cart.php b/src/Models/Cart.php index 8dc9879..ca14b06 100644 --- a/src/Models/Cart.php +++ b/src/Models/Cart.php @@ -5,6 +5,7 @@ namespace Blax\Shop\Models; use Blax\Shop\Contracts\Cartable; use Blax\Shop\Enums\CartStatus; use Blax\Shop\Enums\ProductType; +use Blax\Shop\Services\CartService; use Blax\Workkit\Traits\HasExpiration; use Carbon\Carbon; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -176,6 +177,17 @@ class Cart extends Model }); } + /** + * Store the cart ID in the session for retrieval across requests + * + * @param Cart $cart + * @return void + */ + public static function setSession(Cart $cart): void + { + session([CartService::CART_SESSION_KEY => $cart->id]); + } + /** * Add an item to the cart or increase quantity if it already exists. * @@ -226,7 +238,7 @@ class Cart extends Model ); } } - + // Add items one at a time for progressive pricing $lastCartItem = null; for ($i = 0; $i < $quantity; $i++) { @@ -357,7 +369,7 @@ class Cart extends Model // Get price for the next available item $pricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, null, $from, $until); $regularPricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, false, $from, $until) ?? $pricePerDay; - + // If no price found from pool items, try the pool's direct price as fallback if ($pricePerDay === null && $cartable->hasPrice()) { $pricePerDay = $cartable->defaultPrice()->first()?->getCurrentPrice($cartable->isOnSale()); diff --git a/src/Models/Product.php b/src/Models/Product.php index 6bc12ac..97ed275 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -16,6 +16,7 @@ use Blax\Shop\Exceptions\HasNoDefaultPriceException; use Blax\Shop\Exceptions\HasNoPriceException; use Blax\Shop\Exceptions\InvalidBookingConfigurationException; use Blax\Shop\Exceptions\InvalidPoolConfigurationException; +use Blax\Shop\Services\CartService; use Blax\Shop\Traits\HasCategories; use Blax\Shop\Traits\HasPrices; use Blax\Shop\Traits\HasPricingStrategy; @@ -374,9 +375,22 @@ class Product extends Model implements Purchasable, Cartable { // If this is a pool product, use cart-aware pricing if cart is provided if ($this->isPool()) { - // If no cart provided, try to get the current user's cart - if (!$cart && auth()->check()) { - $cart = auth()->user()->currentCart(); + // If no cart provided, try to get the cart from session first, then user's cart + if (!$cart) { + // Try session first + $cartId = session(CartService::CART_SESSION_KEY); + if ($cartId) { + $cart = \Blax\Shop\Models\Cart::find($cartId); + // Make sure the cart is valid (not expired/converted) + if ($cart && ($cart->isExpired() || $cart->isConverted())) { + $cart = null; + } + } + + // Fall back to authenticated user's cart if no valid session cart + if (!$cart && auth()->check()) { + $cart = auth()->user()->currentCart(); + } } if ($cart) { diff --git a/src/Services/CartService.php b/src/Services/CartService.php index 2955977..bc301d9 100644 --- a/src/Services/CartService.php +++ b/src/Services/CartService.php @@ -14,47 +14,83 @@ use Blax\Shop\Exceptions\NotPurchasable; use Blax\Shop\Models\Product; use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Support\Facades\Auth; class CartService { /** - * Get current authenticated user's cart - * Throws exception if no user is authenticated + * Session key for storing the current cart ID + */ + public const CART_SESSION_KEY = 'blax_shop_cart_id'; + + /** + * Get current cart from session, or fall back to authenticated user's cart + * Throws exception if no cart found in session and no user is authenticated * * @return Cart * @throws \Exception */ public function current(): Cart { - $user = auth()->user(); - - if (!$user) { - throw new \Exception('No authenticated user found. Use guest() for guest carts or provide a cart ID.'); + // Try to get cart from session first + $cartId = session(self::CART_SESSION_KEY); + if ($cartId) { + $cart = Cart::find($cartId); + if ($cart && !$cart->isExpired() && !$cart->isConverted()) { + // If user is authenticated and session cart is a guest cart, clear it + if (Auth::check() && !$cart->customer_id) { + session()->forget(self::CART_SESSION_KEY); + } else { + return $cart; + } + } else { + // Clear expired or converted cart from session + session()->forget(self::CART_SESSION_KEY); + } } - return $user->currentCart(); + // Fall back to authenticated user's cart + $user = Auth::user(); + + if (!$user) { + return self::guest(); + } + + $cart = $user->currentCart(); + + // Store the cart ID in session for future requests + session([self::CART_SESSION_KEY => $cart->id]); + + return $cart; } /** * Get or create a guest cart by session ID * If no session ID provided, uses session()->getId() + * Stores the cart ID in the session for future requests * * @param string|null $sessionId * @return Cart */ public function guest(?string $sessionId = null): Cart { - $sessionId = $sessionId ?? session()->getId(); + $sessionId = $sessionId ?? session(CartService::CART_SESSION_KEY) ?? session()->getId(); - return Cart::firstOrCreate([ + $cart = Cart::firstOrCreate([ 'session_id' => $sessionId, 'customer_id' => null, 'customer_type' => null, ]); + + // Store cart ID in session + Cart::setSession($cart); + + return $cart; } /** * Get cart for specific user + * Stores the cart ID in the session for future requests * * @param Authenticatable $user * @return Cart @@ -65,7 +101,11 @@ class CartService throw new \Exception('User model must have shopping capabilities'); } - return $user->currentCart(); + $cart = $user->currentCart(); + // Store cart ID in session + Cart::setSession($cart); + + return $cart; } /** @@ -80,8 +120,8 @@ class CartService } /** - * Add item to current user's cart (throws exception if no user) - * For guests, use guest() first: Cart::guest()->add($product) + * Add item to current cart (from session or authenticated user) + * For guests without session, use guest() first: Cart::guest()->add($product) * * @param Model&Cartable $product * @param int $quantity @@ -92,23 +132,19 @@ class CartService */ public function add(Model $product, int $quantity = 1, array $parameters = []): CartItem { - $user = auth()->user(); - - if (!$user) { - throw new \Exception('No authenticated user found. Use guest() for guest carts.'); - } + $cart = $this->current(); // Validate pricing before adding to cart if ($product instanceof Product) { $product->validatePricing(throwExceptions: true); } - return $user->addToCart($product, $quantity, $parameters); + return $cart->addToCart($product, $quantity, $parameters); } /** - * Remove item from current user's cart (throws exception if no user) - * For guests, use guest() first: Cart::guest()->remove($product) + * Remove item from current cart (from session or authenticated user) + * For guests without session, use guest() first: Cart::guest()->remove($product) * * @param Model&Cartable $product * @param int $quantity @@ -117,13 +153,8 @@ class CartService */ public function remove(Model $product, int $quantity = 1, array $parameters = []) { - $user = auth()->user(); - - if (!$user) { - throw new \Exception('No authenticated user found. Use guest() for guest carts.'); - } - - return $user->currentCart()->removeFromCart($product, $quantity, $parameters); + $cart = $this->current(); + return $cart->removeFromCart($product, $quantity, $parameters); } /** @@ -397,6 +428,17 @@ class CartService return empty($this->validateBookings($cart)); } + /** + * Clear the cart from session (unlink cart from session) + * This does not delete the cart, just removes it from the session + * + * @return void + */ + public function clearSession(): void + { + session()->forget(self::CART_SESSION_KEY); + } + /** * Add a booking product to cart with timespan * diff --git a/tests/Feature/CartFacadeTest.php b/tests/Feature/CartFacadeTest.php index df3b2c2..23e415f 100644 --- a/tests/Feature/CartFacadeTest.php +++ b/tests/Feature/CartFacadeTest.php @@ -34,7 +34,7 @@ class CartFacadeTest extends TestCase auth()->logout(); $this->expectException(\Exception::class); - $this->expectExceptionMessage('No authenticated user found'); + $this->expectExceptionMessage('No cart in session and no authenticated user found'); Cart::current(); }