Passed
Push — 6.5.0.0 ( b977f8...246a8a )
by Christian
14:42 queued 12s
created

CheckoutController::cartJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Controller;
4
5
use Shopware\Core\Checkout\Cart\Error\Error;
6
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
7
use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;
8
use Shopware\Core\Checkout\Cart\SalesChannel\CartLoadRoute;
9
use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
10
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
11
use Shopware\Core\Checkout\Order\Exception\EmptyCartException;
12
use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
13
use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;
14
use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;
15
use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
16
use Shopware\Core\Checkout\Payment\PaymentService;
17
use Shopware\Core\Framework\Log\Package;
18
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
19
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
20
use Shopware\Core\Profiling\Profiler;
21
use Shopware\Core\System\SalesChannel\SalesChannelContext;
22
use Shopware\Core\System\SystemConfig\SystemConfigService;
23
use Shopware\Storefront\Checkout\Cart\Error\PaymentMethodChangedError;
24
use Shopware\Storefront\Checkout\Cart\Error\ShippingMethodChangedError;
25
use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener;
26
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedHook;
27
use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader;
28
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedHook;
29
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader;
30
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedHook;
31
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader;
32
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutInfoWidgetLoadedHook;
33
use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutOffcanvasWidgetLoadedHook;
34
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader;
35
use Symfony\Component\HttpFoundation\RedirectResponse;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\HttpFoundation\Response;
38
use Symfony\Component\HttpFoundation\Session\SessionInterface;
39
use Symfony\Component\Routing\Annotation\Route;
40
41
/**
42
 * @internal
43
 */
44
#[Route(defaults: ['_routeScope' => ['storefront']])]
45
#[Package('storefront')]
46
class CheckoutController extends StorefrontController
47
{
48
    private const REDIRECTED_FROM_SAME_ROUTE = 'redirected';
49
50
    /**
51
     * @internal
52
     */
53
    public function __construct(
54
        private readonly CartService $cartService,
55
        private readonly CheckoutCartPageLoader $cartPageLoader,
56
        private readonly CheckoutConfirmPageLoader $confirmPageLoader,
57
        private readonly CheckoutFinishPageLoader $finishPageLoader,
58
        private readonly OrderService $orderService,
59
        private readonly PaymentService $paymentService,
60
        private readonly OffcanvasCartPageLoader $offcanvasCartPageLoader,
61
        private readonly SystemConfigService $config,
62
        private readonly AbstractLogoutRoute $logoutRoute,
63
        private readonly CartLoadRoute $cartLoadRoute
64
    ) {
65
    }
66
67
    #[Route(path: '/checkout/cart', name: 'frontend.checkout.cart.page', options: ['seo' => false], defaults: ['_noStore' => true], methods: ['GET'])]
68
    public function cartPage(Request $request, SalesChannelContext $context): Response
69
    {
70
        $page = $this->cartPageLoader->load($request, $context);
71
        $cart = $page->getCart();
72
        $cartErrors = $cart->getErrors();
73
74
        $this->hook(new CheckoutCartPageLoadedHook($page, $context));
75
76
        $this->addCartErrors($cart);
77
78
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
79
            $cartErrors->clear();
80
81
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
82
            return $this->redirectToRoute(
83
                'frontend.checkout.cart.page',
84
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
85
            );
86
        }
87
        $cartErrors->clear();
88
89
        return $this->renderStorefront('@Storefront/storefront/page/checkout/cart/index.html.twig', ['page' => $page]);
90
    }
91
92
    #[Route(path: '/checkout/cart.json', name: 'frontend.checkout.cart.json', methods: ['GET'], options: ['seo' => false], defaults: ['XmlHttpRequest' => true])]
93
    public function cartJson(Request $request, SalesChannelContext $context): Response
94
    {
95
        return $this->cartLoadRoute->load($request, $context);
96
    }
97
98
    #[Route(path: '/checkout/confirm', name: 'frontend.checkout.confirm.page', options: ['seo' => false], defaults: ['XmlHttpRequest' => true, '_noStore' => true], methods: ['GET'])]
99
    public function confirmPage(Request $request, SalesChannelContext $context): Response
100
    {
101
        if (!$context->getCustomer()) {
102
            return $this->redirectToRoute('frontend.checkout.register.page');
103
        }
104
105
        if ($this->cartService->getCart($context->getToken(), $context)->getLineItems()->count() === 0) {
106
            return $this->redirectToRoute('frontend.checkout.cart.page');
107
        }
108
109
        $page = $this->confirmPageLoader->load($request, $context);
110
        $cart = $page->getCart();
111
        $cartErrors = $cart->getErrors();
112
113
        $this->hook(new CheckoutConfirmPageLoadedHook($page, $context));
114
115
        $this->addCartErrors($cart);
116
117
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
118
            $cartErrors->clear();
119
120
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
121
            return $this->redirectToRoute(
122
                'frontend.checkout.confirm.page',
123
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
124
            );
125
        }
126
127
        return $this->renderStorefront('@Storefront/storefront/page/checkout/confirm/index.html.twig', ['page' => $page]);
128
    }
129
130
    #[Route(path: '/checkout/finish', name: 'frontend.checkout.finish.page', options: ['seo' => false], defaults: ['_noStore' => true], methods: ['GET'])]
131
    public function finishPage(Request $request, SalesChannelContext $context, RequestDataBag $dataBag): Response
132
    {
133
        if ($context->getCustomer() === null) {
134
            return $this->redirectToRoute('frontend.checkout.register.page');
135
        }
136
137
        $page = $this->finishPageLoader->load($request, $context);
138
139
        $this->hook(new CheckoutFinishPageLoadedHook($page, $context));
140
141
        if ($page->isPaymentFailed() === true) {
142
            return $this->redirectToRoute(
143
                'frontend.account.edit-order.page',
144
                [
145
                    'orderId' => $request->get('orderId'),
146
                    'error-code' => 'CHECKOUT__UNKNOWN_ERROR',
147
                ]
148
            );
149
        }
150
151
        if ($context->getCustomer()->getGuest() && $this->config->get('core.cart.logoutGuestAfterCheckout', $context->getSalesChannelId())) {
152
            $this->logoutRoute->logout($context, $dataBag);
153
        }
154
155
        return $this->renderStorefront('@Storefront/storefront/page/checkout/finish/index.html.twig', ['page' => $page]);
156
    }
157
158
    #[Route(path: '/checkout/order', name: 'frontend.checkout.finish.order', options: ['seo' => false], methods: ['POST'])]
159
    public function order(RequestDataBag $data, SalesChannelContext $context, Request $request): Response
160
    {
161
        if (!$context->getCustomer()) {
162
            return $this->redirectToRoute('frontend.checkout.register.page');
163
        }
164
165
        try {
166
            $this->addAffiliateTracking($data, $request->getSession());
167
168
            $orderId = Profiler::trace('checkout-order', fn () => $this->orderService->createOrder($data, $context));
169
        } catch (ConstraintViolationException $formViolations) {
170
            return $this->forwardToRoute('frontend.checkout.confirm.page', ['formViolations' => $formViolations]);
171
        } catch (InvalidCartException | Error | EmptyCartException) {
172
            $this->addCartErrors(
173
                $this->cartService->getCart($context->getToken(), $context)
174
            );
175
176
            return $this->forwardToRoute('frontend.checkout.confirm.page');
177
        }
178
179
        try {
180
            $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]);
181
            $errorUrl = $this->generateUrl('frontend.account.edit-order.page', ['orderId' => $orderId]);
182
183
            $response = Profiler::trace('handle-payment', fn (): ?RedirectResponse => $this->paymentService->handlePaymentByOrder($orderId, $data, $context, $finishUrl, $errorUrl));
184
185
            return $response ?? new RedirectResponse($finishUrl);
186
        } catch (PaymentProcessException | InvalidOrderException | UnknownPaymentMethodException) {
187
            return $this->forwardToRoute('frontend.checkout.finish.page', ['orderId' => $orderId, 'changedPayment' => false, 'paymentFailed' => true]);
188
        }
189
    }
190
191
    #[Route(path: '/widgets/checkout/info', name: 'frontend.checkout.info', defaults: ['XmlHttpRequest' => true], methods: ['GET'])]
192
    public function info(Request $request, SalesChannelContext $context): Response
193
    {
194
        $cart = $this->cartService->getCart($context->getToken(), $context);
195
        if ($cart->getLineItems()->count() <= 0) {
196
            return new Response(null, Response::HTTP_NO_CONTENT);
197
        }
198
199
        $page = $this->offcanvasCartPageLoader->load($request, $context);
200
201
        $this->hook(new CheckoutInfoWidgetLoadedHook($page, $context));
202
203
        $response = $this->renderStorefront('@Storefront/storefront/layout/header/actions/cart-widget.html.twig', ['page' => $page]);
204
        $response->headers->set('x-robots-tag', 'noindex');
205
206
        return $response;
207
    }
208
209
    #[Route(path: '/checkout/offcanvas', name: 'frontend.cart.offcanvas', options: ['seo' => false], defaults: ['XmlHttpRequest' => true], methods: ['GET'])]
210
    public function offcanvas(Request $request, SalesChannelContext $context): Response
211
    {
212
        $page = $this->offcanvasCartPageLoader->load($request, $context);
213
214
        $this->hook(new CheckoutOffcanvasWidgetLoadedHook($page, $context));
215
216
        $cart = $page->getCart();
217
        $this->addCartErrors($cart);
218
        $cartErrors = $cart->getErrors();
219
220
        if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
221
            $cartErrors->clear();
222
223
            // To prevent redirect loops add the identifier that the request already got redirected from the same origin
224
            return $this->redirectToRoute(
225
                'frontend.cart.offcanvas',
226
                [...$request->query->all(), ...[self::REDIRECTED_FROM_SAME_ROUTE => true]],
227
            );
228
        }
229
230
        $cartErrors->clear();
231
232
        return $this->renderStorefront('@Storefront/storefront/component/checkout/offcanvas-cart.html.twig', ['page' => $page]);
233
    }
234
235
    private function addAffiliateTracking(RequestDataBag $dataBag, SessionInterface $session): void
236
    {
237
        $affiliateCode = $session->get(AffiliateTrackingListener::AFFILIATE_CODE_KEY);
238
        $campaignCode = $session->get(AffiliateTrackingListener::CAMPAIGN_CODE_KEY);
239
        if ($affiliateCode) {
240
            $dataBag->set(AffiliateTrackingListener::AFFILIATE_CODE_KEY, $affiliateCode);
241
        }
242
243
        if ($campaignCode) {
244
            $dataBag->set(AffiliateTrackingListener::CAMPAIGN_CODE_KEY, $campaignCode);
245
        }
246
    }
247
248
    private function routeNeedsReload(ErrorCollection $cartErrors): bool
249
    {
250
        foreach ($cartErrors as $error) {
251
            if ($error instanceof ShippingMethodChangedError || $error instanceof PaymentMethodChangedError) {
252
                return true;
253
            }
254
        }
255
256
        return false;
257
    }
258
}
259