clearCartConditions(); } /** * Store a new item to the cart. * * @param int $productId * @param $variantId * @param int $qty * @param array $options * * @return void * @throws InvalidItemException */ public function store($productId, $variantId, $qty, $options = []): void { $options = array_filter($options); $variations = []; $product = Product::with('files', 'categories', 'taxClass')->findOrFail($productId); $variant = ProductVariant::find($variantId); $item = $variant ?? $product; if ($variant) { $uids = collect(explode('.', $variant->uids)); $rawVariations = $uids->map(function ($uid) { return VariationValue::where('uid', $uid)->get()->pluck('id', 'variation.id'); }); foreach ($rawVariations as $variation) { foreach ($variation as $variationId => $variationValueId) { $variations[$variationId] = $variationValueId; } } } $chosenVariations = new ChosenProductVariations($product, $variations); $chosenOptions = new ChosenProductOptions($product, $options); $this->add([ 'id' => md5("product_id.{$product->id}:options." . serialize($options) . "product_id.{$product->id}:variations." . serialize($variations)), 'name' => $product->name, 'price' => $item->selling_price->amount(), 'quantity' => $qty, 'attributes' => [ 'product' => $product, 'variant' => $variant, 'item' => $item, 'variations' => $chosenVariations->getEntities(), 'options' => $chosenOptions->getEntities(), 'created_at' => time(), ], ]); } public function updateQuantity($id, $qty) { $this->update($id, [ 'quantity' => [ 'relative' => false, 'value' => $qty, ], ]); } /** * Total quantity of the given cartItem * in the Cart. * * @param CartItem $cartItem * * @return int */ public function addedQty(CartItem $cartItem): int { $items = $this->items()->filter(function ($cartItemAlias) use ($cartItem) { if ($cartItem->variant && $cartItemAlias->variant) { return $cartItemAlias->variant->id === $cartItem->variant->id; } return $cartItemAlias->product->id === $cartItem->product->id; }); return $items->sum('qty'); } public function items() { return $this->getContent() ->sortBy('attributes.created_at') ->map(function ($item) { return new CartItem($item); }); } public function crossSellProducts() { return $this->getAllProducts() ->load([ 'crossSellProducts' => function ($query) { $query->forCard(); }, ]) ->pluck('crossSellProducts') ->flatten(); } public function getAllProducts(): EloquentCollection { return $this->items()->map(function ($cartItem) { return $cartItem->product; })->flatten()->pipe(function ($products) { return new EloquentCollection($products); }); } public function reduceStock() { $this->manageStock(function ($cartItem) { $cartItem->item->decrement('qty', $cartItem->qty); }); } public function restoreStock() { $this->manageStock(function ($cartItem) { $cartItem->product->increment('qty', $cartItem->qty); }); } /** * @throws InvalidConditionException */ public function addShippingMethod($shippingMethod) { $this->removeShippingMethod(); $this->condition( new CartCondition([ 'name' => $shippingMethod->label, 'type' => 'shipping_method', 'target' => 'total', 'value' => $this->coupon()?->free_shipping ? 0 : $shippingMethod->cost->amount(), 'order' => 1, 'attributes' => [ 'shipping_method' => $shippingMethod, ], ]), ); $this->refreshFreeShippingCoupon(); return $this->shippingMethod(); } public function removeShippingMethod() { $this->removeConditionsByType('shipping_method'); } public function coupon() { if (!$this->hasCoupon()) { return new NullCartCoupon(); } $couponCondition = $this->getConditionsByType('coupon')->first(); $coupon = Coupon::with('products', 'categories')->find($couponCondition->getAttribute('coupon_id')); return new CartCoupon($this, $coupon, $couponCondition); } public function hasCoupon() { if ($this->getConditionsByType('coupon')->isEmpty()) { return false; } $couponId = $this->getConditionsByType('coupon') ->first() ->getAttribute('coupon_id'); return Coupon::where('id', $couponId)->exists(); } public function applyCoupon(Coupon $coupon) { $this->removeCoupon(); try { $this->condition( new CartCondition([ 'name' => $coupon->code, 'type' => 'coupon', 'target' => 'total', 'value' => $this->getCouponValue($coupon), 'order' => 2, 'attributes' => [ 'coupon_id' => $coupon->id, ], ]), ); } catch (InvalidConditionException $e) { if (app()->hasDebugModeEnabled()) { $message = $e->getMessage(); } else { $message = trans('core::something_went_wrong'); } if (request()->ajax()) { return response()->json( [ 'message' => $message, ], 400 ); } } } public function removeCoupon() { $this->removeConditionsByType('coupon'); } public function shippingMethod() { if (!$this->hasShippingMethod()) { return new NullCartShippingMethod(); } return new CartShippingMethod($this, $this->getConditionsByType('shipping_method')->first()); } public function hasShippingMethod() { return $this->getConditionsByType('shipping_method')->isNotEmpty(); } public function couponAlreadyApplied(Coupon $coupon) { return $this->coupon()->code() === $coupon->code; } public function discount() { return $this->coupon()->value(); } public function addTaxes($request) { $this->removeTaxes(); $this->findTaxes($request)->each(/** * @throws InvalidConditionException */ function ($taxRate) { $this->condition( new CartCondition([ 'name' => $taxRate->id, 'type' => 'tax', 'target' => 'total', 'value' => "+{$taxRate->rate}%", 'order' => 3, 'attributes' => [ 'tax_rate_id' => $taxRate->id, ], ]), ); }); } public function removeTaxes() { $this->removeConditionsByType('tax'); } public function __toString() { return json_encode($this->jsonSerialize()); } public function jsonSerialize(): array { return $this->toArray(); } public function toArray(): array { return [ 'items' => $this->items(), 'quantity' => $this->getTotalQuantity(), 'availableShippingMethods' => $this->availableShippingMethods(), 'subTotal' => $this->subTotal(), 'shippingMethodName' => $this->shippingMethod()->name(), 'shippingCost' => $this->shippingCost(), 'coupon' => $this->coupon(), 'taxes' => $this->taxes(), 'total' => $this->total(), ]; } public function availableShippingMethods(): Collection { if ($this->allItemsAreVirtual()) { return collect(); } return ShippingMethod::available(); } public function allItemsAreVirtual() { return $this->items()->every(function (CartItem $cartItem) { return $cartItem->product->is_virtual; }); } public function subTotal() { return Money::inDefaultCurrency($this->getSubTotal())->add($this->optionsPrice()); } public function shippingCost() { return $this->shippingMethod()->cost(); } public function taxes() { if (!$this->hasTax()) { return new Collection(); } $taxConditions = $this->getConditionsByType('tax'); $taxRates = TaxRate::whereIn('id', $this->getTaxRateIds($taxConditions))->get(); return $taxConditions->map(function ($taxCondition) use ($taxRates) { $taxRate = $taxRates->where('id', $taxCondition->getAttribute('tax_rate_id'))->first(); return new CartTax($this, $taxRate, $taxCondition); }); } public function hasTax() { return $this->getConditionsByType('tax')->isNotEmpty(); } public function total() { return $this->subTotal() ->add($this->shippingMethod()->cost()) ->subtract($this->coupon()->value()) ->add($this->tax()); } public function tax() { return Money::inDefaultCurrency($this->calculateTax()); } private function manageStock($callback) { $this->items() ->filter(function ($cartItem) { return $cartItem->item->manage_stock; }) ->each($callback); } private function refreshFreeShippingCoupon() { if ($this->coupon()->isFreeShipping()) { $this->applyCoupon($this->coupon()->entity()); } } private function getCouponValue($coupon) { if ($coupon->is_percent) { return "-{$coupon->value}%"; } return "-{$coupon->value->amount()}"; } private function findTaxes($request) { return $this->items() ->groupBy('tax_class_id') ->flatten() ->map(function (CartItem $cartItem) use ($request) { return $cartItem->findTax($request->only(['billing', 'shipping'])); }) ->filter(); } private function optionsPrice() { return Money::inDefaultCurrency($this->calculateOptionsPrice()); } private function calculateOptionsPrice() { return $this->items()->sum(function ($cartItem) { return $cartItem ->optionsPrice() ->multiply($cartItem->qty) ->amount(); }); } private function getTaxRateIds($taxConditions) { return $taxConditions->map(function ($taxCondition) { return $taxCondition->getAttribute('tax_rate_id'); }); } private function calculateTax() { return $this->taxes()->sum(function ($cartTax) { return $cartTax->amount()->amount(); }); } }