Search code examples
phpsymfonyshopwareshopware6

Shopware 6 Plugin - Unable to Retrieve Shipping Price on Product Detail Page (Without adding product to the cart)


I'm currently working on a Shopware 6 plugin that aims to dynamically determine the shipping costs for a single product based on the default country of the store. I want to have the shipping cost for the product without adding the product to the cart. My custom file MyCustomShippingMethodRoute.php within my plugin's directory looks like:

<?php

use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Routing\Annotation\Entity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Shopware\Core\Checkout\Shipping\SalesChannel\AbstractShippingMethodRoute;
use Shopware\Core\Checkout\Shipping\SalesChannel\ShippingMethodRouteResponse;
use SwagProductReview\Service\ShippingPriceService;

/**
 * @Route(defaults={"_routeScope"={"store-api"}})
 */
class MyCustomShippingMethodRoute extends AbstractShippingMethodRoute
{
    private AbstractShippingMethodRoute $decorated;
    private ShippingPriceService $shippingPriceService;

    public function __construct(AbstractShippingMethodRoute $decorated, ShippingPriceService $shippingPriceService) 
    {
        $this->decorated = $decorated;
        $this->shippingPriceService = $shippingPriceService;
    }

    public function getDecorated(): AbstractShippingMethodRoute
    {
        return $this->decorated;
    }

    /**
     * @Entity("shipping_method")
     * @Route("/store-api/shipping-method", name="store-api.shipping.method", methods={"GET", "POST"})
     */
  public function load(Request $request, SalesChannelContext $context, Criteria $criteria): ShippingMethodRouteResponse { $criteria->addAssociation('prices');
      $result = $this->decorated->load($request, $context, $criteria);
      $originalResult = clone $result;
//      $cart = $this->cartService->getCart($context->getToken(), $context);
      $shippingMethods = $result->getShippingMethods()->filterByActiveRules($context);
      /** @var ShippingMethodEntity $shippingMethod */
      try {
          foreach ($shippingMethods as $shippingMethod) {
              $shippingPrices= $shippingMethod->getPrices();
//            dd($shippingMethod->getPrices());
          }
      } catch (\Error $exception) {
          return $originalResult;
      }
      return $result;
  }
}

However, I'm facing an issue where I don't know how to show the value of $shippingPrices on the product details page.

  1. Is it even possible to show the shipping price to the details page without adding it to the cart?
  2. If yes, Kindly, guide me what should I do?

Regards,


Solution

  • When you decorate the product detail route, you could inject CartService, to get the current cart and add the product as a new line item to the cart. Furthermore you could inject CartRuleLoader and use it to then calculate the shipping costs with the product being added to the cart. To avoid the cart from being persisted after the calculation, you can set a dummy value as token for the cart and subscribe to the CartVerifyPersistEvent. In the listener you check the token for that dummy value and set a flag for the cart to not be persisted afterwards. Setting the isRecalculation flag of CartBehavior should also prevent persisting in newer versions, but it can't hurt to have a fallback check either way.

    <service id="SwagBasicExample\Route\MyCustomProductDetailRoute" 
             decorates="Shopware\Core\Content\Product\SalesChannel\Detail\ProductDetailRoute" 
             public="true">
        <argument type="service" id="SwagBasicExample\Route\MyCustomProductDetailRoute.inner"/>
        <argument type="service" id="Shopware\Core\Checkout\Cart\SalesChannel\CartService"/>
        <argument type="service" id="Shopware\Core\Checkout\Cart\CartRuleLoader"/>
    </service>
    
    <service id="SwagBasicExample\Subscriber\MySubscriber">
        <tag name="kernel.event_subscriber"/>
    </service>
    
    use Shopware\Core\Checkout\Cart\CartBehavior;
    use Shopware\Core\Checkout\Cart\CartRuleLoader;
    use Shopware\Core\Checkout\Cart\LineItem\LineItem;
    use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
    use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute;
    use Shopware\Core\Content\Product\SalesChannel\Detail\ProductDetailRouteResponse;
    use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
    use Shopware\Core\System\SalesChannel\SalesChannelContext;
    use Symfony\Component\HttpFoundation\Request;
    
    class MyCustomProductDetailRoute extends AbstractProductDetailRoute
    {
        public const DUMMY_TOKEN = 'dummy-token';
    
        public function __construct(
            private readonly AbstractProductDetailRoute $decorated,
            private readonly CartService $cartService,
            private readonly CartRuleLoader $cartRuleLoader
        ) {
        }
    
        public function getDecorated(): AbstractProductDetailRoute
        {
            return $this->decorated;
        }
    
        /*
         * @Entity("product")
         * @Route("/store-api/product/{productId}", name="store-api.product.detail", methods={"POST"})
         */
        public function load(string $productId, Request $request, SalesChannelContext $context, Criteria $criteria): ProductDetailRouteResponse
        {
            $result = $this->decorated->load($productId, $request, $context, $criteria);
    
            $cart = $this->cartService->getCart($context->getToken(), $context);
            $cart->setToken(self::DUMMY_TOKEN);
    
            $productLineItem = new LineItem($productId, LineItem::PRODUCT_LINE_ITEM_TYPE, $productId);
            $productLineItem->setGood(true);
            $productLineItem->setStackable(true);
            $productLineItem->setRemovable(true);
            
            $cart->add($productLineItem);
    
            $behavior = new CartBehavior([], true, true);
            $cart = $this->cartRuleLoader->loadByCart($context, $cart, $behavior)->getCart();
    
            $result->getResult()->addExtension('expectedShippingCosts', $cart->getShippingCosts());
    
            return $result;
        }
    }
    
    use Shopware\Core\Checkout\Cart\Event\CartVerifyPersistEvent;
    use SwagBasicExample\Route\MyCustomProductDetailRoute;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    class MySubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [CartVerifyPersistEvent::class => 'onCartPersistVerify'];
        }
    
        public function onCartPersistVerify(CartVerifyPersistEvent $event): void
        {
            if ($event->getCart()->getToken() !== MyCustomProductDetailRoute::DUMMY_TOKEN) {
                return;
            }
    
            $event->setShouldPersist(false);
        }
    }