I cart facade
This commit is contained in:
parent
0e776b6d84
commit
7478f69bcf
|
|
@ -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|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 \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 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 \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 float total(\Blax\Shop\Models\Cart|null $cart = null)
|
||||||
* @method static int itemCount(\Blax\Shop\Models\Cart|null $cart = null)
|
* @method static int itemCount(\Blax\Shop\Models\Cart|null $cart = null)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace Blax\Shop\Models;
|
||||||
use Blax\Shop\Contracts\Cartable;
|
use Blax\Shop\Contracts\Cartable;
|
||||||
use Blax\Shop\Enums\CartStatus;
|
use Blax\Shop\Enums\CartStatus;
|
||||||
use Blax\Shop\Enums\ProductType;
|
use Blax\Shop\Enums\ProductType;
|
||||||
|
use Blax\Shop\Services\CartService;
|
||||||
use Blax\Workkit\Traits\HasExpiration;
|
use Blax\Workkit\Traits\HasExpiration;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
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.
|
* 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
|
// Add items one at a time for progressive pricing
|
||||||
$lastCartItem = null;
|
$lastCartItem = null;
|
||||||
for ($i = 0; $i < $quantity; $i++) {
|
for ($i = 0; $i < $quantity; $i++) {
|
||||||
|
|
@ -357,7 +369,7 @@ class Cart extends Model
|
||||||
// Get price for the next available item
|
// Get price for the next available item
|
||||||
$pricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, null, $from, $until);
|
$pricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, null, $from, $until);
|
||||||
$regularPricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, false, $from, $until) ?? $pricePerDay;
|
$regularPricePerDay = $cartable->getNextAvailablePoolPrice($currentQuantityInCart, false, $from, $until) ?? $pricePerDay;
|
||||||
|
|
||||||
// If no price found from pool items, try the pool's direct price as fallback
|
// If no price found from pool items, try the pool's direct price as fallback
|
||||||
if ($pricePerDay === null && $cartable->hasPrice()) {
|
if ($pricePerDay === null && $cartable->hasPrice()) {
|
||||||
$pricePerDay = $cartable->defaultPrice()->first()?->getCurrentPrice($cartable->isOnSale());
|
$pricePerDay = $cartable->defaultPrice()->first()?->getCurrentPrice($cartable->isOnSale());
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use Blax\Shop\Exceptions\HasNoDefaultPriceException;
|
||||||
use Blax\Shop\Exceptions\HasNoPriceException;
|
use Blax\Shop\Exceptions\HasNoPriceException;
|
||||||
use Blax\Shop\Exceptions\InvalidBookingConfigurationException;
|
use Blax\Shop\Exceptions\InvalidBookingConfigurationException;
|
||||||
use Blax\Shop\Exceptions\InvalidPoolConfigurationException;
|
use Blax\Shop\Exceptions\InvalidPoolConfigurationException;
|
||||||
|
use Blax\Shop\Services\CartService;
|
||||||
use Blax\Shop\Traits\HasCategories;
|
use Blax\Shop\Traits\HasCategories;
|
||||||
use Blax\Shop\Traits\HasPrices;
|
use Blax\Shop\Traits\HasPrices;
|
||||||
use Blax\Shop\Traits\HasPricingStrategy;
|
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 is a pool product, use cart-aware pricing if cart is provided
|
||||||
if ($this->isPool()) {
|
if ($this->isPool()) {
|
||||||
// If no cart provided, try to get the current user's cart
|
// If no cart provided, try to get the cart from session first, then user's cart
|
||||||
if (!$cart && auth()->check()) {
|
if (!$cart) {
|
||||||
$cart = auth()->user()->currentCart();
|
// 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) {
|
if ($cart) {
|
||||||
|
|
|
||||||
|
|
@ -14,47 +14,83 @@ use Blax\Shop\Exceptions\NotPurchasable;
|
||||||
use Blax\Shop\Models\Product;
|
use Blax\Shop\Models\Product;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class CartService
|
class CartService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get current authenticated user's cart
|
* Session key for storing the current cart ID
|
||||||
* Throws exception if no user is authenticated
|
*/
|
||||||
|
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
|
* @return Cart
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function current(): Cart
|
public function current(): Cart
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
// Try to get cart from session first
|
||||||
|
$cartId = session(self::CART_SESSION_KEY);
|
||||||
if (!$user) {
|
if ($cartId) {
|
||||||
throw new \Exception('No authenticated user found. Use guest() for guest carts or provide a cart ID.');
|
$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
|
* Get or create a guest cart by session ID
|
||||||
* If no session ID provided, uses session()->getId()
|
* If no session ID provided, uses session()->getId()
|
||||||
|
* Stores the cart ID in the session for future requests
|
||||||
*
|
*
|
||||||
* @param string|null $sessionId
|
* @param string|null $sessionId
|
||||||
* @return Cart
|
* @return Cart
|
||||||
*/
|
*/
|
||||||
public function guest(?string $sessionId = null): 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,
|
'session_id' => $sessionId,
|
||||||
'customer_id' => null,
|
'customer_id' => null,
|
||||||
'customer_type' => null,
|
'customer_type' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Store cart ID in session
|
||||||
|
Cart::setSession($cart);
|
||||||
|
|
||||||
|
return $cart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cart for specific user
|
* Get cart for specific user
|
||||||
|
* Stores the cart ID in the session for future requests
|
||||||
*
|
*
|
||||||
* @param Authenticatable $user
|
* @param Authenticatable $user
|
||||||
* @return Cart
|
* @return Cart
|
||||||
|
|
@ -65,7 +101,11 @@ class CartService
|
||||||
throw new \Exception('User model must have shopping capabilities');
|
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)
|
* Add item to current cart (from session or authenticated user)
|
||||||
* For guests, use guest() first: Cart::guest()->add($product)
|
* For guests without session, use guest() first: Cart::guest()->add($product)
|
||||||
*
|
*
|
||||||
* @param Model&Cartable $product
|
* @param Model&Cartable $product
|
||||||
* @param int $quantity
|
* @param int $quantity
|
||||||
|
|
@ -92,23 +132,19 @@ class CartService
|
||||||
*/
|
*/
|
||||||
public function add(Model $product, int $quantity = 1, array $parameters = []): CartItem
|
public function add(Model $product, int $quantity = 1, array $parameters = []): CartItem
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$cart = $this->current();
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
throw new \Exception('No authenticated user found. Use guest() for guest carts.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate pricing before adding to cart
|
// Validate pricing before adding to cart
|
||||||
if ($product instanceof Product) {
|
if ($product instanceof Product) {
|
||||||
$product->validatePricing(throwExceptions: true);
|
$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)
|
* Remove item from current cart (from session or authenticated user)
|
||||||
* For guests, use guest() first: Cart::guest()->remove($product)
|
* For guests without session, use guest() first: Cart::guest()->remove($product)
|
||||||
*
|
*
|
||||||
* @param Model&Cartable $product
|
* @param Model&Cartable $product
|
||||||
* @param int $quantity
|
* @param int $quantity
|
||||||
|
|
@ -117,13 +153,8 @@ class CartService
|
||||||
*/
|
*/
|
||||||
public function remove(Model $product, int $quantity = 1, array $parameters = [])
|
public function remove(Model $product, int $quantity = 1, array $parameters = [])
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$cart = $this->current();
|
||||||
|
return $cart->removeFromCart($product, $quantity, $parameters);
|
||||||
if (!$user) {
|
|
||||||
throw new \Exception('No authenticated user found. Use guest() for guest carts.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user->currentCart()->removeFromCart($product, $quantity, $parameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -397,6 +428,17 @@ class CartService
|
||||||
return empty($this->validateBookings($cart));
|
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
|
* Add a booking product to cart with timespan
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class CartFacadeTest extends TestCase
|
||||||
auth()->logout();
|
auth()->logout();
|
||||||
|
|
||||||
$this->expectException(\Exception::class);
|
$this->expectException(\Exception::class);
|
||||||
$this->expectExceptionMessage('No authenticated user found');
|
$this->expectExceptionMessage('No cart in session and no authenticated user found');
|
||||||
|
|
||||||
Cart::current();
|
Cart::current();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue