Search code examples
symfonyshopwareshopware6

How Is Shopware 6 Order Saved From The Frontend, The API appears to show nothing


I can't wrap my head on how the order data is saved from the storefront controller frontend.checkout.finish.order @ Shopware\vendor\shopware\storefront\Controller\CheckoutController.php

Below is the code

/**
     * @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]);
        }
    }

Help me explain how it works. I have traced some functions used in the API but can't find where the data is saved.

I also found Shopware\vendor\shopware\core\Checkout\Cart\Order\OrderPersister.php with the code below. But I can't place the connection with the frontend.checkout.finish.order API


<?php declare(strict_types=1);

namespace Shopware\Core\Checkout\Cart\Order;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartException;
use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;
use Shopware\Core\Checkout\Order\Exception\DeliveryWithoutAddressException;
use Shopware\Core\Checkout\Order\Exception\EmptyCartException;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

#[Package('checkout')]
class OrderPersister implements OrderPersisterInterface
{
    private EntityRepositoryInterface $orderRepository;

    private OrderConverter $converter;

    /**
     * @internal
     */
    public function __construct(
        EntityRepositoryInterface $repository,
        OrderConverter $converter
    ) {
        $this->orderRepository = $repository;
        $this->converter = $converter;
    }

    /**
     * @throws CustomerNotLoggedInException
     * @throws DeliveryWithoutAddressException
     * @throws EmptyCartException
     * @throws InvalidCartException
     * @throws InconsistentCriteriaIdsException
     */
    public function persist(Cart $cart, SalesChannelContext $context): string
    {
        if ($cart->getErrors()->blockOrder()) {
            throw CartException::invalidCart($cart->getErrors());
        }

        if (!$context->getCustomer()) {
            throw CartException::customerNotLoggedIn();
        }
        if ($cart->getLineItems()->count() <= 0) {
            throw new EmptyCartException();
        }

        $order = $this->converter->convertToOrder($cart, $context, new OrderConversionContext());

        $context->getContext()->scope(Context::SYSTEM_SCOPE, function (Context $context) use ($order): void {
            $this->orderRepository->create([$order], $context);
        });

        return $order['id'];
    }
}


Solution

  • Here is a basic trace of the method calls that happen:

    1. CheckoutController -> order
    2. OrderService-> createOrder
    3. CartService -> order
    4. OrderRoute -> order
    5. OrderPersistster -> persist

    The current cart is converted to an array for the order data and saved via the order repository, take a look at my comments in the persist method:

        public function persist(Cart $cart, SalesChannelContext $context): string
        {
            if ($cart->getErrors()->blockOrder()) {
                throw CartException::invalidCart($cart->getErrors());
            }
    
            if (!$context->getCustomer()) {
                throw CartException::customerNotLoggedIn();
            }
            if ($cart->getLineItems()->count() <= 0) {
                throw new EmptyCartException();
            }
            
            // Cart is converted to an array
            $order = $this->converter->convertToOrder($cart, $context, new OrderConversionContext());
    
            $context->getContext()->scope(Context::SYSTEM_SCOPE, function (Context $context) use ($order): void {
                // The array is saved to the database
                $this->orderRepository->create([$order], $context);
            });
    
            return $order['id'];
        }