HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/farmfun/reserveren.farmfun.be/app/Komma/Shop/Checkout/CheckoutService.php
<?php

namespace App\Komma\Shop\Checkout;

use App\Komma\Addresses\AddressService;
use App\Komma\Addresses\Models\Address;
use App\Komma\Globalization\Language;
use App\Komma\Globalization\RegionInfo;
use App\Komma\Session\HasSessionTrait;
use App\Komma\Shop\Cart\ShoppingCartItemInterface;
use App\Komma\Shop\Cart\ShoppingCartServiceInterface;
use App\Komma\Shop\Orders\InvoiceNumberSequence;
use App\Komma\Shop\Orders\Kms\OrderService;
use App\Komma\Shop\Orders\Models\Order;
use App\Komma\Shop\Orders\OrderNumberSequence;
use App\Komma\Shop\Orders\OrderStatus;
use App\Komma\Shop\Payment\PaymentServiceInterface;
use App\Komma\Shop\Products\Product\Product;
use App\Komma\Shop\Products\Product\ProductModelService;
use App\Komma\Shop\Products\ProductComposite\ProductComposite;
use App\Komma\Shop\Products\ProductComposite\ProductCompositeModelService;
use App\Komma\Shop\Products\ProductGroup\ProductGroup;
use App\Komma\Shop\Products\ProductGroup\ProductGroupModelService;
use App\Komma\Shop\ShippingCosts\ShippingCostsService;
use App\Komma\Shop\Vat\VatService;
use App\Komma\Users\Models\SiteUser;
use App\Komma\Users\Models\SiteUserRole;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

/**
 * Class CheckoutService
 *
 * The checkout service sits between Order related, product related and payment related code.
 * It directs those domains to work together.
 */
class CheckoutService implements CheckoutServiceInterface
{
    use HasSessionTrait;

    /** @var OrderService */
    private $orderService;

    /** @var ProductCompositeModelService */
    private $productCompositeService;

    /** @var PaymentServiceInterface */
    private $paymentService;

    /** @var AddressService */
    private $addressService;

    /** @var OrderNumberSequence */
    private $orderNumberGenerator;

    /** @var ProductGroupModelService */
    private $productGroupService;

    /** @var ProductModelService */
    private $productService;

    /** @var InvoiceNumberSequence */
    private $invoiceNumberGenerator;

    /** @var Address */
    private $shippingAddress;

    /** @var SiteUser */
    private $guestUser;

    /** @var array The names of variables in this class to store and restore. */
    protected $variablesForSession = [
        'shippingAddress',
        'guestUser',
    ];

    /**
     * @var VatService
     */
    private $rateService;

    public function __construct()
    {
        $this->orderService = app(OrderService::class);
        $this->productService = new ProductModelService();
        $this->productGroupService = new ProductGroupModelService();
        $this->productCompositeService = new ProductCompositeModelService();
        $this->paymentService = app(PaymentServiceInterface::class);
        $this->addressService = new AddressService();
        $this->orderNumberGenerator = app(OrderNumberSequence::class);
        $this->invoiceNumberGenerator = app(InvoiceNumberSequence::class);
        $this->rateService = new VatService(); //TODO Create interface
        $this->shippingAddress = null;
        $this->restoreFromSession();
    }

    /**
     * Initializes a payment request for the user that owns the order.
     *
     * @param Order $order
     * @return RedirectResponse
     */
    public function startPaymentForOrder(Order $order): RedirectResponse
    {
        $pspAdapter = $this->paymentService->getAdapter();
        $transaction = $pspAdapter->createTransaction($order);

        return $pspAdapter->redirectForPayment($transaction);
    }

    /**
     * @param ShoppingCartServiceInterface $shoppingCartService
     * @param SiteUser $customer
     * @param Address $addressForShipping
     * @param Address $addressForInvoice
     * @return Order|null
     */
    public function createOrder(ShoppingCartServiceInterface $shoppingCartService, SiteUser $customer, Address $addressForShipping, Address $addressForInvoice): ?Order
    {
        $order = null;
        \DB::transaction(function () use ($shoppingCartService, $customer, $addressForShipping, $addressForInvoice, &$order) {
            //Create the order, link customer and addresses to it and save it. We need to save it because the ordered productables need a order id
            /** @var ShippingCostsService $shippingCostsService */
            $shippingCostsService = app(ShippingCostsService::class);

            /** @var Order $order */
            $order = $this->orderService->newModel();
            $order->status = OrderStatus::NEW;
            $order->order_number = (string) $this->orderNumberGenerator->next();
            $order->invoice_number = (string) $this->invoiceNumberGenerator->next();
            $order->customer()->associate($customer);
            $order = $this->fillOrderWithInvoiceAddressFromAddress($order, $addressForInvoice);
            $order = $this->fillOrderWithShippingAddressFromAddress($order, $addressForShipping);
            $order->save();

            //Loop over all shopping cart items and create ordered products for them. And link them to the order
            foreach ($shoppingCartService->getItems() as $shoppingCartItem) {
                /** @var ShoppingCartItemInterface $shoppingCartItem */
                $productable = $shoppingCartItem->getProductable();

                $quantity = $shoppingCartItem->getQuantity();
                $productableClassName = get_class($productable);

                switch ($productableClassName) {
                    case Product::class:
                        /** @var Product $productable */
                        $this->productService->createOrderedProductFromProduct($productable, $order, $quantity);
                        break;
                    case ProductGroup::class:
                        /** @var ProductGroup $productable */
                        $this->productGroupService->createOrderedProductGroupFromProductGroup($productable, $order, $quantity);
                        break;
                    case ProductComposite::class:
                        /** @var ProductComposite $productable */
                        $this->productCompositeService->createOrderedProductCompositeProductComposite($productable, $order, $quantity);
                        break;
                    default:
                        return null;
                }
            }

            //Calculate the order total.
            $this->orderService->calculateTotalOrderPrice($order);

            //Take the shipping costs into account. And add them to the order total ex vat.
            $order->total_price_ex += $shippingCostsService->calculate($shoppingCartService, $addressForShipping);
            $order->total_price_ex = round($order->total_price_ex, 0, config('financial.cent_rounding.total_ex', PHP_ROUND_HALF_UP)); //And always as the very last step we round fractions of cents.

            //Then calculate the vat total.
            $this->rateService->calculateOrderVatTotal($order);

            //And finaly save it
            $order->save();
        });

        return $order;
    }

    /**
     * @param Order $order
     * @param Address $address
     * @return Order
     */
    private function fillOrderWithShippingAddressFromAddress(Order $order, Address $address): Order
    {
        foreach ($address->getFillable() as $index => $attributeName) {
            $order['shipping_'.$attributeName] = $address->$attributeName;
        }

        return $order;
    }

    /**
     * @param Order $order
     * @param Address $address
     * @return Order
     */
    private function fillOrderWithInvoiceAddressFromAddress(Order $order, Address $address)
    {
        foreach ($address->getFillable() as $index => $attributeName) {
            $order['invoice_'.$attributeName] = $address->$attributeName;
        }

        return $order;
    }

    /**
     * Returns the last used shipping address for user or null if that not exists.
     *
     * @param SiteUser $user
     * @return Address
     */
    public function getLastUsedShippingAddressForUser(SiteUser $user): ? Address
    {
        $addressQuery = $user->addresses();

        /** @var Order $latestOrder */
        $latestOrder = $user->orders()->latest()->first();
        if (! $latestOrder) {
            return null;
        }

        $attributes = $latestOrder->getShippingAddressAttributes();
        foreach ($attributes as $attribute => $value) {
            $addressQuery->where(str_replace('shipping_', '', $attribute), '=', $value);
        }

        return $addressQuery->first();
    }

    /**
     * Returns the last used invoice address for the user or null if that not exists
     *
     * @param SiteUser $user
     * @return Address
     * @see Address
     */
    public function getLastUsedInvoiceAddressForUser(SiteUser $user): ? Address
    {
        $addressQuery = $user->addresses();

        /** @var Order $latestOrder */
        $latestOrder = $user->orders()->latest()->first();
        if (! $latestOrder) {
            return null;
        }

        $attributes = $latestOrder->getInvoiceAddressAttributes();
        foreach ($attributes as $attribute => $value) {
            $addressQuery->where(str_replace('invoice_', '', $attribute), '=', $value);
        }

        return $addressQuery->first();
    }

    /**
     * @return Address
     */
    public function getShippingAddress(): ? Address
    {
        return $this->shippingAddress;
    }

    /**
     * @param Address $shippingAddress
     * @return CheckoutService
     */
    public function setShippingAddress(Address $shippingAddress): CheckoutServiceInterface
    {
        $this->shippingAddress = $shippingAddress;
        $this->saveSession();

        return $this;
    }

    /**
     * Gets the user that checks out.
     * This can be a guest user. Or it can be a authenticated recurring customer.
     *
     * @param bool $updateUserFromRequest Update the user with data from the request if available.
     * @return SiteUser
     */
    public function getUser($updateUserFromRequest = false): SiteUser
    {
        $authUser = Auth::guard('site')->user();
        if ($authUser) {
            return $authUser;
        }

        if (! $this->guestUser) {
            $this->guestUser = new SiteUser();
            $this->guestUser->guest = 1;
            $this->guestUser->role = SiteUserRole::Customer;
            $this->saveSession();
        }

        if ($updateUserFromRequest) {
            //If we know his e-mail address we try to get his previously used guest user. If it does not exist, use the existing guest user.
            if (request()->has('email') && ! $this->guestUser->exists) {
                $previouslyUsedUser = SiteUser::where('email', '=', request()->get('email'))->first();
                if ($previouslyUsedUser) {
                    $this->guestUser = $previouslyUsedUser;
                }
            }

            //Use the requests email, first_name and last_name fields to update the guest user when available.
            if (request()->has('email')) {
                $this->guestUser->email = request()->get('email');
            }
            if (request()->has('first_name')) {
                $this->guestUser->first_name = request()->get('first_name');
            }
            if (request()->has('last_name')) {
                $this->guestUser->last_name = request()->get('last_name');
            }

            //Use the guests country and language to build a culture string when available.
            if (request()->has('country') && request()->has('language')) {
                /** @var RegionInfo|null $usersRegionInfo */
                $usersRegionInfo = null;
                RegionInfo::getWhere('threeLetterISORegionName', '=', request()->get('country'))->each(function (
                    RegionInfo $regionInfo
                ) use (&$usersRegionInfo) {
                    $regionCode = $regionInfo->getName();
                    $languageCode = null;
                    $regionInfo->getLanguages()->each(function (Language $language) use (&$languageCode) {
                        if ($language->getThreeLetterISOLanguageName() == request()->get('language')) {
                            $languageCode = $language->getName();

                            return false; //break
                        }
                    });

                    if ($regionCode && $languageCode) {
                        $regionInfoString = implode('-', [$languageCode, $regionCode]);
                        $usersRegionInfo = RegionInfo::getInstance($regionInfoString);

                        return false; //break;
                    }
                });

                if ($usersRegionInfo) {
                    $this->guestUser->culture = $usersRegionInfo->getName();
                }
            }
        }

        return $this->guestUser;
    }

    /**
     * Clears session variables values.
     */
    public function clearCheckoutData(): void
    {
        $this->clearSession();
    }
}