Search code examples
shopwareshopware6

Automatically change shipment provider for headless Shopware


I need automatically change the shipment provider if the current one is not available due to some rules on the headless side. I know about this change but after checking this change I've realized it was done on the Storefront side and it doesn’t work for headless due to missing change in core (platform) code.

We have an idea to create a plugin but here are our struggles. We try to use the event "onCartChange" but it has a problem. The route "/store-api /checkout/cart" returns for me old shipment provider although the context on the backend was updated (I use debugger). Looks like the event trigger callback function only after the cart was changed and it was too late for me.

Maybe I can improve my code which is below or we can find another idea for the plugin? Maybe it better do via a headless and no use plugin?

class CartUpdateSubscriber implements EventSubscriberInterface
{
    private $shippingMethodRepository;

    public function __construct(
        SalesChannelContextSwitcher $contextSwitcher,
        SalesChannelRepositoryInterface $shippingMethodRepository
    ) {
        $this->contextSwitcher = $contextSwitcher;
        $this->shippingMethodRepository = $shippingMethodRepository;
    }

    /**
     * Register events to listen to
     *
     * @return array
     */
    public static function getSubscribedEvents(): array
    {
        return [
            CartChangedEvent::class => 'onCartChange',
        ];
    }

    /**
     * @param CartChangedEvent $event
     * @return void
     */
    public function onCartChange(CartChangedEvent $event): void
    {
        $dataBag = new DataBag([
            'shippingMethodId' => $this->getShippingMethod($event->getContext())->getId(),
        ]);
        $this->contextSwitcher->update($dataBag, $event->getContext());
    }

    private function getShippingMethod(SalesChannelContext $context): ShippingMethodEntity
    {
        $criteria = new Criteria();
        $criteria
            ->addFilter(new EqualsFilter('active', true));

        $allShipments = $this->shippingMethodRepository->search($criteria, $context)->getEntities();

        $availableShippingMethods = $allShipments->filterByActiveRules($context);

        $shippingMethod = $availableShippingMethods->filterByProperty('name', "Spedition");
        if($availableShippingMethods->count() >= 2){
            $shippingMethod = $availableShippingMethods->filterByProperty('name',"Express");
        }

        return $shippingMethod->first();
    }
}

Solution

  • I think I can see the problem. CartChangedEvent is dispatched whenever line items are updated/added/removed after the rules have been evaluated. Then on the next request, re-fetching the cart, the new/removed line items are considered when re-evaluating the rules.

    So you probably want to listen to an event that is dispatched after the cart is validated, possibly blocking a shipping method, but before it is persisted. I could only really find one such event that is dispatched within that timespan. That event would be CartVerifyPersistEvent and it is dispatched immediately before the cart is persisted.

    use Shopware\Core\Checkout\Cart\Event\CartVerifyPersistEvent;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    class CartVerifyPersistSubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [
                CartVerifyPersistEvent::class => 'onCartVerifyPersist',
            ];
        }
    
        public function onCartVerifyPersist(CartVerifyPersistEvent $event): void
        {
            $cart = $event->getCart();
            // ...
        }
    }