<?php declare(strict_types=1);namespace Shopware\Storefront\Controller;use Shopware\Core\Checkout\Cart\Cart;use Shopware\Core\Checkout\Cart\Error\Error;use Shopware\Core\Checkout\Cart\Error\ErrorCollection;use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;use Shopware\Core\Checkout\Cart\SalesChannel\CartService;use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;use Shopware\Core\Checkout\Order\Exception\EmptyCartException;use Shopware\Core\Checkout\Order\SalesChannel\OrderService;use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;use Shopware\Core\Checkout\Payment\PaymentService;use Shopware\Core\Framework\Feature;use Shopware\Core\Framework\Log\Package;use Shopware\Core\Framework\Routing\Annotation\RouteScope;use Shopware\Core\Framework\Routing\Annotation\Since;use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;use Shopware\Core\Profiling\Profiler;use Shopware\Core\System\SalesChannel\SalesChannelContext;use Shopware\Core\System\SystemConfig\SystemConfigService;use Shopware\Storefront\Checkout\Cart\Error\PaymentMethodChangedError;use Shopware\Storefront\Checkout\Cart\Error\ShippingMethodChangedError;use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener;use Shopware\Storefront\Framework\Routing\Annotation\NoStore;use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedHook;use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader;use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedHook;use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader;use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedHook;use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader;use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutInfoWidgetLoadedHook;use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutOffcanvasWidgetLoadedHook;use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader;use Symfony\Component\HttpFoundation\RedirectResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpFoundation\Session\SessionInterface;use Symfony\Component\Routing\Annotation\Route;/** * @Route(defaults={"_routeScope"={"storefront"}}) * * @deprecated tag:v6.5.0 - reason:becomes-internal - Will be internal */#[Package('storefront')]class CheckoutController extends StorefrontController{ private const REDIRECTED_FROM_SAME_ROUTE = 'redirected'; private CartService $cartService; private CheckoutCartPageLoader $cartPageLoader; private CheckoutConfirmPageLoader $confirmPageLoader; private CheckoutFinishPageLoader $finishPageLoader; private OrderService $orderService; private PaymentService $paymentService; private OffcanvasCartPageLoader $offcanvasCartPageLoader; private SystemConfigService $config; private AbstractLogoutRoute $logoutRoute; /** * @internal */ public function __construct( CartService $cartService, CheckoutCartPageLoader $cartPageLoader, CheckoutConfirmPageLoader $confirmPageLoader, CheckoutFinishPageLoader $finishPageLoader, OrderService $orderService, PaymentService $paymentService, OffcanvasCartPageLoader $offcanvasCartPageLoader, SystemConfigService $config, AbstractLogoutRoute $logoutRoute ) { $this->cartService = $cartService; $this->cartPageLoader = $cartPageLoader; $this->confirmPageLoader = $confirmPageLoader; $this->finishPageLoader = $finishPageLoader; $this->orderService = $orderService; $this->paymentService = $paymentService; $this->offcanvasCartPageLoader = $offcanvasCartPageLoader; $this->config = $config; $this->logoutRoute = $logoutRoute; } /** * @Since("6.0.0.0") * @NoStore * @Route("/checkout/cart", name="frontend.checkout.cart.page", options={"seo"="false"}, methods={"GET"}) */ public function cartPage(Request $request, SalesChannelContext $context): Response { $page = $this->cartPageLoader->load($request, $context); $cart = $page->getCart(); $cartErrors = $cart->getErrors(); $this->hook(new CheckoutCartPageLoadedHook($page, $context)); $this->addCartErrors($cart); if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) { $cartErrors->clear(); // To prevent redirect loops add the identifier that the request already got redirected from the same origin return $this->redirectToRoute( 'frontend.checkout.cart.page', \array_merge( $request->query->all(), [self::REDIRECTED_FROM_SAME_ROUTE => true] ), ); } $cartErrors->clear(); return $this->renderStorefront('@Storefront/storefront/page/checkout/cart/index.html.twig', ['page' => $page]); } /** * @Since("6.0.0.0") * @NoStore * @Route("/checkout/confirm", name="frontend.checkout.confirm.page", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true}) */ public function confirmPage(Request $request, SalesChannelContext $context): Response { if (!$context->getCustomer()) { return $this->redirectToRoute('frontend.checkout.register.page'); } if ($this->cartService->getCart($context->getToken(), $context)->getLineItems()->count() === 0) { return $this->redirectToRoute('frontend.checkout.cart.page'); } $page = $this->confirmPageLoader->load($request, $context); $cart = $page->getCart(); $cartErrors = $cart->getErrors(); $this->hook(new CheckoutConfirmPageLoadedHook($page, $context)); $this->addCartErrors($cart); if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) { $cartErrors->clear(); // To prevent redirect loops add the identifier that the request already got redirected from the same origin return $this->redirectToRoute( 'frontend.checkout.confirm.page', \array_merge( $request->query->all(), [self::REDIRECTED_FROM_SAME_ROUTE => true] ), ); } return $this->renderStorefront('@Storefront/storefront/page/checkout/confirm/index.html.twig', ['page' => $page]); } /** * @Since("6.0.0.0") * @Route("/checkout/finish", name="frontend.checkout.finish.page", options={"seo"="false"}, methods={"GET"}) * @NoStore */ public function finishPage(Request $request, SalesChannelContext $context, RequestDataBag $dataBag): Response { if ($context->getCustomer() === null) { return $this->redirectToRoute('frontend.checkout.register.page'); } $page = $this->finishPageLoader->load($request, $context); $this->hook(new CheckoutFinishPageLoadedHook($page, $context)); if ($page->isPaymentFailed() === true) { return $this->redirectToRoute( 'frontend.account.edit-order.page', [ 'orderId' => $request->get('orderId'), 'error-code' => 'CHECKOUT__UNKNOWN_ERROR', ] ); } if ($context->getCustomer()->getGuest() && $this->config->get('core.cart.logoutGuestAfterCheckout', $context->getSalesChannelId())) { $this->logoutRoute->logout($context, $dataBag); } return $this->renderStorefront('@Storefront/storefront/page/checkout/finish/index.html.twig', ['page' => $page]); } /** * @Since("6.0.0.0") * @Route("/checkout/order", name="frontend.checkout.finish.order", options={"seo"="false"}, methods={"POST"}) */ public function order(RequestDataBag $data, SalesChannelContext $context, Request $request): Response { if (!$context->getCustomer()) { return $this->redirectToRoute('frontend.checkout.register.page'); } try { $this->addAffiliateTracking($data, $request->getSession()); $orderId = Profiler::trace('checkout-order', function () use ($data, $context) { return $this->orderService->createOrder($data, $context); }); } catch (ConstraintViolationException $formViolations) { return $this->forwardToRoute('frontend.checkout.confirm.page', ['formViolations' => $formViolations]); } catch (InvalidCartException | Error | EmptyCartException $error) { $this->addCartErrors( $this->cartService->getCart($context->getToken(), $context) ); return $this->forwardToRoute('frontend.checkout.confirm.page'); } try { $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]); $errorUrl = $this->generateUrl('frontend.account.edit-order.page', ['orderId' => $orderId]); $response = Profiler::trace('handle-payment', function () use ($orderId, $data, $context, $finishUrl, $errorUrl): ?RedirectResponse { return $this->paymentService->handlePaymentByOrder($orderId, $data, $context, $finishUrl, $errorUrl); }); return $response ?? new RedirectResponse($finishUrl); } catch (PaymentProcessException | InvalidOrderException | UnknownPaymentMethodException $e) { return $this->forwardToRoute('frontend.checkout.finish.page', ['orderId' => $orderId, 'changedPayment' => false, 'paymentFailed' => true]); } } /** * @Since("6.0.0.0") * @Route("/widgets/checkout/info", name="frontend.checkout.info", methods={"GET"}, defaults={"XmlHttpRequest"=true}) */ public function info(Request $request, SalesChannelContext $context): Response { $cart = $this->cartService->getCart($context->getToken(), $context); if ($cart->getLineItems()->count() <= 0 && (Feature::isActive('v6.5.0.0') || Feature::isActive('PERFORMANCE_TWEAKS')) ) { return new Response(null, Response::HTTP_NO_CONTENT); } $page = $this->offcanvasCartPageLoader->load($request, $context); $this->hook(new CheckoutInfoWidgetLoadedHook($page, $context)); $response = $this->renderStorefront('@Storefront/storefront/layout/header/actions/cart-widget.html.twig', ['page' => $page]); $response->headers->set('x-robots-tag', 'noindex'); return $response; } /** * @Since("6.0.0.0") * @Route("/checkout/offcanvas", name="frontend.cart.offcanvas", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true}) */ public function offcanvas(Request $request, SalesChannelContext $context): Response { $page = $this->offcanvasCartPageLoader->load($request, $context); $this->hook(new CheckoutOffcanvasWidgetLoadedHook($page, $context)); $cart = $page->getCart(); $this->addCartErrors($cart); $cartErrors = $cart->getErrors(); if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) { $cartErrors->clear(); // To prevent redirect loops add the identifier that the request already got redirected from the same origin return $this->redirectToRoute( 'frontend.cart.offcanvas', \array_merge( $request->query->all(), [self::REDIRECTED_FROM_SAME_ROUTE => true] ), ); } $cartErrors->clear(); return $this->renderStorefront('@Storefront/storefront/component/checkout/offcanvas-cart.html.twig', ['page' => $page]); } private function addAffiliateTracking(RequestDataBag $dataBag, SessionInterface $session): void { $affiliateCode = $session->get(AffiliateTrackingListener::AFFILIATE_CODE_KEY); $campaignCode = $session->get(AffiliateTrackingListener::CAMPAIGN_CODE_KEY); if ($affiliateCode) { $dataBag->set(AffiliateTrackingListener::AFFILIATE_CODE_KEY, $affiliateCode); } if ($campaignCode) { $dataBag->set(AffiliateTrackingListener::CAMPAIGN_CODE_KEY, $campaignCode); } } private function routeNeedsReload(ErrorCollection $cartErrors): bool { foreach ($cartErrors as $error) { if ($error instanceof ShippingMethodChangedError || $error instanceof PaymentMethodChangedError) { return true; } } return false; }}