File: D:/HostingSpaces/SBogers10/blije-gasten.komma.pro/app/Komma/Shop/Cart/ShoppingCartService.php
<?php
namespace App\Komma\Shop\Cart;
use App\Komma\Session\HasSessionTrait;
use App\Komma\Shop\Discounts\Actions\ModifyPriceAction;
use App\Komma\Shop\Discounts\DiscountableInterface;
use App\Komma\Shop\Discounts\Discount;
use App\Komma\Shop\Discounts\DiscountServiceInterface;
use App\Komma\Shop\Discounts\DiscountTypes;
use App\Komma\Shop\Products\Product\Product;
use App\Komma\Shop\Products\ProductableInterface;
use App\Komma\Shop\Vat\VatServiceInterface;
use App\Komma\Zipcodes\ZipcodeService;
use Carbon\Carbon;
use Illuminate\Http\Request;
/**
* Represents a shopping cart
*
* Class ShoppingCartService
* @package App\Komma\Shop\Cart
*/
class ShoppingCartService implements ShoppingCartServiceInterface, HasCouponCodesInterface
{
use HasSessionTrait;
use HasCouponCodesTrait {
addCouponCode as traitAddCouponCode;
removeCouponCode as traitRemoveCouponCode;
}
/** @var array The names of variables in this class to store and restore. */
protected $variablesForSession = [
'discounts',
'couponCodes',
'deliveryMethod',
'deliveryLocation',
'items',
'date',
];
/**
* Indicates whether or not if the ShoppingCartService restores all items in the shopping cart by looking at the session variable
*
* @var bool
*/
public static $restoreFromSession = true;
/**
* @var ShoppingCartItemInterface[] $items
*/
private $items = [];
/**
* @var Discount[] $discounts
*/
private $discounts = [];
private $deliveryLocation;
private $deliveryMethod;
/** @var Carbon $date */
private $date;
public function __construct()
{
$this->discountService = app(DiscountServiceInterface::class);
$this->restoreFromSession();
}
/**
* Add a productable in the cart
*
* @param ProductableInterface $productable
* @param int $quantity
* @return ShoppingCartItem
*/
public function addProductable(ProductableInterface $productable, int $quantity = 1) : ?ShoppingCartItemInterface
{
/** @var Product $product */
if(!$productable) return null;
/** @var ShoppingCartItemInterface $shoppingCartItem */
$item = $this->getShoppingCartItemByProductableIdAndType($productable);
if($item) {
$item->setQuantity($item->getQuantity() + $quantity);
} else {
/** @var ShoppingCartItem $shoppingCartItem */
$shoppingCartItem = (app(ShoppingCartItemInterface::class));
$item = $shoppingCartItem->setProductable($productable)->setQuantity($quantity);
$this->items[] = $item;
}
$this->updateDiscounts();
$this->saveSession();
return $item;
}
/**
* Returns an existing shoppingCartItem by checking if it has a productable with a
* certain id or false if there was no shoppingcart item with a
* productable with the specified productable id
* @param $productable
* @return ShoppingCartItemInterface|false
*/
private function getShoppingCartItemByProductableIdAndType(ProductableInterface $productable)
{
foreach($this->items as $item)
{
$itemProductable = $item->getProductable();
/** @var ShoppingCartItemInterface $item */
if($itemProductable->id == $productable->id && get_class($productable) === get_class($itemProductable)) return $item;
}
return false;
}
/**
* Set the amount of items for a product
*
* @param int $itemId
* @param int $quantity
* @return $this
*/
public function setItemQuantity(int $itemId, int $quantity = 1): ShoppingCartServiceInterface
{
$item = $this->getItemById($itemId);
if(!$item) return $this;
$item->setQuantity($quantity);
$this->updateDiscounts();
$this->saveSession();
return $this;
}
/**
* Deletes the ShoppingCartItem that has a product with the specified id
*
* @param int $itemId
* @return ShoppingCartServiceInterface
*/
public function deleteItem(int $itemId) : ShoppingCartServiceInterface
{
foreach($this->items as $index => $item)
{
// if($item->getProductable()->id == $productable->id)
// {
// array_splice($this->items, $index, 1);
// break;
// }
if ($item->getId() == $itemId) {
array_splice($this->items, $index, 1);
break;
}
}
$this->updateDiscounts();
$this->saveSession();
return $this;
}
/**
* Clears the shopping cart of all items
*
* @return ShoppingCartServiceInterface
*/
public function clear(): ShoppingCartServiceInterface
{
$this->items = [];
$this->updateDiscounts();
$this->saveSession();
return $this;
}
/**
* Get sum of the prices of the products in the shopping cart
*
* @return float
*/
public function getProductTotal(): float
{
$price = 0;
foreach ($this->items as $index => $item) {
$price += $item->getTotal();
}
return $price;
}
/**
* Get the product total and apply the discounts on it.
* Then add the shipping cost to the price.
* Remember the base price is in cents. And may be a float (e.g. value in cents with decimals)
*
* @return float
*/
public function getTotal(): float
{
$price = $this->getProductTotal();
foreach($this->discounts as $discount)
{
$action = $discount->getDiscountAction();
switch (get_class($action))
{
case ModifyPriceAction::class;
$price = $action->do($price);
}
}
if($this->getShippingCost() != 0) {
$price += $this->getShippingCost();
}
return $price;
}
/**
* Reverse calculated the vat
*
* @return float
*/
public function getTotalWithoutVat(): float
{
// Product vat
$vat = $this->getProductTotal() / (100 + config('shop.vat_percentage')) * 100;
// Shipping vat
$shippingVat = $this->getShippingCost() / (100 + config('shop.vat_percentage')) * 100;
// Round the total of the 2 combined
return round($vat + $shippingVat);
}
public function getShippingCost()
{
if(empty($this->deliveryLocation)) return 0;
/** @var ZipcodeService $zipcodeService */
$zipcodeService = app()->make(ZipcodeService::class);
if(empty($this->deliveryLocation->country) || empty($this->deliveryLocation->postal_code)) return null;
$zipcodes = $zipcodeService->getZipcodeByCountryAndZipcodePart($this->deliveryLocation->country, $this->deliveryLocation->postal_code);
if($zipcodes->count() == 0 || $zipcodes->count() > 1) return null;
return $zipcodes->first()->price;
}
/**
* Get the deposit amount of the shopping cart
*
* @return float
*/
public function getDeposit(): float
{
return round($this->getProductTotal() * (config('shop.deposit_rate_percentage') / 100));
}
/**
* Get the total of the shopping cart with the deposit rate.
*
* @return float
*/
public function getTotalIncludingDeposit(): float
{
return $this->getTotal() + $this->getDeposit();
}
/**
* Get the amount of vat from the shopping cart
*
* @return float
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function getVat(): float
{
// Product vat
$vat = $this->getProductTotal() / (100 + config('shop.vat_percentage')) * config('shop.vat_percentage');
// Shipping vat
$shippingVat = $this->getShippingCost() / (100 + config('shop.vat_percentage')) * config('shop.vat_percentage');
// Round the total of the 2 combined
return round($vat + $shippingVat);
}
/**
* Returns the amount of items in the cart.
* If you specify the $unique argument as true it returns the number of ShoppingCartItems.
* If it is false it will return the sum of all products in all Shopping cart items
* If you specify the $includeDiscounts argument as true it takes the Discount into account
* @param bool $unique
* @param bool $includeDiscounts
* @return int
*/
public function getItemsCount(bool $unique = false, bool $includeDiscounts = false): int
{
if($includeDiscounts === false) {
$itemCount = 0;
if($unique == true) {
$products = [];
foreach($this->items as $shoppingCartItem)
{
/** @var $shoppingCartItem ShoppingCartItem */
$productClass = get_class($shoppingCartItem->getProductable());
if(!in_array($productClass, $products))
{
$itemCount++;
$products[] = $productClass;
}
}
} else {
foreach($this->items as $item) $itemCount = $itemCount + $item->getQuantity();
}
return $itemCount;
} else {
//TODO Implement me
return 0;
}
}
/**
* Get all shopping cart items
*
* @return array
*/
public function getItems(): array
{
return !empty($this->items) ? $this->items : [];
}
/**
* Applies the discount to itself
*
* @param Discount $discount
* @return DiscountableInterface
*/
public function applyDiscount(Discount $discount): DiscountableInterface
{
$this->discounts[] = $discount;
$this->saveSession();
return $this;
}
/**
* Returns all discounts applied
*
* @return Discount[]
*/
public function getDiscounts(): array
{
return $this->discounts;
}
/**
* Returns all discounts applied, from a certain type
*
* @param null $type
* @return Discount[]
*/
public function getDiscountsByType($type = null): array
{
if($type == null) return $this->discounts;
if(!DiscountTypes::isValidType($type)) throw new \InvalidArgumentException('The given type is not a valid DiscountType.');
return array_filter($this->discounts, function(Discount $discount) use ($type) {
return $discount->type == $type;
});
}
/**
* Get a shoppingCartItem by ID
*
* @param $itemId int
* @return ShoppingCartItemInterface
*/
private function getItemById(int $itemId): ?ShoppingCartItemInterface
{
$returnItem = null;
foreach($this->items as $index => $item) {
if ($item->getId() == $itemId) {
$returnItem = $item;
}
}
return $returnItem;
}
/**
* Validates a coupon code
*
* @param string $code
* @return bool
*/
public function validateCouponCode(string $code): bool
{
return $this->discountService->checkIfCouponCodeCanBeApplied($code, $this);
}
/**
* Removes all discounts
*/
public function clearDiscounts(): void
{
$this->discounts = [];
foreach($this->items as $shoppingCartItem) $shoppingCartItem->clearDiscounts();
}
/**
* Updates the discounts on the shopping cart and all shoppingCartItems after removing all discounts from them.
*/
public function updateDiscounts(): void
{
$this->clearDiscounts();
$this->discountService->updateDiscounts($this);
foreach($this->items as $shoppingCartItem) $this->discountService->updateDiscounts($shoppingCartItem);
}
/**
* Adds a coupon code. Notice that this does not mean that it is valid.
* Validation is done via the DiscountServiceInterface. Returns true if
* it was added. false if not
*
* @param string $code
* @return mixed
*/
public function addCouponCode(string $code): bool
{
$result = $this->traitAddCouponCode($code);
$this->updateDiscounts();
$this->saveSession();
return $result;
}
/**
* Removes a coupon code and returns true if it was removed and false if not.
*
* @param string $code
* @return bool
*/
public function removeCouponCode(string $code): bool
{
$result = $this->traitRemoveCouponCode($code);
$this->updateDiscounts();
$this->saveSession();
return $result;
}
/**
* Append the delivery location to session.
*/
public function setDeliveryLocation($city, $postal_code, $country)
{
$this->deliveryLocation = (object)[
'city' => $city,
'postal_code' => $postal_code,
'country' => $country
];
$this->saveSession();
}
/**
* Get the delivery location from the shopping cart
*
* @return mixed
*/
public function getDeliveryLocation()
{
return $this->deliveryLocation;
}
/**
* Clear the delivery location
*/
public function removeDeliveryLocation()
{
$this->deliveryLocation = null;
$this->saveSession();
}
/**
* Set the delivery method
*
* @param string $deliveryMethod
*/
public function setDeliveryMethod(string $deliveryMethod)
{
$this->deliveryMethod = $deliveryMethod;
$this->saveSession();
}
/**
* Get the delivery method
*
* @return mixed
*/
public function getDeliveryMethod()
{
return $this->deliveryMethod;
}
/**
* Set the delivery method
*
* @param string $date
*/
public function setDate(string $date)
{
$dateFormat = Carbon::createFromFormat('d-m-Y', $date);
$dateFormat->startOfDay();
$this->date = $dateFormat;
$this->saveSession();
}
/**
* @return Carbon
*/
public function getDate()
{
return $this->date;
}
/**
* Will be using shipping
*
* @return bool
*/
public function useShipping(): bool
{
if($this->deliveryMethod == 'pickup') return false;
return true;
}
/**
* Determine if we have a product that is marked as a bouncy castle
*
* @return bool
*/
public function containsBouncyCastle(): bool
{
foreach ($this->items as $item) {
$product = $item->getProductable();
if($product->categories->isEmpty()) continue;
if($product->categories->contains('code_name', 'bouncy_castle')) return true;
}
return false;
}
/**
* Update the delivery location from the given request
*
* @param Request $request
*/
public function updateDeliveryLocationFromRequest(Request $request)
{
$this->setDeliveryMethod($request->get('pickup_or_ship'));
// If pickup we remove the city, postal and country
if($this->deliveryMethod == 'pickup') {
foreach (['city', 'postal_code','country'] as $requestKey) {
$request->request->set('invoice_'.$requestKey, null);
}
$this->removeDeliveryLocation();
}
else {
$deliveryLocation = (object)[];
foreach (['city', 'postal_code','country'] as $requestKey) {
// Also populate the shipping postal, city and country from the request.
// Because if the user check a different shipping address, it will be these.
if($request->has('invoice_'.$requestKey)) {
$request->request->add(['shipping_'.$requestKey => $request->get('invoice_'.$requestKey)]);
}
// Store into the delivery location array
$deliveryLocation->{$requestKey} = $request->get('invoice_'.$requestKey);
}
$this->setDeliveryLocation($deliveryLocation->city, $deliveryLocation->postal_code, $deliveryLocation->country);
}
}
}