initCart(); } /** * Initialize cart */ public function initCart(?CustomerContract $customer = null): void { if (! $customer) { $customer = auth()->guard()->user(); } if ($customer) { $this->cart = $this->cartRepository->findOneWhere([ 'customer_id' => $customer->id, 'is_active' => 1, ]); } elseif (session()->has('cart')) { $this->cart = $this->cartRepository->find(session()->get('cart')->id); } } /** * Returns cart */ public function refreshCart(): void { if (! $this->cart) { return; } $this->cart = $this->cartRepository->find($this->cart->id); } /** * Set cart */ public function setCart(Contracts\Cart $cart): void { $this->cart = $cart; if ($this->cart->customer) { return; } $cartTemp = new \stdClass; $cartTemp->id = $this->cart->id; session()->put('cart', $cartTemp); } /** * Returns cart. */ public function getCart(): ?Contracts\Cart { return $this->cart; } /** * Create new cart instance. */ public function createCart(array $data): ?Contracts\Cart { $data = array_merge([ 'is_guest' => 1, 'channel_id' => core()->getCurrentChannel()->id, 'global_currency_code' => $baseCurrencyCode = core()->getBaseCurrencyCode(), 'base_currency_code' => $baseCurrencyCode, 'channel_currency_code' => core()->getChannelBaseCurrencyCode(), 'cart_currency_code' => core()->getCurrentCurrencyCode(), ], $data); $customer = $data['customer'] ?? auth()->guard()->user(); if ($customer) { $data = array_merge($data, [ 'is_guest' => 0, 'customer_id' => $customer->id, 'customer_first_name' => $customer->first_name, 'customer_last_name' => $customer->last_name, 'customer_email' => $customer->email, ]); } $cart = $this->cartRepository->create($data); $this->setCart($cart); return $cart; } /** * Remove cart and destroy the session */ public function removeCart(Contracts\Cart $cart): void { $this->cartRepository->delete($cart->id); if (session()->has('cart')) { session()->forget('cart'); } $this->resetCart(); } /** * Reset cart */ public function resetCart(): void { $this->cart = null; } /** * Activate the cart by id. */ public function activateCart(int $cartId): void { $cart = $this->cartRepository->update([ 'is_active' => true, ], $cartId); $this->setCart($cart); } /** * Deactivates current cart. */ public function deActivateCart(): void { if (! $this->cart) { return; } $this->cartRepository->update(['is_active' => false], $this->cart->id); $this->resetCart(); if (session()->has('cart')) { session()->forget('cart'); } } /** * This method handles when guest has some of cart products and then logs in. */ public function mergeCart(CustomerContract $customer): void { if (! session()->has('cart')) { return; } $cart = $this->cartRepository->findOneWhere([ 'customer_id' => $customer->id, 'is_active' => 1, ]); $guestCart = $this->cartRepository->find(session()->get('cart')->id); /** * When the logged in customer is not having any of the cart instance previously and are active. */ if (! $cart) { $this->cartRepository->update([ 'customer_id' => $customer->id, 'is_guest' => 0, 'customer_first_name' => $customer->first_name, 'customer_last_name' => $customer->last_name, 'customer_email' => $customer->email, ], $guestCart->id); session()->forget('cart'); return; } $this->setCart($cart); foreach ($guestCart->items as $guestCartItem) { try { $this->addProduct($guestCartItem->product, $guestCartItem->additional); } catch (\Exception $e) { // Ignore exception } } $this->collectTotals(); $this->removeCart($guestCart); } /** * Add items in a cart with some cart and item details. */ public function addProduct(ProductContract $product, array $data): Contracts\Cart|\Exception { Event::dispatch('checkout.cart.add.before', $product->id); if (! $this->cart) { $this->createCart([]); } $cartProducts = $product->getTypeInstance()->prepareForCart(array_merge([ 'cart_id' => $this->cart->id, ], $data)); if (is_string($cartProducts)) { if (! $this->cart->all_items->count()) { $this->removeCart($this->cart); } else { $this->collectTotals(); } throw new \Exception($cartProducts); } else { $parentCartItem = null; foreach ($cartProducts as $cartProduct) { $cartItem = $this->getItemByProduct($cartProduct, $data); if (isset($cartProduct['parent_id'])) { $cartProduct['parent_id'] = $parentCartItem->id; } if (! $cartItem) { $cartItem = $this->cartItemRepository->create(array_merge($cartProduct, ['cart_id' => $this->cart->id])); } else { if ( isset($cartProduct['parent_id']) && $cartItem->parent_id !== $parentCartItem->id ) { $cartItem = $this->cartItemRepository->create(array_merge($cartProduct, [ 'cart_id' => $this->cart->id, ])); } else { $cartItem = $this->cartItemRepository->update($cartProduct, $cartItem->id); } } if (! $parentCartItem) { $parentCartItem = $cartItem; } } } $this->collectTotals(); Event::dispatch('checkout.cart.add.after', $this->cart); return $this->cart; } /** * Remove the item from the cart. */ public function removeItem(int $itemId): bool { if (! $this->cart) { return false; } if (! $this->cart->items->pluck('id')->contains($itemId)) { return false; } Event::dispatch('checkout.cart.delete.before', $itemId); Shipping::removeAllShippingRates(); $result = $this->cartItemRepository->delete($itemId); Event::dispatch('checkout.cart.delete.after', $itemId); return $result; } /** * Update cart items information. */ public function updateItems(array $data): bool|\Exception { foreach ($data['qty'] as $itemId => $quantity) { $item = $this->cartItemRepository->find($itemId); if (! $item) { continue; } if ($item->cart_id !== $this->cart->id) { continue; } if (! $item->product->status) { throw new \Exception(__('shop::app.checkout.cart.inactive')); } if ($quantity <= 0) { $this->removeItem($itemId); throw new \Exception(__('shop::app.checkout.cart.illegal')); } $item->quantity = $quantity; if (! $this->isItemHaveQuantity($item)) { throw new \Exception(__('shop::app.checkout.cart.inventory-warning')); } Event::dispatch('checkout.cart.update.before', $item); $this->cartItemRepository->update([ 'quantity' => $quantity, 'total' => core()->convertPrice($item->base_price * $quantity), 'total_incl_tax' => core()->convertPrice($item->base_price_incl_tax * $quantity), 'base_total' => $item->base_price * $quantity, 'base_total_incl_tax' => $item->base_price_incl_tax * $quantity, 'total_weight' => $item->weight * $quantity, 'base_total_weight' => $item->weight * $quantity, 'additional' => [ ...$item->additional, 'quantity' => $quantity, ], ], $itemId); Event::dispatch('checkout.cart.update.after', $item); } $this->collectTotals(); return true; } /** * Get cart item by product. */ public function getItemByProduct(array $data, ?array $parentData = null): ?Contracts\CartItem { $items = $this->cart->all_items; foreach ($items as $item) { if ($item->getTypeInstance()->compareOptions($item->additional, $data['additional'])) { if ( ! isset($data['additional']['parent_id']) && ! $item->parent_id ) { return $item; } if ($item->parent?->getTypeInstance()->compareOptions($item->parent->additional, $parentData ?: request()->all())) { return $item; } } } return null; } /** * Update or create billing address. */ public function saveAddresses(array $params): void { $this->updateOrCreateBillingAddress($params['billing']); $this->updateOrCreateShippingAddress($params['shipping'] ?? []); $this->setCustomerPersonnelDetails(); $this->resetShippingMethod(); } /** * Update or create billing address. */ public function updateOrCreateBillingAddress(array $params): CartAddressContract { $params = collect($params) ->only([ 'use_for_shipping', 'default_address', 'company_name', 'first_name', 'last_name', 'vat_id', 'email', 'address', 'country', 'state', 'city', 'postcode', 'phone', ]) ->merge([ 'address_type' => CartAddress::ADDRESS_TYPE_BILLING, 'parent_address_id' => ($params['address_type'] ?? '') == 'customer' ? $params['id'] : null, 'cart_id' => $this->cart->id, 'customer_id' => $this->cart->customer_id, 'address' => implode(PHP_EOL, $params['address']), 'use_for_shipping' => (bool) ($params['use_for_shipping'] ?? false), ]) ->toArray(); if ($this->cart->billing_address) { $address = $this->cartAddressRepository->update($params, $this->cart->billing_address->id); } else { $address = $this->cartAddressRepository->create($params); } $this->cart->setRelation('billing_address', $address); return $address; } /** * Update or create shipping address. */ public function updateOrCreateShippingAddress(array $params): ?CartAddressContract { /** * If cart is not having any stockable items then no need to save shipping address. */ if (! $this->cart->haveStockableItems()) { return null; } if (! $this->cart->billing_address) { throw new BillingAddressNotFoundException; } $fillableFields = [ 'default_address', 'company_name', 'first_name', 'last_name', 'email', 'address', 'country', 'state', 'city', 'postcode', 'phone', ]; if ($this->cart->billing_address->use_for_shipping) { $params = $this->cart->billing_address->only($fillableFields); $params = array_merge($params, [ 'address_type' => CartAddress::ADDRESS_TYPE_SHIPPING, 'parent_address_id' => $this->cart->billing_address->parent_address_id, 'cart_id' => $this->cart->id, 'customer_id' => $this->cart->customer_id, ]); } else { if (empty($params)) { return null; } $params = collect($params) ->only($fillableFields) ->merge([ 'address_type' => CartAddress::ADDRESS_TYPE_SHIPPING, 'parent_address_id' => ($params['address_type'] ?? '') == 'customer' ? $params['id'] : null, 'cart_id' => $this->cart->id, 'customer_id' => $this->cart->customer_id, 'address' => implode(PHP_EOL, $params['address']), ]) ->toArray(); } if ($this->cart->shipping_address) { $address = $this->cartAddressRepository->update($params, $this->cart->shipping_address->id); } else { $params['default_address'] = 0; $address = $this->cartAddressRepository->create($params); } $this->cart->setRelation('shipping_address', $address); return $address; } /** * Save customer details. */ public function setCustomerPersonnelDetails(): void { $this->cart->customer_email = $this->cart->customer?->email ?? $this->cart->billing_address->email; $this->cart->customer_first_name = $this->cart->customer?->first_name ?? $this->cart->billing_address->first_name; $this->cart->customer_last_name = $this->cart->customer?->last_name ?? $this->cart->billing_address->last_name; $this->cart->save(); } /** * Save shipping method for cart. */ public function saveShippingMethod(string $shippingMethodCode): bool { if (! $this->cart) { return false; } if (! Shipping::isMethodCodeExists($shippingMethodCode)) { return false; } $this->cart->shipping_method = $shippingMethodCode; $this->cart->save(); return true; } /** * Save shipping method for cart. */ public function resetShippingMethod(): bool { if (! $this->cart) { return false; } Shipping::removeAllShippingRates(); $this->cart->shipping_method = null; $this->cart->save(); return true; } /** * Save payment method for cart. */ public function savePaymentMethod(array $params): bool|Contracts\CartPayment { if (! $this->cart) { return false; } if ($cartPayment = $this->cart->payment) { $cartPayment->delete(); } $cartPayment = new CartPayment; $cartPayment->method = $params['method']; $cartPayment->method_title = core()->getConfigData('sales.payment_methods.'.$params['method'].'.title'); $cartPayment->cart_id = $this->cart->id; $cartPayment->save(); return $cartPayment; } /** * Set coupon code to the cart. */ public function setCouponCode(?string $code): self { $this->cart->coupon_code = $code; $this->cart->save(); return $this; } /** * Set coupon code to the cart. */ public function removeCouponCode(): self { return $this->setCouponCode(null); } /** * Move a wishlist item to cart. */ public function moveToCart(WishlistContract $wishlistItem, ?int $quantity = 1): bool { if (! $wishlistItem->product->getTypeInstance()->canBeMovedFromWishlistToCart($wishlistItem)) { return false; } if (! $wishlistItem->additional) { $wishlistItem->additional = ['product_id' => $wishlistItem->product_id]; } $additional = [ ...$wishlistItem->additional, 'quantity' => $quantity, ]; $result = $this->addProduct($wishlistItem->product, $additional); if ($result) { Event::dispatch('customer.wishlist.delete.before', $wishlistItem->id); $this->wishlistRepository->delete($wishlistItem->id); Event::dispatch('customer.wishlist.delete.after', $wishlistItem->id); return true; } return false; } /** * Move to wishlist items. */ public function moveToWishlist(int $itemId, int $quantity = 1): bool { $cartItem = $this->cart->items()->find($itemId); if (! $cartItem) { return false; } $wishlistItems = $this->wishlistRepository->findWhere([ 'customer_id' => $this->cart->customer_id, 'product_id' => $cartItem->product_id, ]); $found = false; foreach ($wishlistItems as $wishlistItem) { $options = $wishlistItem->item_options; if (! $options) { $options = ['product_id' => $wishlistItem->product_id]; } if ($cartItem->getTypeInstance()->compareOptions($cartItem->additional, $options)) { $found = true; } } if (! $found) { Event::dispatch('customer.wishlist.create.before', $cartItem->product_id); $wishlist = $this->wishlistRepository->create([ 'channel_id' => $this->cart->channel_id, 'customer_id' => $this->cart->customer_id, 'product_id' => $cartItem->product_id, 'additional' => [ ...$cartItem->additional, 'quantity' => $quantity, ], ]); Event::dispatch('customer.wishlist.create.after', $wishlist); } if (! $this->cart->items->count()) { $this->cartRepository->delete($this->cart->id); $this->refreshCart(); } else { $this->cartItemRepository->delete($itemId); $this->refreshCart(); $this->collectTotals(); } return true; } /** * Checks if cart has any error. */ public function hasError(): bool { return ! empty($this->getErrors()); } /** * Get Cart Errors. */ public function getErrors() { if (! $this->cart) { return [ 'error_code' => 'CART_NOT_FOUND', 'message' => trans('shop::app.checkout.cart.index.empty-product'), ]; } if (! $this->isItemsHaveSufficientQuantity()) { return [ 'error_code' => 'INSUFFICIENT_QUANTITY', 'message' => trans('shop::app.checkout.cart.inventory-warning'), ]; } if (! $this->haveMinimumOrderAmount()) { $minimumOrderDescription = core()->getConfigData('sales.order_settings.minimum_order.description'); return [ 'error_code' => 'MINIMUM_ORDER_AMOUNT', 'message' => $minimumOrderDescription ?: trans('shop::app.checkout.cart.minimum-order-message'), 'amount' => core()->formatPrice((int) core()->getConfigData('sales.order_settings.minimum_order.minimum_order_amount') ?: $this->getOrderAmount()), ]; } return []; } /** * Check minimum Order Amount of cart. */ public function getOrderAmount(): int { $minimumOrderAmount = $this->cart->sub_total; if (core()->getConfigData('sales.order_settings.minimum_order.include_tax_to_amount')) { $minimumOrderAmount += $this->cart->tax_total; } if (core()->getConfigData('sales.order_settings.minimum_order.include_discount_amount')) { $minimumOrderAmount -= $this->cart->tax_total; if ($this->cart->discount_amount) { $minimumOrderAmount -= $this->cart->discount_amount; } } return $minimumOrderAmount; } /** * Check minimum order. */ public function haveMinimumOrderAmount(): bool { if (! core()->getConfigData('sales.order_settings.minimum_order.enable')) { return true; } return $this->getOrderAmount() >= ((int) core()->getConfigData('sales.order_settings.minimum_order.minimum_order_amount') ?: 0); } /** * Checks if all cart items have sufficient quantity. */ public function isItemsHaveSufficientQuantity(): bool { if (! $this->cart) { return false; } foreach ($this->cart->items as $item) { if (! $this->isItemHaveQuantity($item)) { return false; } } return true; } /** * Checks if all cart items have sufficient quantity. */ public function isItemHaveQuantity(Contracts\CartItem $item): bool { return $item->getTypeInstance()->isItemHaveQuantity($item); } /** * Updates cart totals. */ public function collectTotals(): self { if (! $this->validateItems()) { /** * Reset the cart so that fresh copy of cart can be created. */ $this->refreshCart(); } if (! $this->cart) { return $this; } Event::dispatch('checkout.cart.collect.totals.before', $this->cart); $this->calculateItemsTax(); $this->calculateShippingTax(); $this->refreshCart(); $this->cart->sub_total = $this->cart->base_sub_total = 0; $this->cart->sub_total_incl_tax = $this->cart->base_sub_total_incl_tax = 0; $this->cart->grand_total = $this->cart->base_grand_total = 0; $this->cart->tax_total = $this->cart->base_tax_total = 0; $this->cart->discount_amount = $this->cart->base_discount_amount = 0; $this->cart->shipping_amount = $this->cart->base_shipping_amount = 0; $this->cart->shipping_amount_incl_tax = $this->cart->base_shipping_amount_incl_tax = 0; $quantities = 0; foreach ($this->cart->items as $item) { $this->cart->discount_amount += $item->discount_amount; $this->cart->base_discount_amount += $item->base_discount_amount; $this->cart->tax_total += $item->tax_amount; $this->cart->base_tax_total += $item->base_tax_amount; $this->cart->sub_total = (float) $this->cart->sub_total + $item->total; $this->cart->base_sub_total = (float) $this->cart->base_sub_total + $item->base_total; $this->cart->sub_total_incl_tax = (float) $this->cart->sub_total_incl_tax + $item->total_incl_tax; $this->cart->base_sub_total_incl_tax = (float) $this->cart->base_sub_total_incl_tax + $item->base_total_incl_tax; $quantities += $item->quantity; } $this->cart->items_qty = $quantities; $this->cart->items_count = $this->cart->items->count(); $this->cart->grand_total = $this->cart->sub_total + $this->cart->tax_total - $this->cart->discount_amount; $this->cart->base_grand_total = $this->cart->base_sub_total + $this->cart->base_tax_total - $this->cart->base_discount_amount; if ($shipping = $this->cart->selected_shipping_rate) { $this->cart->tax_total += $shipping->tax_amount; $this->cart->base_tax_total += $shipping->base_tax_amount; $this->cart->shipping_amount = $shipping->price; $this->cart->base_shipping_amount = $shipping->base_price; $this->cart->shipping_amount_incl_tax = $shipping->price_incl_tax; $this->cart->base_shipping_amount_incl_tax = $shipping->base_price_incl_tax; $this->cart->grand_total = (float) $this->cart->grand_total + $shipping->tax_amount + $shipping->price - $shipping->discount_amount; $this->cart->base_grand_total = (float) $this->cart->base_grand_total + $shipping->base_tax_amount + $shipping->base_price - $shipping->base_discount_amount; $this->cart->discount_amount += $shipping->discount_amount; $this->cart->base_discount_amount += $shipping->base_discount_amount; } $this->cart->discount_amount = round($this->cart->discount_amount, 2); $this->cart->base_discount_amount = round($this->cart->base_discount_amount, 2); $this->cart->sub_total = round($this->cart->sub_total, 2); $this->cart->base_sub_total = round($this->cart->base_sub_total, 2); $this->cart->sub_total_incl_tax = round($this->cart->sub_total_incl_tax, 2); $this->cart->base_sub_total_incl_tax = round($this->cart->base_sub_total_incl_tax, 2); $this->cart->grand_total = round($this->cart->grand_total, 2); $this->cart->base_grand_total = round($this->cart->base_grand_total, 2); $this->cart->cart_currency_code = core()->getCurrentCurrencyCode(); $this->cart->save(); Event::dispatch('checkout.cart.collect.totals.after', $this->cart); return $this; } /** * To validate if the product information is changed by admin and the items have been added to the cart before it. */ public function validateItems(): bool { if (! $this->cart) { return false; } if (! $this->cart->items->count()) { $this->removeCart($this->cart); return false; } $isInvalid = false; foreach ($this->cart->items as $key => $item) { $validationResult = $item->getTypeInstance()->validateCartItem($item); if ($validationResult->isItemInactive()) { $this->removeItem($item->id); $isInvalid = true; session()->flash('info', __('shop::app.checkout.cart.inactive')); } else { if (Tax::isInclusiveTaxProductPrices()) { $itemBasePrice = $item->base_price_incl_tax; } else { $itemBasePrice = $item->base_price; } $basePrice = ! is_null($item->custom_price) ? $item->custom_price : $itemBasePrice; $price = core()->convertPrice($basePrice); /** * Reset the item price every time to initial price if the inclusive price is enabled. * Update the item price if exchange rates changes with exclusive price is enabled. */ if ($price != $item->price) { $item = $this->cartItemRepository->update([ 'price' => $price, 'price_incl_tax' => $price, 'base_price' => $basePrice, 'base_price_incl_tax' => $basePrice, 'total' => $total = core()->convertPrice($basePrice * $item->quantity), 'total_incl_tax' => $total, 'base_total' => ($baseTotal = $basePrice * $item->quantity), 'base_total_incl_tax' => $baseTotal, ], $item->id); $this->cart->items->put($key, $item); } } $isInvalid |= $validationResult->isCartInvalid(); } return ! $isInvalid; } /** * Calculates cart items tax. */ public function calculateItemsTax(): void { if (! $this->cart) { return; } Event::dispatch('checkout.cart.calculate.items.tax.before', $this->cart); $taxCategories = []; foreach ($this->cart->items as $key => $item) { $taxCategoryId = $item->tax_category_id; if (empty($taxCategoryId)) { $taxCategoryId = $item->product->tax_category_id; } if (empty($taxCategoryId)) { $taxCategoryId = core()->getConfigData('sales.taxes.categories.product'); } if (empty($taxCategoryId)) { continue; } if (! isset($taxCategories[$taxCategoryId])) { $taxCategories[$taxCategoryId] = $this->taxCategoryRepository->find($taxCategoryId); } if (! $taxCategories[$taxCategoryId]) { continue; } $calculationBasedOn = core()->getConfigData('sales.taxes.calculation.based_on'); $address = null; if ($calculationBasedOn == self::TAX_CALCULATION_BASED_ON_SHIPPING_ORIGIN) { $address = Tax::getShippingOriginAddress(); } elseif ($calculationBasedOn == self::TAX_CALCULATION_BASED_ON_SHIPPING_ADDRESS) { if ($item->getTypeInstance()->isStockable()) { $address = $this->cart->shipping_address; } else { $address = $this->cart->billing_address; } } elseif ($calculationBasedOn == self::TAX_CALCULATION_BASED_ON_BILLING_ADDRESS) { $address = $this->cart->billing_address; } if ($address === null && $this->cart->customer) { $address = $this->cart->customer->addresses() ->where('default_address', 1)->first(); } if ($address === null) { $address = Tax::getDefaultAddress(); } $item->applied_tax_rate = null; $item->tax_percent = $item->tax_amount = $item->base_tax_amount = 0; Tax::isTaxApplicableInCurrentAddress($taxCategories[$taxCategoryId], $address, function ($rate) use ($item, $taxCategoryId) { $item->applied_tax_rate = $rate->identifier; $item->tax_category_id = $taxCategoryId; $item->tax_percent = $rate->tax_rate; if (Tax::isInclusiveTaxProductPrices()) { $item->tax_amount = round(($item->total_incl_tax * $rate->tax_rate) / (100 + $rate->tax_rate), 4); $item->base_tax_amount = round(($item->base_total_incl_tax * $rate->tax_rate) / (100 + $rate->tax_rate), 4); $item->total = $item->total_incl_tax - $item->tax_amount; $item->base_total = $item->base_total_incl_tax - $item->base_tax_amount; $item->price = $item->total / $item->quantity; $item->base_price = $item->base_total / $item->quantity; } else { $item->tax_amount = round(($item->total * $rate->tax_rate) / 100, 4); $item->base_tax_amount = round(($item->base_total * $rate->tax_rate) / 100, 4); $item->total_incl_tax = $item->total + $item->tax_amount; $item->base_total_incl_tax = $item->base_total + $item->base_tax_amount; $item->price_incl_tax = $item->price + ($item->tax_amount / $item->quantity); $item->base_price_incl_tax = $item->base_price + ($item->base_tax_amount / $item->quantity); } }); if (empty($item->applied_tax_rate)) { $item->price_incl_tax = $item->price; $item->base_price_incl_tax = $item->base_price; $item->total_incl_tax = $item->total; $item->base_total_incl_tax = $item->base_total; } $item->save(); $this->cart->items->put($key, $item); } Event::dispatch('checkout.cart.calculate.items.tax.after', $this->cart); } /** * Calculates cart shipping tax. */ public function calculateShippingTax(): void { if (! $this->cart) { return; } $shippingRate = $this->cart->selected_shipping_rate; if (! $shippingRate) { return; } if (! $taxCategoryId = core()->getConfigData('sales.taxes.categories.shipping')) { return; } $taxCategory = $this->taxCategoryRepository->find($taxCategoryId); $calculationBasedOn = core()->getConfigData('sales.taxes.calculation.based_on'); $address = null; if ($calculationBasedOn == self::TAX_CALCULATION_BASED_ON_SHIPPING_ORIGIN) { $address = Tax::getShippingOriginAddress(); } elseif ( $this->cart->haveStockableItems() && $calculationBasedOn == self::TAX_CALCULATION_BASED_ON_SHIPPING_ADDRESS ) { $address = $this->cart->shipping_address; } elseif ($calculationBasedOn == self::TAX_CALCULATION_BASED_ON_BILLING_ADDRESS) { $address = $this->cart->billing_address; } if ($address === null && $this->cart->customer) { $address = $this->cart->customer->addresses() ->where('default_address', 1)->first(); } if ($address === null) { $address = Tax::getDefaultAddress(); } Event::dispatch('checkout.cart.calculate.shipping.tax.before', $this->cart); Tax::isTaxApplicableInCurrentAddress($taxCategory, $address, function ($rate) use ($shippingRate) { $shippingRate->applied_tax_rate = $rate->identifier; $shippingRate->tax_percent = $rate->tax_rate; if (Tax::isInclusiveTaxShippingPrices()) { $shippingRate->tax_amount = round(($shippingRate->price_incl_tax * $rate->tax_rate) / (100 + $rate->tax_rate), 4); $shippingRate->base_tax_amount = round(($shippingRate->base_price_incl_tax * $rate->tax_rate) / (100 + $rate->tax_rate), 4); $shippingRate->price = $shippingRate->price_incl_tax - $shippingRate->tax_amount; $shippingRate->base_price = $shippingRate->base_price_incl_tax - $shippingRate->base_tax_amount; } else { $shippingRate->tax_amount = round(($shippingRate->price * $rate->tax_rate) / 100, 4); $shippingRate->base_tax_amount = round(($shippingRate->base_price * $rate->tax_rate) / 100, 4); $shippingRate->price_incl_tax = $shippingRate->price + $shippingRate->tax_amount; $shippingRate->base_price_incl_tax = $shippingRate->base_price + $shippingRate->base_tax_amount; } }); if (empty($shippingRate->applied_tax_rate)) { $shippingRate->price_incl_tax = $shippingRate->price; $shippingRate->base_price_incl_tax = $shippingRate->base_price; } $shippingRate->save(); Event::dispatch('checkout.cart.calculate.shipping.tax.after', $this->cart); } }