Shopware 6.5: Der benutzerdefinierte Warenkorb-Validator zeigt weiterhin die Validierung an, auch wenn die Bedingungen erfüllt sind

Hallo,

Ich habe ein benutzerdefiniertes Plugin hinzugefügt, um zu überprüfen, ob die Lieferadresse aus der Kasse null ist oder nicht. Darüber hinaus prüft es auch, ob die Entfernung zwischen der Shop-Adresse und der Lieferadresse null ist oder nicht. Wenn ich jedoch versuche, mit einer gültigen Adresse zur Kasse zu gehen, wird immer noch die Fehlermeldung angezeigt, auch wenn die Bedingungen erfüllt sind.

Hier sind meine Codes.

service.xml

<service id="Mymodule\CustomhippingCost\Subscriber\ShippingMethodValidator">
            <tag name="shopware.cart.validator"/>
            <argument type="service" id="Mymodule\CustomhippingCost\Service\ConfigService"/>
            <argument type="service" id="Mymodule\CustomhippingCost\Service\GoogleMapsApiService"/>
            <argument type="service" id="request_stack"/>
        </service>

Validator class

<?php

declare(strict_types=1);

namespace Mymodule\CustomhippingCost\Subscriber;

use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartValidatorInterface;
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Mymodule\CustomhippingCost\Core\Checkout\Cart\Shipping\Error\InvalidShippingAddressError;
use Mymodule\CustomhippingCost\Service\ConfigService;
use Mymodule\CustomhippingCost\Service\GoogleMapsApiService;
use Mymodule\CustomhippingCost\Exception\GoogleMapsApiResponseException;
use Mymodule\CustomhippingCost\Exception\InvalidDistanceBasedShippingCostConfigException;
use Psr\Cache\InvalidArgumentException;
use GuzzleHttp\Exception\GuzzleException;
use Mymodule\CustomhippingCost\Helper\AddressHelper;
use Shopware\Core\Framework\Context;
use Symfony\Component\HttpFoundation\RequestStack;

class ShippingMethodValidator implements CartValidatorInterface
{
    private ConfigService $configService;

    private GoogleMapsApiService $gmapsApiService;

    private RequestStack $requestStack;

    public function __construct(
        ConfigService        $configService,
        GoogleMapsApiService $gmapsApiService,
        RequestStack $requestStack
    ) {
        $this->configService = $configService;
        $this->gmapsApiService = $gmapsApiService;
        $this->requestStack = $requestStack;
    }

    public function validate(Cart $cart, ErrorCollection $errorCollection, SalesChannelContext $salesChannelContext): void
    {
        $shippingMethod = $shippingMethodId = $salesChannelContext->getShippingMethod();

        if (!$this->isValidShippingMethod($shippingMethod, $salesChannelContext)) {
            $errorCollection->add(new InvalidShippingAddressError($shippingMethod->getId()));
            return;
        } 

        $session = $this->requestStack->getSession();
        $session->getFlashBag()->get('danger');
        $session->getFlashBag()->get('warning');
        return;
    }
    private function isValidShippingMethod($shippingMethod, $salesChannelContext): bool
    {
        $context = Context::createDefaultContext();
        $config = $this->configService->getValidConfig($context);
        try {
            if ($config->getShippingMethodId() === $shippingMethod->getId()) {
                if ($salesChannelContext->getShippingLocation()->getAddress() === null) {
                    throw new GoogleMapsApiResponseException("Shipping Address cannot be null", 500);
                }
                $distanceInMeters = $this->gmapsApiService->getDistanceInMeters(
                    $config->getGoogleMapsApiKey(),
                    $config->getStoreAddress(),
                    AddressHelper::getShippingAddressAsStr($salesChannelContext->getShippingLocation()->getAddress())
                );

                if (is_null($distanceInMeters)) {
                    return false;
                }
            }
        } catch (\Exception | GuzzleException | InvalidArgumentException | InvalidDistanceBasedShippingCostConfigException | GoogleMapsApiResponseException $e) {
            return false;
        }
        return true;
    }
}

Das gleiche Problem habe ich leider auch.
Ich kann in meinen Logs nachvollziehen, dass der Validator mehrmals aufgerufen wird und anfangs teilweise veraltete Daten hat, welche einen Error reinwerfen und das Cart invalidieren, obwohl die aktuelllen Daten korrekt sind.

Laut Shopware Doku soll der Eingabeparameter ErrorCollection $errorCollection

der Funktion
public function validate(Cart $cart, ErrorCollection $errorCollection, SalesChannelContext $salesChannelContext): void

alle vorangegangenen Fehler enthalten, jedoch enthält die Collection bei mir nie „vorangegangene“ Fehler, sodass ich diese nicht im Zweifelsfall entfernen kann.

Ref zur Doku
Zitat:

As already said, a cart validator has to implement the CartValidatorInterface and therefore implement a validate method. This method has access to some important parts of the checkout, such as the cart and the current sales channel context. Also you have access to the error collection, which may or may not contain errors from other earlier validators.

In meinem Szenario limitiere ich den Maximalwert eines Carts. Tritt die Überschreitung und damit der Fehler auf, ist der Fehler erst nach der zweiten validen Änderung des Warenkorbs komplett verschwunden.

1 Like

Okay, ich habe für mein Szenario eine Möglichkeit gefunden.

Das Problem ist, dass die $errorCollection zumindest bei mir immer leer ist. Jedoch enthält $cart->getErrors() noch vorangegangene Errors, wie es eigentlich bei der $errorCollection sein sollte.

Meine Lösung sieht wiefolgt aus:

class CartOrderLimitValidator implements CartValidatorInterface {
    
    private SystemConfigService $systemConfigService;

    public function __construct(SystemConfigService $systemConfigService)
    {
        $this->systemConfigService = $systemConfigService;
    }

    public function validate(Cart $cart, ErrorCollection $errorCollection, SalesChannelContext $salesChannelContext): void
    {

        if(!$this->systemConfigService->get('B2BBasics.config.orderValueLimitExceededNoticeEnabled') || !$salesChannelContext->getCustomer()) {
            return;
        }

// Stell dir hier vorbereitenden Code für die Überprüfung vor

// Hier validieren wir die Werte, die vorbereitend abgeholt wurden. Dient nur zur Veranschauung
        if ($cartOrderValue >= $cartOrderLimit && $cartCustomerOrderLimit < $cartOrderValue ) {
// Wenn die Werte nicht passen, fügen wir der Errorcollection den Fehler hinzu und brechen ab. Dies ist nur zur Veranschauung wichtig, weil wir den Error-Typen brauchen
            $errorCollection->add(new CartOrderLimitExceededError($cartOrderValue, $cartOrderLimit > $cartCustomerOrderLimit ? $cartOrderLimit : $cartCustomerOrderLimit));
            return;
        }

// Wir machen eine neue ErrorCollection, in der wir die Fehler reintun, die nicht von diesem Validator kommen
        $cartErrors = new ErrorCollection();
// Wir iterieren über die Errors des Carts
        foreach($cart->getErrors() as $error) {
// Wenn der Fehler unser desired Fehler `$CartOrderLimitExceededError` in meinem Fall ist, machen wir weiter. Wir filtern damit vorangegangene Fehler dieses Typs raus, da wir an dieser Stelle Erfolg haben und Vorgänger nicht mehr aktuell sind
            if($error instanceof CartOrderLimitExceededError) {
                continue;
            }
// Ist der Fehler irgendein anderer, fügen wir ihn wieder hinzu. Wir wollen ja schließlich nicht in andere Validierungen hineintreten
            $cartErrors->add($error);
        }
// Am Ende setzen wir die ErrorCollection des Carts auf die neue Errorcollection, in der wir veraltete Fehler unseres Typs herausgefiltert haben
        $cart->setErrors($cartErrors);
    }
}

Ich hoffe die Antwort ist halbwegs verständlich und kommt nicht all zu spät. Wobei ich mir sicher bin, dass wir nicht die einzigen mit dem Problem wären.

MfG
Simon

1 Like

Danke für die Lösung @SRach
Ich für meinen Teil hab sie verstanden, umgesetzt und sie funktioniert.

Wäre nur hübsch zu erfahren, warum das mit den „Altfehlern“ so ist. Vielleicht muss das ja so und wir haben irgendwo einen Denkfehler.

Leider auch in 6.6 immer noch dieser Fehler.

Es machts deutlich einfacher den Error aus der Cart zu löschen anstatt die foreach… In der Error-Class einen KEY definieren und den Error via Cart->getErrors->remove löschen

if ($hatFehler) {
            $errorCollection->add(new CartOrderLimitExceededError(...));
            return;
        } else {
$cart->getErrors()->remove(CartOrderLimitExceededError::KEY);
}