diff --git a/config/app.php b/config/app.php index 33129e719..3a6ec6903 100755 --- a/config/app.php +++ b/config/app.php @@ -102,6 +102,8 @@ return [ | Ensure it is uppercase and reflects the 'code' column of the | countries table. | + | for example: DE EN FR + | (use capital letters!) */ 'default_country' => null, diff --git a/packages/Webkul/API/Http/Resources/Checkout/Cart.php b/packages/Webkul/API/Http/Resources/Checkout/Cart.php index 2a6401b9e..028d42f43 100644 --- a/packages/Webkul/API/Http/Resources/Checkout/Cart.php +++ b/packages/Webkul/API/Http/Resources/Checkout/Cart.php @@ -16,6 +16,12 @@ class Cart extends JsonResource */ public function toArray($request) { + $taxes = \Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($this, false); + $baseTaxes = \Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($this, true); + + $formatedTaxes = $this->formatTaxAmounts($taxes, false); + $formatedBaseTaxes = $this->formatTaxAmounts($baseTaxes, true); + return [ 'id' => $this->id, 'customer_email' => $this->customer_email, @@ -60,6 +66,31 @@ class Cart extends JsonResource 'shipping_address' => new CartAddress($this->shipping_address), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, + 'taxes' => json_encode($taxes, JSON_FORCE_OBJECT), + 'formated_taxes' => json_encode($formatedTaxes, JSON_FORCE_OBJECT), + 'base_taxes' => json_encode($baseTaxes, JSON_FORCE_OBJECT), + 'formated_base_taxes' => json_encode($formatedBaseTaxes, JSON_FORCE_OBJECT), ]; } + + /** + * @param array $taxes + * @param bool $isBase + * + * @return array + */ + private function formatTaxAmounts(array $taxes, bool $isBase = false): array + { + $result = []; + + foreach ($taxes as $taxRate => $taxAmount) { + if ($isBase === true) { + $result[$taxRate] = core()->formatBasePrice($taxAmount); + } else { + $result[$taxRate] = core()->formatPrice($taxAmount, $this->cart_currency_code); + } + } + + return $result; + } } \ No newline at end of file diff --git a/packages/Webkul/Admin/src/Resources/views/sales/orders/view.blade.php b/packages/Webkul/Admin/src/Resources/views/sales/orders/view.blade.php index a1b28cbd0..dcf5d8872 100755 --- a/packages/Webkul/Admin/src/Resources/views/sales/orders/view.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/sales/orders/view.blade.php @@ -266,7 +266,7 @@ @if (isset($item->additional['attributes']))
- + @foreach ($item->additional['attributes'] as $attribute) {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
@endforeach @@ -344,11 +344,14 @@ @endif - - {{ __('admin::app.sales.orders.tax') }} + @php ($taxRates = Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($order, true)) + @foreach ($taxRates as $taxRate => $baseTaxAmount) + last ? 'class=border' : ''}}> + {{ __('admin::app.sales.orders.tax') }} {{ $taxRate }} % - - {{ core()->formatBasePrice($order->base_tax_amount) }} + {{ core()->formatBasePrice($baseTaxAmount) }} + @endforeach {{ __('admin::app.sales.orders.grand-total') }} diff --git a/packages/Webkul/Checkout/src/Cart.php b/packages/Webkul/Checkout/src/Cart.php index f293aea7f..7b125b01a 100755 --- a/packages/Webkul/Checkout/src/Cart.php +++ b/packages/Webkul/Checkout/src/Cart.php @@ -5,7 +5,9 @@ namespace Webkul\Checkout; use Webkul\Checkout\Repositories\CartRepository; use Webkul\Checkout\Repositories\CartItemRepository; use Webkul\Checkout\Repositories\CartAddressRepository; +use Webkul\Customer\Models\CustomerAddress; use Webkul\Product\Repositories\ProductRepository; +use Webkul\Tax\Helpers\Tax; use Webkul\Tax\Repositories\TaxCategoryRepository; use Webkul\Checkout\Models\CartItem; use Webkul\Checkout\Models\CartPayment; @@ -21,7 +23,8 @@ use Illuminate\Support\Arr; * @author Jitendra Singh * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) */ -class Cart { +class Cart +{ /** * CartRepository instance @@ -75,15 +78,16 @@ class Cart { /** * Create a new controller instance. * - * @param Webkul\Checkout\Repositories\CartRepository $cart - * @param Webkul\Checkout\Repositories\CartItemRepository $cartItem - * @param Webkul\Checkout\Repositories\CartAddressRepository $cartAddress - * @param Webkul\Product\Repositories\ProductRepository $product - * @param Webkul\Product\Repositories\TaxCategoryRepository $taxCategory - * @param Webkul\Product\Repositories\CustomerAddressRepository $customerAddress - * @param Webkul\Product\Repositories\CustomerAddressRepository $customerAddress - * @param Webkul\Discount\Repositories\CartRuleRepository $cartRule - * @param Webkul\Helpers\Discount $discount + * @param Webkul\Checkout\Repositories\CartRepository $cart + * @param Webkul\Checkout\Repositories\CartItemRepository $cartItem + * @param Webkul\Checkout\Repositories\CartAddressRepository $cartAddress + * @param Webkul\Product\Repositories\ProductRepository $product + * @param Webkul\Product\Repositories\TaxCategoryRepository $taxCategory + * @param Webkul\Product\Repositories\CustomerAddressRepository $customerAddress + * @param Webkul\Product\Repositories\CustomerAddressRepository $customerAddress + * @param Webkul\Discount\Repositories\CartRuleRepository $cartRule + * @param Webkul\Helpers\Discount $discount + * * @return void */ public function __construct( @@ -94,8 +98,7 @@ class Cart { TaxCategoryRepository $taxCategoryRepository, WishlistRepository $wishlistRepository, CustomerAddressRepository $customerAddressRepository - ) - { + ) { $this->cartRepository = $cartRepository; $this->cartItemRepository = $cartItemRepository; @@ -128,6 +131,7 @@ class Cart { * * @param integer $productId * @param array $data + * * @return Mixed Cart on success, array with warning otherwise */ public function addProduct($productId, $data) @@ -147,7 +151,7 @@ class Cart { if (is_string($cartProducts)) { $this->collectTotals(); - if (! count($cart->all_items) > 0) { + if (!count($cart->all_items) > 0) { session()->forget('cart'); } @@ -162,11 +166,12 @@ class Cart { $cartProduct['parent_id'] = $parentCartItem->id; } - if (! $cartItem) { + if (!$cartItem) { $cartItem = $this->cartItemRepository->create(array_merge($cartProduct, ['cart_id' => $cart->id])); } else { if (isset($cartProduct['parent_id']) && $cartItem->parent_id != $parentCartItem->id) { - $cartItem = $this->cartItemRepository->create(array_merge($cartProduct, ['cart_id' => $cart->id])); + $cartItem = $this->cartItemRepository->create(array_merge($cartProduct, + ['cart_id' => $cart->id])); } else { if ($cartItem->product->getTypeInstance()->showQuantityBox() === false) { return ['warning' => __('shop::app.checkout.cart.integrity.qty_impossible')]; @@ -176,8 +181,9 @@ class Cart { } } - if (! $parentCartItem) + if (!$parentCartItem) { $parentCartItem = $cartItem; + } } } @@ -192,17 +198,18 @@ class Cart { * Create new cart instance. * * @param array $data + * * @return Cart|null */ public function create($data) { $cartData = [ - 'channel_id' => core()->getCurrentChannel()->id, - 'global_currency_code' => core()->getBaseCurrencyCode(), - 'base_currency_code' => core()->getBaseCurrencyCode(), + 'channel_id' => core()->getCurrentChannel()->id, + 'global_currency_code' => core()->getBaseCurrencyCode(), + 'base_currency_code' => core()->getBaseCurrencyCode(), 'channel_currency_code' => core()->getChannelBaseCurrencyCode(), - 'cart_currency_code' => core()->getCurrentCurrencyCode(), - 'items_count' => 1 + 'cart_currency_code' => core()->getCurrentCurrencyCode(), + 'items_count' => 1, ]; //Authentication details @@ -218,7 +225,7 @@ class Cart { $cart = $this->cartRepository->create($cartData); - if (! $cart) { + if (!$cart) { session()->flash('error', trans('shop::app.checkout.cart.create-error')); return; @@ -241,8 +248,9 @@ class Cart { foreach ($data['qty'] as $itemId => $quantity) { $item = $this->cartItemRepository->findOneByField('id', $itemId); - if (! $item) + if (!$item) { continue; + } if ($quantity <= 0) { $this->removeItem($itemId); @@ -252,18 +260,19 @@ class Cart { $item->quantity = $quantity; - if (! $this->isItemHaveQuantity($item)) + if (!$this->isItemHaveQuantity($item)) { throw new \Exception(trans('shop::app.checkout.cart.quantity.inventory_warning')); + } Event::dispatch('checkout.cart.update.before', $item); $this->cartItemRepository->update([ - 'quantity' => $quantity, - 'total' => core()->convertPrice($item->price * $quantity), - 'base_total' => $item->price * $quantity, - 'total_weight' => $item->weight * $quantity, - 'base_total_weight' => $item->weight * $quantity - ], $itemId); + 'quantity' => $quantity, + 'total' => core()->convertPrice($item->price * $quantity), + 'base_total' => $item->price * $quantity, + 'total_weight' => $item->weight * $quantity, + 'base_total_weight' => $item->weight * $quantity, + ], $itemId); Event::dispatch('checkout.cart.update.after', $item); } @@ -277,6 +286,7 @@ class Cart { * Get cart item by product * * @param array $data + * * @return CartItem|void */ public function getItemByProduct($data) @@ -286,8 +296,10 @@ class Cart { foreach ($items as $item) { if ($item->product->getTypeInstance()->compareOptions($item->additional, $data['additional'])) { if (isset($data['additional']['parent_id'])) { - if ($item->parent->product->getTypeInstance()->compareOptions($item->parent->additional, request()->all())) + if ($item->parent->product->getTypeInstance()->compareOptions($item->parent->additional, + request()->all())) { return $item; + } } else { return $item; } @@ -299,14 +311,16 @@ class Cart { * Remove the item from the cart * * @param integer $itemId + * * @return boolean */ public function removeItem($itemId) { Event::dispatch('checkout.cart.delete.before', $itemId); - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return false; + } $this->cartItemRepository->delete($itemId); @@ -334,18 +348,21 @@ class Cart { public function mergeCart() { if (session()->has('cart')) { - $cart = $this->cartRepository->findOneWhere(['customer_id' => $this->getCurrentCustomer()->user()->id, 'is_active' => 1]); + $cart = $this->cartRepository->findOneWhere([ + 'customer_id' => $this->getCurrentCustomer()->user()->id, + 'is_active' => 1, + ]); $guestCart = session()->get('cart'); //when the logged in customer is not having any of the cart instance previously and are active. - if (! $cart) { + if (!$cart) { $this->cartRepository->update([ - 'customer_id' => $this->getCurrentCustomer()->user()->id, - 'is_guest' => 0, + 'customer_id' => $this->getCurrentCustomer()->user()->id, + 'is_guest' => 0, 'customer_first_name' => $this->getCurrentCustomer()->user()->first_name, - 'customer_last_name' => $this->getCurrentCustomer()->user()->last_name, - 'customer_email' => $this->getCurrentCustomer()->user()->email + 'customer_last_name' => $this->getCurrentCustomer()->user()->last_name, + 'customer_email' => $this->getCurrentCustomer()->user()->email, ], $guestCart->id); session()->forget('cart'); @@ -358,23 +375,25 @@ class Cart { $found = false; foreach ($cart->items as $cartItem) { - if (! $cartItem->product->getTypeInstance()->compareOptions($cartItem->additional, $guestCartItem->additional)) + if (!$cartItem->product->getTypeInstance()->compareOptions($cartItem->additional, + $guestCartItem->additional)) { continue; + } $cartItem->quantity = $newQuantity = $cartItem->quantity + $guestCartItem->quantity; - if (! $this->isItemHaveQuantity($cartItem)) { + if (!$this->isItemHaveQuantity($cartItem)) { $this->cartItemRepository->delete($guestCartItem->id); continue; } $this->cartItemRepository->update([ - 'quantity' => $newQuantity, - 'total' => core()->convertPrice($cartItem->price * $newQuantity), - 'base_total' => $cartItem->price * $newQuantity, - 'total_weight' => $cartItem->weight * $newQuantity, - 'base_total_weight' => $cartItem->weight * $newQuantity + 'quantity' => $newQuantity, + 'total' => core()->convertPrice($cartItem->price * $newQuantity), + 'base_total' => $cartItem->price * $newQuantity, + 'total_weight' => $cartItem->weight * $newQuantity, + 'base_total_weight' => $cartItem->weight * $newQuantity, ], $cartItem->id); $guestCart->items->forget($key); @@ -384,14 +403,14 @@ class Cart { $found = true; } - if (! $found) { + if (!$found) { $this->cartItemRepository->update([ - 'cart_id' => $cart->id + 'cart_id' => $cart->id, ], $guestCartItem->id); foreach ($guestCartItem->children as $child) { $this->cartItemRepository->update([ - 'cart_id' => $cart->id + 'cart_id' => $cart->id, ], $child->id); } } @@ -411,11 +430,12 @@ class Cart { * Save cart * * @param Cart $cart + * * @return void */ public function putCart($cart) { - if (! $this->getCurrentCustomer()->check()) { + if (!$this->getCurrentCustomer()->check()) { session()->put('cart', $cart); } } @@ -432,7 +452,7 @@ class Cart { if ($this->getCurrentCustomer()->check()) { $cart = $this->cartRepository->findOneWhere([ 'customer_id' => $this->getCurrentCustomer()->user()->id, - 'is_active' => 1 + 'is_active' => 1, ]); } elseif (session()->has('cart')) { $cart = $this->cartRepository->find(session()->get('cart')->id); @@ -471,18 +491,20 @@ class Cart { * Save customer address * * @param array $data + * * @return boolean */ public function saveCustomerAddress($data) { - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return false; + } $billingAddress = $data['billing']; $billingAddress['cart_id'] = $cart->id; if (isset($data['billing']['address_id']) && $data['billing']['address_id']) { - $address = $this->customerAddressRepository->findOneWhere(['id'=> $data['billing']['address_id']])->toArray(); + $address = $this->customerAddressRepository->findOneWhere(['id' => $data['billing']['address_id']])->toArray(); $billingAddress['first_name'] = $this->getCurrentCustomer()->user()->first_name; $billingAddress['last_name'] = $this->getCurrentCustomer()->user()->last_name; @@ -496,7 +518,7 @@ class Cart { } if (isset($data['billing']['save_as_address']) && $data['billing']['save_as_address']) { - $billingAddress['customer_id'] = $this->getCurrentCustomer()->user()->id; + $billingAddress['customer_id'] = $this->getCurrentCustomer()->user()->id; $this->customerAddressRepository->create($billingAddress); } @@ -505,7 +527,7 @@ class Cart { $shippingAddress['cart_id'] = $cart->id; if (isset($data['shipping']['address_id']) && $data['shipping']['address_id']) { - $address = $this->customerAddressRepository->findOneWhere(['id'=> $data['shipping']['address_id']])->toArray(); + $address = $this->customerAddressRepository->findOneWhere(['id' => $data['shipping']['address_id']])->toArray(); $shippingAddress['first_name'] = $this->getCurrentCustomer()->user()->first_name; $shippingAddress['last_name'] = $this->getCurrentCustomer()->user()->last_name; @@ -519,7 +541,7 @@ class Cart { } if (isset($data['shipping']['save_as_address']) && $data['shipping']['save_as_address']) { - $shippingAddress['customer_id'] = $this->getCurrentCustomer()->user()->id; + $shippingAddress['customer_id'] = $this->getCurrentCustomer()->user()->id; $this->customerAddressRepository->create($shippingAddress); } @@ -537,9 +559,11 @@ class Cart { } } else { if (isset($billingAddress['use_for_shipping']) && $billingAddress['use_for_shipping']) { - $this->cartAddressRepository->create(array_merge($billingAddress, ['address_type' => 'shipping'])); + $this->cartAddressRepository->create(array_merge($billingAddress, + ['address_type' => 'shipping'])); } else { - $this->cartAddressRepository->create(array_merge($shippingAddress, ['address_type' => 'shipping'])); + $this->cartAddressRepository->create(array_merge($shippingAddress, + ['address_type' => 'shipping'])); } } } @@ -567,6 +591,8 @@ class Cart { $cart->save(); + $this->collectTotals(); + return true; } @@ -574,12 +600,14 @@ class Cart { * Save shipping method for cart * * @param string $shippingMethodCode + * * @return boolean */ public function saveShippingMethod($shippingMethodCode) { - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return false; + } $cart->shipping_method = $shippingMethodCode; $cart->save(); @@ -591,15 +619,18 @@ class Cart { * Save payment method for cart * * @param string $payment + * * @return CartPayment */ public function savePaymentMethod($payment) { - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return false; + } - if ($cartPayment = $cart->payment) + if ($cartPayment = $cart->payment) { $cartPayment->delete(); + } $cartPayment = new CartPayment; @@ -619,11 +650,13 @@ class Cart { { $validated = $this->validateItems(); - if (! $validated) + if (!$validated) { return false; + } - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return false; + } Event::dispatch('checkout.cart.collect.totals.before', $cart); @@ -638,19 +671,19 @@ class Cart { $cart->discount_amount += $item->discount_amount; $cart->base_discount_amount += $item->base_discount_amount; - $cart->grand_total = (float) $cart->grand_total + $item->total + $item->tax_amount - $item->discount_amount; - $cart->base_grand_total = (float) $cart->base_grand_total + $item->base_total + $item->base_tax_amount - $item->base_discount_amount; - - $cart->sub_total = (float) $cart->sub_total + $item->total; - $cart->base_sub_total = (float) $cart->base_sub_total + $item->base_total; - - $cart->tax_total = (float) $cart->tax_total + $item->tax_amount; - $cart->base_tax_total = (float) $cart->base_tax_total + $item->base_tax_amount; + $cart->sub_total = (float)$cart->sub_total + $item->total; + $cart->base_sub_total = (float)$cart->base_sub_total + $item->base_total; } + $cart->tax_total = Tax::getTaxTotal($cart, false); + $cart->base_tax_total = Tax::getTaxTotal($cart, true); + + $cart->grand_total = $cart->sub_total + $cart->tax_total + $cart->discount_amount; + $cart->base_grand_total = $cart->base_sub_total + $cart->base_tax_total - $cart->base_discount_amount; + if ($shipping = $cart->selected_shipping_rate) { - $cart->grand_total = (float) $cart->grand_total + $shipping->price - $shipping->discount_amount; - $cart->base_grand_total = (float) $cart->base_grand_total + $shipping->base_price - $shipping->base_discount_amount; + $cart->grand_total = (float)$cart->grand_total + $shipping->price - $shipping->discount_amount; + $cart->base_grand_total = (float)$cart->base_grand_total + $shipping->base_price - $shipping->base_discount_amount; $cart->discount_amount += $shipping->discount_amount; $cart->base_discount_amount += $shipping->base_discount_amount; @@ -679,8 +712,9 @@ class Cart { */ public function validateItems() { - if (! $cart = $this->getCart()) + if (!$cart = $this->getCart()) { return; + } //rare case of accident-->used when there are no items. if (count($cart->items) == 0) { @@ -691,12 +725,12 @@ class Cart { foreach ($cart->items as $item) { $item->product->getTypeInstance()->validateCartItem($item); - $price = ! is_null($item->custom_price) ? $item->custom_price : $item->base_price; + $price = !is_null($item->custom_price) ? $item->custom_price : $item->base_price; $this->cartItemRepository->update([ - 'price' => core()->convertPrice($price), + 'price' => core()->convertPrice($price), 'base_price' => $price, - 'total' => core()->convertPrice($price * $item->quantity), + 'total' => core()->convertPrice($price * $item->quantity), 'base_total' => $price * $item->quantity, ], $item->id); } @@ -710,19 +744,18 @@ class Cart { * * @return void */ - public function calculateItemsTax() + public function calculateItemsTax(): void { - if (! $cart = $this->getCart()) - return false; - - if (! $cart->shipping_address && ! $cart->billing_address) + if (!$cart = $this->getCart()) { return; + } foreach ($cart->items()->get() as $item) { $taxCategory = $this->taxCategoryRepository->find($item->product->tax_category_id); - if (! $taxCategory) + if (!$taxCategory) { continue; + } if ($item->product->getTypeInstance()->isStockable()) { $address = $cart->shipping_address; @@ -730,58 +763,71 @@ class Cart { $address = $cart->billing_address; } + if ($address === null && auth()->guard('customer')->check()) { + $address = auth()->guard('customer')->user()->addresses() + ->where('default_address',1)->first(); + } + + if ($address === null) { + $address = new class() { + public $country; + public $postcode; + + function __construct() + { + $this->country = strtoupper(config('app.default_country')); + } + }; + } + $taxRates = $taxCategory->tax_rates()->where([ - 'country' => $address->country, - ])->orderBy('tax_rate', 'desc')->get(); + 'country' => $address->country, + ])->orderBy('tax_rate', 'desc')->get(); + + $item = $this->setItemTaxToZero($item); if ($taxRates->count()) { foreach ($taxRates as $rate) { $haveTaxRate = false; if ($rate->state != '' && $rate->state != $address->state) { - $this->setItemTaxToZero($item); - continue; } - - if (! $rate->is_zip) { - if ($rate->zip_code == '*' || $rate->zip_code == $address->postcode) + if (!$rate->is_zip) { + if ($rate->zip_code == '*' || $rate->zip_code == $address->postcode) { $haveTaxRate = true; + } } else { - if ($address->postcode >= $rate->zip_from && $address->postcode <= $rate->zip_to) + if ($address->postcode >= $rate->zip_from && $address->postcode <= $rate->zip_to) { $haveTaxRate = true; + } } - if ($haveTaxRate) { $item->tax_percent = $rate->tax_rate; $item->tax_amount = ($item->total * $rate->tax_rate) / 100; $item->base_tax_amount = ($item->base_total * $rate->tax_rate) / 100; - $item->save(); - break; - } else { - $this->setItemTaxToZero($item); - break; } } - } else { - $this->setItemTaxToZero($item); } + + $item->save(); } } /** * Set Item tax to zero. * - * @return void + * @param CartItem $item + * @return CartItem */ - protected function setItemTaxToZero($item) { + protected function setItemTaxToZero(CartItem $item): CartItem { $item->tax_percent = 0; $item->tax_amount = 0; $item->base_tax_amount = 0; - $item->save(); + return $item; } /** @@ -791,11 +837,13 @@ class Cart { */ public function hasError() { - if (! $this->getCart()) + if (!$this->getCart()) { return true; + } - if (! $this->isItemsHaveSufficientQuantity()) + if (!$this->isItemsHaveSufficientQuantity()) { return true; + } return false; } @@ -808,8 +856,9 @@ class Cart { public function isItemsHaveSufficientQuantity() { foreach ($this->getCart()->items as $item) { - if (! $this->isItemHaveQuantity($item)) + if (!$this->isItemHaveQuantity($item)) { return false; + } } return true; @@ -819,6 +868,7 @@ class Cart { * Checks if all cart items have sufficient quantity. * * @param CartItem $item + * * @return boolean */ public function isItemHaveQuantity($item) @@ -852,42 +902,42 @@ class Cart { $data = $this->toArray(); $finalData = [ - 'cart_id' => $this->getCart()->id, - 'customer_id' => $data['customer_id'], - 'is_guest' => $data['is_guest'], - 'customer_email' => $data['customer_email'], - 'customer_first_name' => $data['customer_first_name'], - 'customer_last_name' => $data['customer_last_name'], - 'customer' => $this->getCurrentCustomer()->check() ? $this->getCurrentCustomer()->user() : null, - 'total_item_count' => $data['items_count'], - 'total_qty_ordered' => $data['items_qty'], - 'base_currency_code' => $data['base_currency_code'], + 'cart_id' => $this->getCart()->id, + 'customer_id' => $data['customer_id'], + 'is_guest' => $data['is_guest'], + 'customer_email' => $data['customer_email'], + 'customer_first_name' => $data['customer_first_name'], + 'customer_last_name' => $data['customer_last_name'], + 'customer' => $this->getCurrentCustomer()->check() ? $this->getCurrentCustomer()->user() : null, + 'total_item_count' => $data['items_count'], + 'total_qty_ordered' => $data['items_qty'], + 'base_currency_code' => $data['base_currency_code'], 'channel_currency_code' => $data['channel_currency_code'], - 'order_currency_code' => $data['cart_currency_code'], - 'grand_total' => $data['grand_total'], - 'base_grand_total' => $data['base_grand_total'], - 'sub_total' => $data['sub_total'], - 'base_sub_total' => $data['base_sub_total'], - 'tax_amount' => $data['tax_total'], - 'base_tax_amount' => $data['base_tax_total'], - 'coupon_code' => $data['coupon_code'], + 'order_currency_code' => $data['cart_currency_code'], + 'grand_total' => $data['grand_total'], + 'base_grand_total' => $data['base_grand_total'], + 'sub_total' => $data['sub_total'], + 'base_sub_total' => $data['base_sub_total'], + 'tax_amount' => $data['tax_total'], + 'base_tax_amount' => $data['base_tax_total'], + 'coupon_code' => $data['coupon_code'], 'applied_cart_rule_ids' => $data['applied_cart_rule_ids'], - 'discount_amount' => $data['discount_amount'], - 'base_discount_amount' => $data['base_discount_amount'], - 'billing_address' => Arr::except($data['billing_address'], ['id', 'cart_id']), - 'payment' => Arr::except($data['payment'], ['id', 'cart_id']), - 'channel' => core()->getCurrentChannel(), + 'discount_amount' => $data['discount_amount'], + 'base_discount_amount' => $data['base_discount_amount'], + 'billing_address' => Arr::except($data['billing_address'], ['id', 'cart_id']), + 'payment' => Arr::except($data['payment'], ['id', 'cart_id']), + 'channel' => core()->getCurrentChannel(), ]; if ($this->getCart()->haveStockableItems()) { $finalData = array_merge($finalData, [ - 'shipping_method' => $data['selected_shipping_rate']['method'], - 'shipping_title' => $data['selected_shipping_rate']['carrier_title'] . ' - ' . $data['selected_shipping_rate']['method_title'], - 'shipping_description' => $data['selected_shipping_rate']['method_description'], - 'shipping_amount' => $data['selected_shipping_rate']['price'], - 'base_shipping_amount' => $data['selected_shipping_rate']['base_price'], - 'shipping_address' => Arr::except($data['shipping_address'], ['id', 'cart_id']), - 'shipping_discount_amount' => $data['selected_shipping_rate']['discount_amount'], + 'shipping_method' => $data['selected_shipping_rate']['method'], + 'shipping_title' => $data['selected_shipping_rate']['carrier_title'] . ' - ' . $data['selected_shipping_rate']['method_title'], + 'shipping_description' => $data['selected_shipping_rate']['method_description'], + 'shipping_amount' => $data['selected_shipping_rate']['price'], + 'base_shipping_amount' => $data['selected_shipping_rate']['base_price'], + 'shipping_address' => Arr::except($data['shipping_address'], ['id', 'cart_id']), + 'shipping_discount_amount' => $data['selected_shipping_rate']['discount_amount'], 'base_shipping_discount_amount' => $data['selected_shipping_rate']['base_discount_amount'], ]); } @@ -903,29 +953,30 @@ class Cart { * Prepares data for order item * * @param array $data + * * @return array */ public function prepareDataForOrderItem($data) { $finalData = [ - 'product' => $this->productRepository->find($data['product_id']), - 'sku' => $data['sku'], - 'type' => $data['type'], - 'name' => $data['name'], - 'weight' => $data['weight'], - 'total_weight' => $data['total_weight'], - 'qty_ordered' => $data['quantity'], - 'price' => $data['price'], - 'base_price' => $data['base_price'], - 'total' => $data['total'], - 'base_total' => $data['base_total'], - 'tax_percent' => $data['tax_percent'], - 'tax_amount' => $data['tax_amount'], - 'base_tax_amount' => $data['base_tax_amount'], - 'discount_percent' => $data['discount_percent'], - 'discount_amount' => $data['discount_amount'], + 'product' => $this->productRepository->find($data['product_id']), + 'sku' => $data['sku'], + 'type' => $data['type'], + 'name' => $data['name'], + 'weight' => $data['weight'], + 'total_weight' => $data['total_weight'], + 'qty_ordered' => $data['quantity'], + 'price' => $data['price'], + 'base_price' => $data['base_price'], + 'total' => $data['total'], + 'base_total' => $data['base_total'], + 'tax_percent' => $data['tax_percent'], + 'tax_amount' => $data['tax_amount'], + 'base_tax_amount' => $data['base_tax_amount'], + 'discount_percent' => $data['discount_percent'], + 'discount_amount' => $data['discount_amount'], 'base_discount_amount' => $data['base_discount_amount'], - 'additional' => $data['additional'], + 'additional' => $data['additional'], ]; if (isset($data['children']) && $data['children']) { @@ -943,15 +994,18 @@ class Cart { * Move a wishlist item to cart * * @param WishlistItem $wishlistItem + * * @return boolean */ public function moveToCart($wishlistItem) { - if (! $wishlistItem->product->getTypeInstance()->canBeMovedFromWishlistToCart($wishlistItem)) + if (!$wishlistItem->product->getTypeInstance()->canBeMovedFromWishlistToCart($wishlistItem)) { return false; + } - if (! $wishlistItem->additional) + if (!$wishlistItem->additional) { $wishlistItem->additional = ['product_id' => $wishlistItem->product_id]; + } request()->merge($wishlistItem->additional); @@ -970,6 +1024,7 @@ class Cart { * Function to move a already added product to wishlist will run only on customer authentication. * * @param integer $itemId + * * @return boolean|void */ public function moveToWishlist($itemId) @@ -978,13 +1033,14 @@ class Cart { $cartItem = $cart->items()->find($itemId); - if (! $cartItem) + if (!$cartItem) { return false; + } $wishlistItems = $this->wishlistRepository->findWhere([ - 'customer_id' => $this->getCurrentCustomer()->user()->id, - 'product_id' => $cartItem->product_id - ]); + 'customer_id' => $this->getCurrentCustomer()->user()->id, + 'product_id' => $cartItem->product_id, + ]); $found = false; @@ -998,19 +1054,20 @@ class Cart { $found = true; } - if (! $found) { + if (!$found) { $this->wishlistRepository->create([ - 'channel_id' => $cart->channel_id, - 'customer_id' => $this->getCurrentCustomer()->user()->id, - 'product_id' => $cartItem->product_id, - 'additional' => $cartItem->additional - ]); + 'channel_id' => $cart->channel_id, + 'customer_id' => $this->getCurrentCustomer()->user()->id, + 'product_id' => $cartItem->product_id, + 'additional' => $cartItem->additional, + ]); } $result = $this->cartItemRepository->delete($itemId); - if (! $cart->items()->count()) + if (!$cart->items()->count()) { $this->cartRepository->delete($cart->id); + } $this->collectTotals(); @@ -1021,6 +1078,7 @@ class Cart { * Set coupon code to the cart * * @param string $code + * * @return Cart */ public function setCouponCode($code) @@ -1049,4 +1107,4 @@ class Cart { return $this; } -} \ No newline at end of file +} diff --git a/packages/Webkul/Core/src/Core.php b/packages/Webkul/Core/src/Core.php index d39459dff..7bae26f5d 100755 --- a/packages/Webkul/Core/src/Core.php +++ b/packages/Webkul/Core/src/Core.php @@ -969,4 +969,15 @@ class Core return $instance[$className] = app($className); } + + /** + * Returns a string as selector part for identifying elements in views + * @param float $taxRate + * + * @return string + */ + public static function taxRateAsIdentifier(float $taxRate): string + { + return str_replace('.', '_', (string)$taxRate); + } } \ No newline at end of file diff --git a/packages/Webkul/Core/src/Helpers/Laravel5Helper.php b/packages/Webkul/Core/src/Helpers/Laravel5Helper.php index b15b3d89f..baa7a650c 100644 --- a/packages/Webkul/Core/src/Helpers/Laravel5Helper.php +++ b/packages/Webkul/Core/src/Helpers/Laravel5Helper.php @@ -72,6 +72,7 @@ class Laravel5Helper extends Laravel5 * @param array $productStates * * @return \Webkul\Product\Models\Product + * @part ORM */ public function haveProduct(int $productType, array $configs = [], array $productStates = []): Product { diff --git a/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php b/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php index 4da6ddaf0..cf06abca4 100644 --- a/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php +++ b/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php @@ -42,7 +42,7 @@ $factory->defineAs(ProductAttributeValue::class, 'tax_category_id', function (Fa return factory(Product::class)->create()->id; }, 'channel' => 'default', - 'integer_value' => null, // ToDo + 'integer_value' => null, 'attribute_id' => 4, ]; }); diff --git a/packages/Webkul/Shop/src/Resources/views/checkout/total/summary.blade.php b/packages/Webkul/Shop/src/Resources/views/checkout/total/summary.blade.php index 70d5d87d6..76a6b238b 100755 --- a/packages/Webkul/Shop/src/Resources/views/checkout/total/summary.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/checkout/total/summary.blade.php @@ -18,10 +18,12 @@ @endif @if ($cart->base_tax_total) + @foreach (Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($cart, true) as $taxRate => $baseTaxAmount )
- - + +
+ @endforeach @endif
base_discount_amount && $cart->base_discount_amount > 0) style="display: block;" @else style="display: none;" @endif> diff --git a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-admin-order.blade.php b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-admin-order.blade.php index 9e913b50b..ec6933735 100644 --- a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-admin-order.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-admin-order.blade.php @@ -123,7 +123,7 @@ @if (isset($item->additional['attributes']))
- + @foreach ($item->additional['attributes'] as $attribute) {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
@endforeach @@ -161,12 +161,14 @@
+ @foreach (Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($order, true) as $taxRate => $baseTaxAmount )
- {{ __('shop::app.mail.order.tax') }} - - {{ core()->formatBasePrice($order->base_tax_amount) }} + {{ __('shop::app.mail.order.tax') }} {{ $taxRate }} % + + {{ core()->formatBasePrice($baseTaxAmount) }}
+ @endforeach @if ($order->discount_amount > 0)
diff --git a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-invoice.blade.php b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-invoice.blade.php index 55b0d5264..5c24a70fd 100755 --- a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-invoice.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-invoice.blade.php @@ -30,7 +30,8 @@ {{ __('shop::app.mail.invoice.summary') }}
-
+
@if ($order->shipping_address)
@@ -103,37 +104,41 @@ - - - - - + + + + + - @foreach ($invoice->items as $item) - - + + @if (isset($item->additional['attributes'])) +
-
+ @foreach ($item->additional['attributes'] as $attribute) + {{ $attribute['attribute_name'] }} + : {{ $attribute['option_label'] }}
+ @endforeach - - + + @endif + - @endforeach + + + + + + @endforeach
{{ __('shop::app.customer.account.order.view.product-name') }}{{ __('shop::app.customer.account.order.view.price') }}{{ __('shop::app.customer.account.order.view.qty') }}
{{ __('shop::app.customer.account.order.view.product-name') }}{{ __('shop::app.customer.account.order.view.price') }}{{ __('shop::app.customer.account.order.view.qty') }}
- {{ $item->name }} - - @if (isset($item->additional['attributes'])) -
- - @foreach ($item->additional['attributes'] as $attribute) - {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
- @endforeach + @foreach ($invoice->items as $item) +
+ {{ $item->name }} - - @endif - {{ core()->formatPrice($item->price, $order->order_currency_code) }} - {{ $item->qty }}
{{ core()->formatPrice($item->price, $order->order_currency_code) }} + {{ $item->qty }}
@@ -146,7 +151,7 @@ {{ core()->formatPrice($invoice->sub_total, $invoice->order_currency_code) }}
- + @if ($order->shipping_address)
{{ __('shop::app.mail.order.shipping-handling') }} @@ -158,8 +163,8 @@
{{ __('shop::app.mail.order.tax') }} - - {{ core()->formatPrice($invoice->tax_amount, $invoice->order_currency_code) }} + + {{ core()->formatPrice($invoice->tax_amount, $order->order_currency_code) }}
@@ -180,7 +185,8 @@
-
+

{!! __('shop::app.mail.order.help', [ diff --git a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-order.blade.php b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-order.blade.php index 2d848b5ca..a1b58f641 100755 --- a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-order.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-order.blade.php @@ -116,10 +116,10 @@ {{ $item->name }} - + @if (isset($item->additional['attributes']))

- + @foreach ($item->additional['attributes'] as $attribute) {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
@endforeach @@ -156,12 +156,14 @@
@endif + @foreach (Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($order, false) as $taxRate => $taxAmount )
- {{ __('shop::app.mail.order.tax') }} - - {{ core()->formatPrice($order->tax_amount, $order->order_currency_code) }} + {{ __('shop::app.mail.order.tax') }} {{ $taxRate }} % + + {{ core()->formatPrice($taxAmount, $order->order_currency_code) }}
+ @endforeach @if ($order->discount_amount > 0)
diff --git a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-refund.blade.php b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-refund.blade.php index d80d9937d..8bd270795 100644 --- a/packages/Webkul/Shop/src/Resources/views/emails/sales/new-refund.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/emails/sales/new-refund.blade.php @@ -115,10 +115,10 @@ {{ $item->name }} - + @if (isset($item->additional['attributes']))
- + @foreach ($item->additional['attributes'] as $attribute) {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
@endforeach @@ -160,12 +160,14 @@ @endif @if ($refund->tax_amount > 0) + @foreach (Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($refund, false) as $taxRate => $taxAmount)
{{ __('shop::app.mail.order.tax') }} {{ core()->formatPrice($refund->tax_amount, $refund->order_currency_code) }}
+ @endforeach @endif @if ($refund->discount_amount > 0) diff --git a/packages/Webkul/Shop/src/Resources/views/emails/sales/order-cancel.blade.php b/packages/Webkul/Shop/src/Resources/views/emails/sales/order-cancel.blade.php index cd6b9600e..2b0139e1c 100644 --- a/packages/Webkul/Shop/src/Resources/views/emails/sales/order-cancel.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/emails/sales/order-cancel.blade.php @@ -119,7 +119,7 @@ @if (isset($item->additional['attributes']))
- + @foreach ($item->additional['attributes'] as $attribute) {{ $attribute['attribute_name'] }} : {{ $attribute['option_label'] }}
@endforeach @@ -157,12 +157,14 @@
-
- {{ __('shop::app.mail.order.cancel.tax') }} - - {{ core()->formatPrice($order->tax_amount, $order->order_currency_code) }} + @foreach (Webkul\Tax\Helpers\Tax::getTaxRatesWithAmount($order, false) as $taxRate => $taxAmount ) +
+ {{ __('shop::app.mail.order.cancel.tax') }} {{ $taxRate }} % + + {{ core()->formatPrice($taxAmount, $order->order_currency_code) }} -
+
+ @endforeach @if ($order->discount_amount > 0)
diff --git a/packages/Webkul/Tax/src/Database/Factories/TaxCategoryFactory.php b/packages/Webkul/Tax/src/Database/Factories/TaxCategoryFactory.php new file mode 100644 index 000000000..b704d1d84 --- /dev/null +++ b/packages/Webkul/Tax/src/Database/Factories/TaxCategoryFactory.php @@ -0,0 +1,17 @@ +define(TaxCategory::class, function (Faker $faker) { + return [ + 'channel_id' => function () { + return core()->getCurrentChannel()->id; + }, + 'code' => $faker->uuid, + 'name' => $faker->words(2, true), + 'description' => $faker->sentence(10), + ]; +}); diff --git a/packages/Webkul/Tax/src/Database/Factories/TaxMapFactory.php b/packages/Webkul/Tax/src/Database/Factories/TaxMapFactory.php new file mode 100644 index 000000000..7e7d50769 --- /dev/null +++ b/packages/Webkul/Tax/src/Database/Factories/TaxMapFactory.php @@ -0,0 +1,19 @@ +define(TaxMap::class, function (Faker $faker) { + return [ + 'tax_category_id' => function () { + return factory(TaxCategory::class)->create()->id; + }, + 'tax_rate_id' => function () { + return factory(TaxRate::class)->create()->id; + }, + ]; +}); diff --git a/packages/Webkul/Tax/src/Database/Factories/TaxRateFactory.php b/packages/Webkul/Tax/src/Database/Factories/TaxRateFactory.php new file mode 100644 index 000000000..a611bebc0 --- /dev/null +++ b/packages/Webkul/Tax/src/Database/Factories/TaxRateFactory.php @@ -0,0 +1,19 @@ +define(TaxRate::class, function (Faker $faker) { + return [ + 'identifier' => $faker->uuid, + 'is_zip' => 0, + 'zip_code' => '*', + 'zip_from' => null, + 'zip_to' => null, + 'state' => '', + 'country' => $faker->countryCode, + 'tax_rate' => $faker->randomFloat(2, 3, 25), + ]; +}); diff --git a/packages/Webkul/Tax/src/Helpers/Tax.php b/packages/Webkul/Tax/src/Helpers/Tax.php new file mode 100644 index 000000000..fac5dc7ca --- /dev/null +++ b/packages/Webkul/Tax/src/Helpers/Tax.php @@ -0,0 +1,48 @@ +items as $item) { + $taxRate = (string)round((float)$item->tax_percent, self::TAX_PRECISION); + + if (!array_key_exists($taxRate, $taxes)) { + $taxes[$taxRate] = 0; + } + $taxes[$taxRate] += $asBase ? $item->base_tax_amount : $item->tax_amount; + } + + return $taxes; + } + + /** + * Returns the total tax amount + * @param object $that + * @param bool $asBase + * + * @return float + */ + public static function getTaxTotal(object $that, bool $asBase = false): float + { + $taxes = self::getTaxRatesWithAmount($that, $asBase); + + $result = 0; + foreach ($taxes as $taxRate => $taxAmount) { + $result += round($taxAmount, 2); + } + return $result; + } +} \ No newline at end of file diff --git a/packages/Webkul/Tax/src/Providers/TaxServiceProvider.php b/packages/Webkul/Tax/src/Providers/TaxServiceProvider.php index 9f67df0df..181258405 100755 --- a/packages/Webkul/Tax/src/Providers/TaxServiceProvider.php +++ b/packages/Webkul/Tax/src/Providers/TaxServiceProvider.php @@ -22,5 +22,6 @@ class TaxServiceProvider extends ServiceProvider */ public function register() { + $this->loadFactoriesFrom(__DIR__ . '/../Database/Factories'); } } \ No newline at end of file diff --git a/tests/_support/UnitTester.php b/tests/_support/UnitTester.php index 5e3dccc40..43a47890d 100644 --- a/tests/_support/UnitTester.php +++ b/tests/_support/UnitTester.php @@ -16,7 +16,7 @@ use Codeception\Stub; * @method void pause() * * @SuppressWarnings(PHPMD) -*/ + */ class UnitTester extends \Codeception\Actor { use _generated\UnitTesterActions; diff --git a/tests/functional/Shop/CartTaxesCest.php b/tests/functional/Shop/CartTaxesCest.php new file mode 100644 index 000000000..6e474fe16 --- /dev/null +++ b/tests/functional/Shop/CartTaxesCest.php @@ -0,0 +1,436 @@ +country = strtoupper(Config::get('app.default_country')) ?? 'DE'; + } + + public function checkCartWithMultipleTaxRates(FunctionalTester $I): void + { + $tax1 = $I->have(TaxRate::class, [ + 'country' => $this->country, + ]); + $taxCategorie1 = $I->have(TaxCategory::class); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax1->id, + 'tax_category_id' => $taxCategorie1->id, + ]); + + $tax2 = $I->have(TaxRate::class, [ + 'country' => $this->country, + ]); + $taxCategorie2 = $I->have(TaxCategory::class); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax2->id, + 'tax_category_id' => $taxCategorie2->id, + ]); + + $config1 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie1->id, + ], + ]; + $product1 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $config1); + + $config2 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie2->id, + ], + ]; + $product2 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $config2); + + $prod1Quantity = $I->fake()->numberBetween(9, 30); + // quantity of product1 should be not even + if ($prod1Quantity % 2 !== 0) { + $prod1Quantity -= 1; + } + + $prod2Quantity = $I->fake()->numberBetween(9, 30); + // quantity of product2 should be even + if ($prod2Quantity % 2 == 0) { + $prod2Quantity -= 1; + } + + Cart::addProduct($product1->id, [ + '_token' => session('_token'), + 'product_id' => $product1->id, + 'quantity' => 1, + ]); + + $I->amOnPage('/checkout/cart'); + $I->see('Tax ' . $tax1->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + $I->see( + core()->currency(round($product1->price * $tax1->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax1->tax_rate) + ); + + Cart::addProduct($product1->id, [ + '_token' => session('_token'), + 'product_id' => $product1->id, + 'quantity' => $prod1Quantity, + ]); + + $I->amOnPage('/checkout/cart'); + $I->see('Tax ' . $tax1->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + $I->see( + core()->currency(round(($prod1Quantity + 1) * $product1->price * $tax1->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax1->tax_rate) + ); + + Cart::addProduct($product2->id, [ + '_token' => session('_token'), + 'product_id' => $product2->id, + 'quantity' => $prod2Quantity, + ]); + + $I->amOnPage('/checkout/cart'); + $I->see('Tax ' . $tax1->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + $taxAmount1 = round(($prod1Quantity + 1) * $product1->price * $tax1->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount1), '#basetaxamount-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + + $I->see('Tax ' . $tax2->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax2->tax_rate)); + $taxAmount2 = round($prod2Quantity * $product2->price * $tax2->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount2), '#basetaxamount-' . core()->taxRateAsIdentifier($tax2->tax_rate)); + + $cart = Cart::getCart(); + + $I->assertEquals(2, $cart->items_count); + $I->assertEquals((float)($prod1Quantity + 1 + $prod2Quantity), $cart->items_qty); + $I->assertEquals($taxAmount1 + $taxAmount2, $cart->tax_total); + + Cart::removeItem($cart->items[1]->id); + + $I->amOnPage('/checkout/cart'); + $I->amOnPage('/checkout/cart'); + $I->see('Tax ' . $tax1->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + $taxAmount1 = round(($prod1Quantity + 1) * $product1->price * $tax1->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount1), '#basetaxamount-' . core()->taxRateAsIdentifier($tax1->tax_rate)); + + $I->dontSee('Tax ' . $tax2->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax2->tax_rate)); + $taxAmount2 = round($prod2Quantity * $product2->price * $tax2->tax_rate / 100, 2); + $I->dontSee(core()->currency($taxAmount2), '#basetaxamount-' . core()->taxRateAsIdentifier($tax2->tax_rate)); + + $cart = Cart::getCart(); + + $I->assertEquals(1, $cart->items_count); + $I->assertEquals((float)($prod1Quantity + 1), $cart->items_qty); + $I->assertEquals($taxAmount1, $cart->tax_total); + } + + public function checkCartWithMultipleZipRangeBasedTaxes(FunctionalTester $I): void + { + $tax11 = $I->have(TaxRate::class, [ + 'country' => $this->country, + 'is_zip' => 1, + 'zip_code' => null, + 'zip_from' => '00000', + 'zip_to' => '49999', + 'tax_rate' => $I->fake()->randomFloat(2, 3, 8), + ]); + $tax12 = $I->have(TaxRate::class, [ + 'country' => $this->country, + 'is_zip' => 1, + 'zip_code' => null, + 'zip_from' => '50000', + 'zip_to' => '89999', + 'tax_rate' => $I->fake()->randomFloat(2, 3, 8), + ]); + + $taxCategorie1 = $I->have(TaxCategory::class); + + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax11->id, + 'tax_category_id' => $taxCategorie1->id, + ]); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax12->id, + 'tax_category_id' => $taxCategorie1->id, + ]); + + $tax21 = $I->have(TaxRate::class, [ + 'country' => $this->country, + 'is_zip' => 1, + 'zip_code' => null, + 'zip_from' => '00000', + 'zip_to' => '49999', + 'tax_rate' => $I->fake()->randomFloat(2, 14, 25), + ]); + $tax22 = $I->have(TaxRate::class, [ + 'country' => $this->country, + 'is_zip' => 1, + 'zip_code' => null, + 'zip_from' => '50000', + 'zip_to' => '89999', + 'tax_rate' => $I->fake()->randomFloat(2, 14, 25), + ]); + + $taxCategorie2 = $I->have(TaxCategory::class); + + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax21->id, + 'tax_category_id' => $taxCategorie2->id, + ]); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax22->id, + 'tax_category_id' => $taxCategorie2->id, + ]); + + $config1 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie1->id, + ], + ]; + $product1 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $config1); + + $config2 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie2->id, + ], + ]; + $product2 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $config2); + + $customer = $I->have(Customer::class); + + $addressZip012345 = $I->have(CustomerAddress::class, [ + 'customer_id' => $customer->id, + 'postcode' => '012345', + 'vat_id' => 'DE123456789', + 'country' => $this->country, + 'default_address' => 1, + ]); + + Cart::addProduct($product1->id, [ + '_token' => session('_token'), + 'product_id' => $product1->id, + 'quantity' => 1, + ]); + + Cart::saveCustomerAddress( + [ + 'billing' => [ + 'address1' => $addressZip012345->address1, + 'use_for_shipping' => 1, + 'email' => $customer->email, + 'company_name' => $addressZip012345->company_name, + 'first_name' => $addressZip012345->first_name, + 'last_name' => $addressZip012345->last_name, + 'city' => $addressZip012345->city, + 'state' => $addressZip012345->state, + 'postcode' => $addressZip012345->postcode, + 'country' => $addressZip012345->country, + ], + 'shipping' => [ + 'address1' => '', + ], + ]); + + $I->wantToTest('customer address with postcode in range of 00000 - 49999'); + $I->amOnPage('/checkout/cart'); + + $I->see('Tax ' . $tax11->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax11->tax_rate)); + $I->see( + core()->currency(round($product1->price * $tax11->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax11->tax_rate) + ); + + $I->dontSee('Tax ' . $tax12->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax12->tax_rate)); + $I->dontSee( + core()->currency(round($product1->price * $tax12->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax12->tax_rate) + ); + + $I->dontSee('Tax ' . $tax21->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax21->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax21->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax21->tax_rate) + ); + + $I->dontSee('Tax ' . $tax22->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax22->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax22->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax22->tax_rate) + ); + + Cart::addProduct($product2->id, [ + '_token' => session('_token'), + 'product_id' => $product2->id, + 'quantity' => 1, + ]); + + $I->amOnPage('/checkout/cart'); + + $I->see('Tax ' . $tax11->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax11->tax_rate)); + $I->see( + core()->currency(round($product1->price * $tax11->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax11->tax_rate) + ); + + $I->dontSee('Tax ' . $tax12->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax12->tax_rate)); + $I->dontSee( + core()->currency(round($product1->price * $tax12->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax12->tax_rate) + ); + + $I->see('Tax ' . $tax21->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax21->tax_rate)); + $I->see( + core()->currency(round($product2->price * $tax21->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax21->tax_rate) + ); + + $I->dontSee('Tax ' . $tax22->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax22->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax22->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax22->tax_rate) + ); + + $taxAmount1 = round($product1->price * $tax11->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount1), '#basetaxamount-' . core()->taxRateAsIdentifier($tax11->tax_rate)); + + $taxAmount2 = round($product2->price * $tax21->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount2), '#basetaxamount-' . core()->taxRateAsIdentifier($tax21->tax_rate)); + + + $I->wantToTest('customer address with postcode in range of 50000 - 89999'); + $addressZip67890 = $I->have(CustomerAddress::class, [ + 'customer_id' => $customer->id, + 'postcode' => '67890', + 'vat_id' => 'DE123456789', + 'country' => $this->country, + 'default_address' => 1, + ]); + + Cart::saveCustomerAddress( + [ + 'billing' => [ + 'address1' => $addressZip67890->address1, + 'use_for_shipping' => 1, + 'email' => $customer->email, + 'company_name' => $addressZip67890->company_name, + 'first_name' => $addressZip67890->first_name, + 'last_name' => $addressZip67890->last_name, + 'city' => $addressZip67890->city, + 'state' => $addressZip67890->state, + 'postcode' => $addressZip67890->postcode, + 'country' => $addressZip67890->country, + ], + 'shipping' => [ + 'address1' => '', + ], + ]); + + $I->amOnPage('/checkout/cart'); + + $I->dontSee('Tax ' . $tax11->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax11->tax_rate)); + $I->dontSee( + core()->currency(round($product1->price * $tax11->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax11->tax_rate) + ); + + $I->see('Tax ' . $tax12->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax12->tax_rate)); + $I->see( + core()->currency(round($product1->price * $tax12->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax12->tax_rate) + ); + + $I->dontSee('Tax ' . $tax21->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax21->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax21->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax21->tax_rate) + ); + + $I->see('Tax ' . $tax22->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax22->tax_rate)); + $I->see( + core()->currency(round($product2->price * $tax22->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax22->tax_rate) + ); + + $taxAmount1 = round($product1->price * $tax12->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount1), '#basetaxamount-' . core()->taxRateAsIdentifier($tax12->tax_rate)); + + $taxAmount2 = round($product2->price * $tax22->tax_rate / 100, 2); + $I->see(core()->currency($taxAmount2), '#basetaxamount-' . core()->taxRateAsIdentifier($tax22->tax_rate)); + + $I->wantToTest('customer address with postcode in range of 90000 - 99000'); + $I->wanttoTest('as we dont have any taxes in this zip range'); + $addressZip98765 = $I->have(CustomerAddress::class, [ + 'customer_id' => $customer->id, + 'postcode' => '98765', + 'vat_id' => 'DE123456789', + 'country' => $this->country, + 'default_address' => 1, + ]); + + Cart::saveCustomerAddress( + [ + 'billing' => [ + 'address1' => $addressZip98765->address1, + 'use_for_shipping' => 1, + 'email' => $customer->email, + 'company_name' => $addressZip98765->company_name, + 'first_name' => $addressZip98765->first_name, + 'last_name' => $addressZip98765->last_name, + 'city' => $addressZip98765->city, + 'state' => $addressZip98765->state, + 'postcode' => $addressZip98765->postcode, + 'country' => $addressZip98765->country, + ], + 'shipping' => [ + 'address1' => '', + ], + ]); + + $I->amOnPage('/checkout/cart'); + + $I->dontSee('Tax ' . $tax11->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax11->tax_rate)); + $I->dontSee( + core()->currency(round($product1->price * $tax11->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax11->tax_rate) + ); + + $I->dontSee('Tax ' . $tax12->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax12->tax_rate)); + $I->dontSee( + core()->currency(round($product1->price * $tax12->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax12->tax_rate) + ); + + $I->dontSee('Tax ' . $tax21->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax21->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax21->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax21->tax_rate) + ); + + $I->dontSee('Tax ' . $tax22->tax_rate . ' %', '#taxrate-' . core()->taxRateAsIdentifier($tax22->tax_rate)); + $I->dontSee( + core()->currency(round($product2->price * $tax22->tax_rate / 100, 2)), + '#basetaxamount-' . core()->taxRateAsIdentifier($tax22->tax_rate) + ); + } +} \ No newline at end of file diff --git a/tests/functional/Shop/GuestCheckoutCest.php b/tests/functional/Shop/GuestCheckoutCest.php index 4a62cb411..dd6e1e76a 100644 --- a/tests/functional/Shop/GuestCheckoutCest.php +++ b/tests/functional/Shop/GuestCheckoutCest.php @@ -1,6 +1,6 @@ productNoGuestCheckout = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $pConfigDefault, ['simple']); + $this->productNoGuestCheckout = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $pConfigDefault); $this->productNoGuestCheckout->refresh(); - $this->productGuestCheckout = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $pConfigGuestCheckout, ['simple']); + $this->productGuestCheckout = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT, $pConfigGuestCheckout); $this->productGuestCheckout->refresh(); } diff --git a/tests/unit/Checkout/Cart/Controllers/CartControllerCest.php b/tests/unit/Checkout/Cart/Controllers/CartControllerCest.php index 5d05a6ca3..ce950fc33 100644 --- a/tests/unit/Checkout/Cart/Controllers/CartControllerCest.php +++ b/tests/unit/Checkout/Cart/Controllers/CartControllerCest.php @@ -3,34 +3,46 @@ namespace Tests\Unit\Checkout\Cart\Controllers; use UnitTester; +use Codeception\Example; use Webkul\Checkout\Models\Cart; use Webkul\Shop\Http\Controllers\CartController; class CartControllerCest { - public function _before(UnitTester $I) + /** + * @param \UnitTester $I + * + * @param \Example $scenario + * + * @throws \Exception + * @dataProvider getOnWarningAddingToCartScenarios + */ + public function testOnWarningAddingToCart(UnitTester $I, Example $scenario): void { + $I->assertEquals($scenario['expected'], + $I->executeFunction( + CartController::class, + 'onWarningAddingToCart', + [$scenario['result']] + ) + ); } - public function testOnWarningAddingToCart(UnitTester $I) + protected function getOnWarningAddingToCartScenarios(): array { - $scenarios = [ + return [ [ - 'result' => ['key' => 'value', 'warning' => 'Hello World. Something went wrong.'], + 'result' => ['key' => 'value', 'warning' => 'Hello World. Something went wrong.'], 'expected' => true, ], [ - 'result' => ['key' => 'value'], + 'result' => ['key' => 'value'], 'expected' => false, ], [ - 'result' => new Cart(), + 'result' => new Cart(), 'expected' => false, ], ]; - - foreach ($scenarios as $scenario) { - $I->assertEquals($scenario['expected'], $I->executeFunction(CartController::class, 'onWarningAddingToCart', [$scenario['result']])); - } } } \ No newline at end of file diff --git a/tests/unit/Core/CoreCest.php b/tests/unit/Core/CoreCest.php new file mode 100644 index 000000000..89ee927ed --- /dev/null +++ b/tests/unit/Core/CoreCest.php @@ -0,0 +1,52 @@ +assertEquals( + $scenario['expected'], + $I->executeFunction( + \Webkul\Core\Core::class, + 'taxRateAsIdentifier', + [$scenario['input']] + ) + ); + } + + protected function getTaxRateScenarios(): array + { + return [ + [ + 'input' => 0, + 'expected' => '0', + ], + [ + 'input' => 0.01, + 'expected' => '0_01', + ], + [ + 'input' => .12, + 'expected' => '0_12', + ], + [ + 'input' => 1234.5678, + 'expected' => '1234_5678', + ], + ]; + } +} diff --git a/tests/unit/Tax/Helpers/TaxCest.php b/tests/unit/Tax/Helpers/TaxCest.php new file mode 100644 index 000000000..7a29e5e3a --- /dev/null +++ b/tests/unit/Tax/Helpers/TaxCest.php @@ -0,0 +1,113 @@ +have(TaxRate::class, [ + 'country' => $country, + ]); + $taxCategorie1 = $I->have(TaxCategory::class); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax1->id, + 'tax_category_id' => $taxCategorie1->id, + ]); + + $tax2 = $I->have(TaxRate::class, [ + 'country' => $country, + ]); + $taxCategorie2 = $I->have(TaxCategory::class); + $I->have(TaxMap::class, [ + 'tax_rate_id' => $tax2->id, + 'tax_category_id' => $taxCategorie2->id, + ]); + + $config1 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie1->id, + ], + ]; + $product1 = $I->haveProduct(\Webkul\Core\Helpers\Laravel5Helper::SIMPLE_PRODUCT, $config1); + + $config2 = [ + 'productInventory' => ['qty' => 100], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'tax_category_id' => $taxCategorie2->id, + ], + ]; + $product2 = $I->haveProduct(\Webkul\Core\Helpers\Laravel5Helper::SIMPLE_PRODUCT, $config2); + + Cart::addProduct($product1->id, [ + '_token' => session('_token'), + 'product_id' => $product1->id, + 'quantity' => 11, + ]); + + Cart::addProduct($product2->id, [ + '_token' => session('_token'), + 'product_id' => $product2->id, + 'quantity' => 7, + ]); + + + $this->scenario = [ + 'object' => Cart::getCart(), + 'expectedTaxRates' => [ + (string)round((float)$tax1->tax_rate, \Webkul\Tax\Helpers\Tax::TAX_PRECISION) + => round(11 * $product1->price * $tax1->tax_rate / 100, 4), + + (string)round((float)$tax2->tax_rate, \Webkul\Tax\Helpers\Tax::TAX_PRECISION) + => round(7 * $product2->price * $tax2->tax_rate / 100, 4), + ], + 'expectedTaxTotal' => + round( + round(11 * $product1->price * $tax1->tax_rate / 100, 2) + + round(7 * $product2->price * $tax2->tax_rate / 100, 2) + , 2), + ]; + } + + public function testGetTaxRatesWithAmount(UnitTester $I) + { + $result = $I->executeFunction( + \Webkul\Tax\Helpers\Tax::class, + 'getTaxRatesWithAmount', + [$this->scenario['object'], false] + ); + + foreach ($result as $taxRate => $taxAmount) { + $I->assertTrue(array_key_exists($taxRate, $result)); + $I->assertEquals($this->scenario['expectedTaxRates'][$taxRate], $taxAmount); + } + } + + public function testGetTaxTotal(UnitTester $I) + { + $result = $I->executeFunction( + \Webkul\Tax\Helpers\Tax::class, + 'getTaxTotal', + [$this->scenario['object'], false] + ); + + $I->assertEquals($this->scenario['expectedTaxTotal'], $result); + } +}