File: D:/HostingSpaces/SBogers10/shop.komma.nl/app/Checkout/CheckoutController.php
<?php
namespace App\Checkout;
use App\Addresses\AddressResource;
use App\Addresses\Models\Address;
use App\Base\Controller;
use App\Addresses\AddressService;
use App\Cart\HasCouponCodesInterface;
use App\Cart\Requests\AddCouponRequest;
use App\Cart\Requests\RemoveCouponRequest;
use App\Cart\Resources\ShoppingCartItemResource;
use App\Cart\ShoppingCartInterface;
use App\Cart\ShoppingCartItem;
use App\Cart\ShoppingCart;
use App\Checkout\Requests\UserDetailsFormRequest;
use App\Checkout\Resources\CheckoutInformationResource;
use App\Discounts\DiscountService;
use App\Discounts\DiscountTypes;
use App\Finance\RoundingService;
use App\Orders\Kms\OrderMailService;
use App\ShippingCosts\Resources\ShippingCostsResource;
use App\Users\Resources\SiteUserResource;
use App\Users\SiteUser;
use App\Users\SiteUserInterface;
use App\Vat\VatRateTotal;
use App\Vat\VatService;
use App\ShippingCosts\ShippingCostsService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;
use Komma\KMS\Globalization\RegionInfo;
use Komma\KMS\Globalization\RegionInfoInterface;
/**
* Handles the checkout process and the different views
*
* Class ShoppingCart
* @package App\Cart
*/
class CheckoutController extends Controller
{
private OrderMailService $orderMailService;
private AddressService $addressService;
private RegionInfoInterface $regionInfo;
private VatService $vatService;
/**
* CheckoutController constructor.
*/
public function __construct()
{
if(\App::runningInConsole()) return;
$this->orderMailService = new OrderMailService();
$this->addressService = app(AddressService::class);
$this->vatService = new VatService();
$this->regionInfo = app(RegionInfoInterface::class);
parent::__construct();
}
/**
* Start the checkout process.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function startCheckout()
{
if(auth()->guard('site')->check()) {
//Authenticated, may proceed to confirmation page after loading some data
$checkoutService = new CheckoutService();
/** @var SiteUser $user */
$user = auth()->guard('site')->user();
$addressForInvoice = $user->invoiceAddress()->first();
$addressForShipping = $user->shippingAddress()->first();
//Give addresses and user to the checkout service, so they will be persisted between requests.
$checkoutService->setInvoiceAddress($addressForInvoice);
$checkoutService->setShippingAddress($addressForShipping);
$checkoutService->setUser($user);
return redirect()->to(localized_route('checkout.confirm'));
} else {
//Not authenticated, user must enter user details first, so we redirect him to a form in which he can do so.
return redirect()->to(localized_route('checkout.user_details'));
}
}
/**
* Shows an address form in which the user can enter his details
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|View
*/
public function showUserDetailsForm(Request $request) {
$checkoutService = new CheckoutService();
/** @var ShoppingCartInterface $shoppingCart */
$shoppingCart = app(ShoppingCartInterface::class);
$user = $checkoutService->getUser();
$shippingAddress = $checkoutService->getShippingAddress();
$neutralCulturesByIso3 = RegionInfo::getNeutralCultures()->map(function(RegionInfo $regionInfo) {
return [$regionInfo->getThreeLetterISORegionName() => $regionInfo->getNativeName() !== '' ? $regionInfo->getNativeName() : $regionInfo->getDisplayName()];
})->collapse()->sort();
$neutralCulturesHotList = RegionInfo::getNeutralCultures()->filter(function(RegionInfo $regionInfo) {
return in_array($regionInfo->getThreeLetterISORegionName(), config('shop.country_hotlist', []), true);
})->map(function(RegionInfo $regionInfo) use(&$neutralCulturesByIso3) {
if(isset($neutralCulturesByIso3[$regionInfo->getThreeLetterISORegionName()])) unset($neutralCulturesByIso3[$regionInfo->getThreeLetterISORegionName()]); //Remove the item from the regular cultures list
return [$regionInfo->getThreeLetterISORegionName() => $regionInfo->getNativeName() !== '' ? $regionInfo->getNativeName() : $regionInfo->getDisplayName()];
})->collapse()->sort();
return $this->addressFormView()->with([
// 'addresses' => $addresses,
'lastUsedShippingAddress' => $shippingAddress,
'lastUsedInvoiceAddress' => $shippingAddress,
'shippingAddress' => $shippingAddress,
'items' => $shoppingCart->getItems(),
'checkoutInformation' => $this->getCheckoutInformation(),
'user' => $user,
'regionInfo' => $this->regionInfo,
'links' => $this->links,
'culturesForSelectByIso3' => $neutralCulturesHotList->merge($neutralCulturesByIso3),
'neutralCulturesByIso3' => $neutralCulturesByIso3,
'neutralCulturesHotlistByIso3' => $neutralCulturesHotList,
'cartRoute' => localized_route('shoppingcart'),
]);
}
/**
* Saves the user details for the user in the session.
* When it has done so, it returns a form for the user to confirm the checkout process
*
* @param UserDetailsFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function saveUserDetailsForm(UserDetailsFormRequest $request) {
$addressForInvoice = $this->addressService->getAddressForInvoiceFromInput();
$addressForShipping = $this->addressService->getAddressForShippingFromInput();
if($request->get('shipping-address-equals')) {
$addressForShipping = (new Address($addressForInvoice->getAttributes()));
}
$checkoutService = new CheckoutService();
$checkoutService->setInvoiceAddress($addressForInvoice);
$checkoutService->setShippingAddress($addressForShipping);
$checkoutService->setUser($checkoutService->getUserFromInput());
return redirect()->to(localized_route('checkout.confirm'));
}
/**
* Saves the shipping address for the user in the session
*/
public function setShippingAddress() {
$addressForShipping = $this->addressService->getAddressForShippingFromInput();
/** @var CheckoutService $checkoutService */
$checkoutService = new CheckoutService();
$checkoutService->setShippingAddress($addressForShipping);
return $checkoutService->getShippingAddress();
}
/**
* Shows the form in which the user can see an overview of what he is about to order
* along with his address details etc/
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|View
*/
public function showConfirmCheckoutForm() {
//Initalize some services. We cannot load them in the constructors since the session won't yet be available there.
$checkoutService = new CheckoutService();
/** @var ShippingCostsService $shippingCostsService */
$shippingCostsService = app(ShippingCostsService::class);
$shoppingCart = app(ShoppingCartInterface::class);
$discountService = new DiscountService();
//Get the user that is used in the checkout process. Remember It may be a guest user.
$user = $checkoutService->getUser();
$data = [];
//Get the shipping address for the user
$shippingAddress = $checkoutService->getShippingAddress();
if(!$shippingAddress && $user) $shippingAddress = $checkoutService->getLastUsedShippingAddressForUser($user);
//Re-calculate the shipping costs
$shippingCosts = $shippingCostsService->get($shoppingCart, $shippingAddress);
$this->vatService->calculateVatForModelWithVatScenarioEnum($shippingCosts);
$discountService->applyDiscountsTo($shoppingCart);
$subtotalInc = 0;
$subtotalEx = 0;
foreach($shoppingCart->getItems() as $item) {
$discountService->applyDiscountsTo($item);
$this->vatService->calculateVatForModelWithVatScenarioEnum($item);
$subtotalInc += $item->getPriceInc();
$subtotalEx += $item->getPriceEx();
}
//Get the vat rate totals collection from the shopping cart
$vatRateTotals = $checkoutService->getVatRateTotalsFromShoppingCart($shoppingCart);
//Add the vat amount of the shipping costs to the vat amount of the highest VatRateTotal.
$highestVatRateTotal = $checkoutService->getVatRateTotalHavingTheHighestVatPercentage($vatRateTotals);
if($highestVatRateTotal) {
/** @var VatRateTotal $highestVatRateTotal */
$highestVatRateTotal->setVatTotal(RoundingService::RoundVat($highestVatRateTotal->getVatTotal() + $shippingCosts->getVatAmount()));
}
$data['shippingAddress'] = $checkoutService->getShippingAddress();
$data['invoiceAddress'] = $checkoutService->getInvoiceAddress();
$data['user'] = $checkoutService->getUser();
$data['checkoutRoute'] = localized_route('checkout.user_details');
$data['subtotal'] = $subtotalInc;
$data['total'] = $subtotalInc + $shippingCosts->getPriceInc();
$data['shippingCosts'] = $shippingCosts;
$data['vatRateTotals'] = $vatRateTotals;
$data['shoppingCartItems'] = $shoppingCart->getItems();
$data['regionInfo'] = app(RegionInfoInterface::class);
$data['links'] = $this->links;
return view('templates.confirmation', $data);
}
/**
* Triggered in response of a push on a checkout button
*
* @param ProceedToPaymentRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function proceedToPayment(ProceedToPaymentRequest $request)
{
/** @var CheckoutService $checkoutService */
$checkoutService = new CheckoutService();
//Check if the checkout session does not has data.
//In that case we redirect the user back to the cart. Because the order most likely was handed over to the payment service provider and an order was created.
if(!$checkoutService->hasSession()) return redirect()->to(localized_route('shoppingcart'))->withErrors([__('checkout.payment_request_already_handed_off')]);
/** @var SiteUserInterface $user */
$user = $checkoutService->getUser();
//If there is a previously used user and this user is a guest, update that with the data from the user from the session.
if($prevUser = $checkoutService->previouslyUsedUser($user))
$user = $checkoutService->updatePreviouslyUsedUserIfGuest($prevUser, $user);
//Make sure the user is saved. At this point it can be a new guest user, or the existing one which is updated or not.
$user->save();
/** @var ShoppingCartInterface|HasCouponCodesInterface $shoppingCart */
$shoppingCart = app(ShoppingCartInterface::class);
/** @var CheckoutService $checkoutService */
$checkoutService = new CheckoutService();
//Create addresses for shipping and invoice.
$addressForInvoice = $checkoutService->getInvoiceAddress();
$addressForShipping = $checkoutService->getShippingAddress();
//Make sure the names on the shipping and invoice addresses are the one of the user.
$addressForInvoice->first_name = $addressForShipping->first_name = $user->first_name;
$addressForInvoice->last_name_prefix = $addressForShipping->last_name_prefix = $user->last_name_prefix;
$addressForInvoice->last_name = $addressForShipping->last_name = $user->last_name;
//We only store the address for guests in the order themselves. Not as a separate address
if(auth()->guard('site')->check()) {
// if(!$user->is_guest) {
$addressForInvoice = $this->addressService->saveAddressForUser($addressForInvoice, $user);
if($addressForInvoice === $addressForShipping) $addressForShipping = $addressForInvoice;
$addressForShipping = $this->addressService->saveAddressForUser($addressForShipping, $user);
}
/** @var ShoppingCartItem[] $items */
$order = $checkoutService->createOrder($shoppingCart, $user, $addressForShipping, $addressForInvoice);
//Add remarks to the order if any where given
if($request->has('remarks')) {
$order->remarks = $request->get('remarks');
$order->save();
}
//Clear the checkout session data (shipping address and guest user if available)
$checkoutService->clearCheckoutData();
$shoppingCart->clear(); //Clear the shopping cart
return $checkoutService->startPaymentForOrder($order);
}
/**
* Returns info about checkout totals. Used for ajax / api requests
*
* @return CheckoutInformationResource
*/
public function getCheckoutInformation(): CheckoutInformationResource
{
//Initialize some services which cannot be initialized in the construction phase. Because session isn't available in there.
/** @var CheckoutService $checkoutService */
$checkoutService = new CheckoutService();
/** @var ShoppingCartInterface|HasCouponCodesInterface $shoppingCart */
$shoppingCart = app(ShoppingCartInterface::class);
/** @var ShippingCostsService $shippingCostsService */
$shippingCostsService = app(ShippingCostsService::class);
$discountService = new DiscountService();
//Get the user if available already.
$user = $checkoutService->getUser();
//Get the users addresses
$invoiceAddress = $checkoutService->getInvoiceAddress();
$shippingAddress = $checkoutService->getShippingAddress();
//Re-calculate the shipping costs
$shippingCosts = $shippingCostsService->get($shoppingCart, $shippingAddress);
$this->vatService->calculateVatForModelWithVatScenarioEnum($shippingCosts);
$discountService->applyDiscountsTo($shoppingCart);
//Re-calculate the total inc and ex vat price of the shopping cart
$subtotalInc = 0;
$subtotalEx = 0;
foreach($shoppingCart->getItems() as $item) {
$discountService->applyDiscountsTo($item);
$this->vatService->calculateVatForModelWithVatScenarioEnum($item);
$subtotalInc += $item->getPriceInc();
$subtotalEx += $item->getPriceEx();
}
$cartPriceModifications = $shoppingCart->getDiscountPriceMutations();
$subtotalWithDiscountsEx = $subtotalEx;
$subtotalWithDiscountsInc = $subtotalInc;
foreach($cartPriceModifications as $cartPriceModification) {
$subtotalWithDiscountsEx += $cartPriceModification->getPriceEx();
$subtotalWithDiscountsInc += $cartPriceModification->getPriceInc();
}
//Get the vat rate totals collection
$vatRateTotals = $checkoutService->getVatRateTotalsFromShoppingCart($shoppingCart);
//Add the vat amount of the shipping costs to the vat amount of the highest VatRateTotal.
$highestVatRateTotal = $checkoutService->getVatRateTotalHavingTheHighestVatPercentage($vatRateTotals);
if($highestVatRateTotal) {
/** @var VatRateTotal $highestVatRateTotal */
$highestVatRateTotal->setVatTotal(RoundingService::RoundVat($highestVatRateTotal->getVatTotal() + $shippingCosts->getVatAmount()));
}
//Determine the continue shopping route
$continueShoppingRoute = '';
if(property_exists($this->links, 'categories')) $continueShoppingRoute = url($this->links->categories->route) ?? url();
//Prepare a resource, that will be used as the (json) response.
$checkoutInformationResource = new CheckoutInformationResource();
$checkoutInformationResource->couponCodes = $shoppingCart->getCouponCodes();
$checkoutInformationResource->subtotalInc = $subtotalInc;
$checkoutInformationResource->subtotalEx = $subtotalEx;
$checkoutInformationResource->subtotalWithDiscountsInc = $subtotalWithDiscountsInc;
$checkoutInformationResource->subtotalWithDiscountsEx = $subtotalWithDiscountsEx;
$checkoutInformationResource->vatRateTotals = $vatRateTotals; //Warning: Vat Rate totals represent the vat rate totals of producs
$checkoutInformationResource->shippingCosts = new ShippingCostsResource($shippingCosts);
$checkoutInformationResource->shippingCostsVatAmount = RoundingService::Round($shippingCosts->getVatAmount());
$checkoutInformationResource->shoppingCartItems = ShoppingCartItemResource::collection($shoppingCart->getItems());
$checkoutInformationResource->totalItemsCount = $shoppingCart->getItemsCount();
$checkoutInformationResource->cartRoute = localized_route('shoppingcart');
$checkoutInformationResource->startCheckoutRoute = route('checkout.start');
$checkoutInformationResource->editDetailsRoute = auth()->guard('site')->check() ? localized_route('my_account') : localized_route('checkout.user_details');
$checkoutInformationResource->startPaymentRoute = route('checkout.start_payment');
$checkoutInformationResource->continueShoppingRoute = $continueShoppingRoute;
$checkoutInformationResource->shippingAddress = new AddressResource($shippingAddress);
$checkoutInformationResource->invoiceAddress = new AddressResource($invoiceAddress);
$checkoutInformationResource->discountPriceMutations = $shoppingCart->getDiscountPriceMutations();
$checkoutInformationResource->user = $user;
return $checkoutInformationResource;
}
public function makeUsersAddressTheCheckoutAddresses() {
$checkoutService = new CheckoutService();
/** @var SiteUser $user */
$user = auth()->guard('site')->user();
$checkoutService->setInvoiceAddress($user->invoiceAddress()->first());
$checkoutService->setShippingAddress($user->shippingAddress()->first());
}
/**
* @param Request $request
* @param Address $address
*/
public function markAddressAsInvoiceAddress(Request $request, Address $address) {
$this->authorizeForUser(\Auth::guard('site')->user(),'mark_as_invoice_address_for_checkout', $address);
$checkoutService = new CheckoutService();
$checkoutService->setInvoiceAddress($address);
}
/**
* @param Request $request
* @param Address $address
*/
public function markAddressAsShippingAddress(Request $request, Address $address) {
$this->authorizeForUser(\Auth::guard('site')->user(), 'mark_as_shipping_address_for_checkout', $address);
$checkoutService = new CheckoutService();
$checkoutService->setShippingAddress($address);
}
public function makeUserTheCheckoutUser() {
$checkoutService = new CheckoutService();
$user = auth()->guard('site')->user();
$checkoutService->setUser($user);
return new SiteUserResource($checkoutService->getUser());
}
/**
* Adds a coupon code via an ajax request
*
* @param AddCouponRequest $request
*
* @return CheckoutInformationResource|\Illuminate\Http\JsonResponse
*/
public function addCouponCode(AddCouponRequest $request)
{
/** @var ShoppingCartInterface|HasCouponCodesInterface $shoppingCart */
$shoppingCart = app(ShoppingCartInterface::class);
$couponCodesBeforeAddedNewOne = $shoppingCart->getDiscountsByType(DiscountTypes::Coupon);
$shoppingCart->addCouponCode($request->get('coupon'));
$couponCodesAfterAddedNewOne = $shoppingCart->getDiscountsByType(DiscountTypes::Coupon);
if(count($couponCodesAfterAddedNewOne) == count($couponCodesBeforeAddedNewOne)) { //If the amount of valid coupon codes is the same as before we tried to add one, the coupon code is invalid. Else if would be added.
$shoppingCart->removeCouponCode($request->get('coupon'));
return response()->json([
'message' => 'Coupon code invalid',
'errors' => [
'coupon_code' => [__('shop/discounts.coupon_fail')]
]
], 400);
}
$request->flash();
return $this->getCheckoutInformation();
}
/**
* Removes an coupon code via an ajax request
*
* @param RemoveCouponRequest $request
* @return CheckoutInformationResource
*/
public function removeCouponCode(RemoveCouponRequest $request)
{
/** @var ShoppingCartInterface|HasCouponCodesInterface $shoppingCart */
$shoppingCart = app(ShoppingCartInterface::class);
$shoppingCart->removeCouponCode($request->get('coupon'));
$request->flash();
return $this->getCheckoutInformation();
}
/**
* @return View
*/
public function index()
{
return $this->addressFormView();
}
/**
* @return View
*/
protected function addressFormView(): View
{
return view('templates.checkout')->with('links', $this->links);
}
public function checkoutUser(Request $request) {
if(!$request->ajax()) return response('Only ajax requests please', Response::HTTP_NOT_IMPLEMENTED);
$checkoutService = new CheckoutService();
$siteUser = $checkoutService->getUser();
if($siteUser) return new SiteUserResource($siteUser);
return response(null, Response::HTTP_NO_CONTENT);
}
}