Custom Fields in Checkout

Hallo zusammen,

ich habe mir eigene Custom Fields im Admin angelegt und diese den Orders zugewiesen. Im Checkout habe ich sie per Twig eingebunden (z. B. direkt unter dem Block additional) und die Felder lassen sich dort auch befüllen.

Das Problem: nach Abschluss der Bestellung sind die Felder leer. Wenn ich im Admin die Order-Details öffne, sehe ich in den Custom Fields keine Werte.

In meinem Plugin habe ich bereits eine PHP-Datei, die auf das Event CheckoutOrderPlacedEvent hört. Idee war: die im Cart gespeicherten Werte beim Bestellabschluss in order.customFields schreiben. Also: Event abfangen → Felder aus dem Request lesen → per Repository in die Order-Custom-Fields speichern.

Offenbar klappt das Speichern aber nicht – die Werte scheinen gar nicht in der Order anzukommen.
Hat jemand von euch das schon mal umgesetzt oder einen Tipp, ob ich einen Zwischenschritt über die Cart-Extension gehen muss bzw. wie genau die Übernahme ins Order-Objekt funktioniert?

Danke vorab!

Es gibt doch das Kommentarfeld. Kannst du im Quellcode nicht nachsehen, wie das übernommen wird?

Ich schaue mal, ob ich ein Codeblock finde…

Beim Quellcode vom Kommentarfeld bin ich leider nicht fündig geworden. Also, bis jetzt schaffe ich, dass die Werte der Felder korrekt am Order-Form hängen & dass die mit im POST /checkout/order gehen. Aber Shopware speichert sie nicht automatisch in order.customFields. Habe daher den Subscriber geschrieben, der die Felder beim Checkout abfangen und in die Order schreiben sollen, aber das funktioniert irgendwie nicht. :frowning:

Aus 6.4 Zeiten, sollte vom Prinzip aber noch passen:

class OrderSubscriber implements EventSubscriberInterface
{
    
    private $customerRepository;

    public function __construct(
        EntityRepositoryInterface $customerRepository
    ) {
        $this->customerRepository = $customerRepository;
    }

    public static function getSubscribedEvents()
    {
        return [
          CartConvertedEvent::class => 'addCustomFieldsToConvertedCart',
        ];
    }

    public function addCustomFieldsToConvertedCart(CartConvertedEvent $event)
    {
      $sepa_iban = (isset($_POST['sepa_iban'])?$_POST['sepa_iban']:false);

      if($sepa_iban !== false) {
          
        $cart = $event->getConvertedCart();
        $cart['customFields']['custom_order_sepa_iban'] = $sepa_iban;

        $event->setConvertedCart($cart);

        $this->customerRepository->upsert([
            [
                'id' => $cart['orderCustomer']['customerId'],
                'customFields' => [
                  'custom_customer_sepa_iban' => $sepa_iban
                ]
            ],
        ], $event->getContext());

      }

    }
}

Das ist der Subscriber:

<?php

namespace CryptoCustomField\Subscriber;

use Shopware\Core\Checkout\Order\Event\OrderPlacedEvent;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class EndkundeOrderPlacedSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly EntityRepository $orderRepository,
        private readonly RequestStack $requestStack
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [
            OrderPlacedEvent::class => 'onOrderPlaced',
        ];
    }

    public function onOrderPlaced(OrderPlacedEvent $event): void
    {
        $request = $this->requestStack->getCurrentRequest();
        if (!$request) {
            return;
        }

        $values = $request->getSession()->get('custom_endkunde');
        if (!\is_array($values) || !$values) {
            return;
        }

        $orderId = $event->getOrderId();
        $context = $event->getContext();

        $update = [
            'id' => $orderId,
            'customFields' => [
                'custom_endkunde_name' => (string)($values['custom_endkunde_name'] ?? ''),
                'custom_endkunde_mail' => (string)($values['custom_endkunde_mail'] ?? ''),
                'custom_endkunde_strasse' => (string)($values['custom_endkunde_strasse'] ?? ''),
                'custom_endkunde_plz' => (string)($values['custom_endkunde_plz'] ?? ''),
                'custom_endkunde_stadt' => (string)($values['custom_endkunde_stadt'] ?? ''),
                'custom_endkunde_walletid' => (string)($values['custom_endkunde_walletid'] ?? ''),
                'custom_endkunde_miningpool' => (string)($values['custom_endkunde_miningpool'] ?? ''),
            ],
        ];

        $this->orderRepository->update([$update], $context);
        $request->getSession()->remove('custom_endkunde');
    }
}

Das ist der Controller:

<?php declare(strict_types=1);

namespace CryptoCustomField\Storefront\Controller;

use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class EndkundeController extends StorefrontController
{
    #[Route(
        path: '/checkout/endkunde/save',
        name: 'frontend.checkout.endkunde.save',
        methods: ['POST'],
        defaults: ['_routeScope' => ['storefront'], '_csrf_protected' => false]
    )]
    public function save(Request $request): JsonResponse
    {
        error_log('[EndkundeController] entry');

        try {
            $keys = [
                'custom_endkunde_name',
                'custom_endkunde_mail',
                'custom_endkunde_strasse',
                'custom_endkunde_plz',
                'custom_endkunde_stadt',
                'custom_endkunde_walletid',
                'custom_endkunde_miningpool',
            ];

            $payload = [];
            foreach ($keys as $k) {
                $payload[$k] = (string) $request->request->get($k, '');
            }

            $session = $request->hasSession() ? $request->getSession() : null;
            if ($session) {
                $session->set('custom_endkunde', $payload);
                // optional: $session->save();

                error_log('[EndkundeController] saved to session: ' . json_encode($payload));

                return new JsonResponse(['saved' => true, 'via' => 'session']);
            }

            error_log('[EndkundeController] no session available');
            return new JsonResponse(['saved' => false, 'error' => 'no-session'], 200);
        } catch (\Throwable $e) {
            error_log('[EndkundeController] ERROR: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
            return new JsonResponse(['saved' => false, 'error' => 'internal'], 500);
        }
    }
}

Der Request wird korrekt abgesetzt (POST ist im Network-Tab sichtbar), jedoch kommt vom Server eine 500-Antwort. Meine bisherigen Troubleshooting-Schritte haben keinen klaren Hinweis geliefert. Ich vermute, dass der OrderPlaced-Subscriber an irgendeiner Stelle bricht.