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();
}
}