Zusatzfeld/Custom Field in Checkout confirmation setzen

Hallo liebe Gemeinde,

aktuell sitze ich an einem Problem, bei dem ich nicht weiter weiß. Ich habe ein Zusatzfeld der Order Entity hinzugefügt (type checkbox/bool) und möchte dem Kunden nun auf der Seite zum Bestellabschluss ermöglichen, den Wert davon zu setzen.

Jetzt hänge ich gerade daran, beim Versenden des Abschluss-Formulars die Payload des versendeten Werts zu verarbeiten. Sowohl bei dem CheckoutOrderPlacedEvent als auch bei dem OrderWrittenEvent sehe ich nirgends die Möglichkeit, die Rohdaten einzusehen, welche mit dem Formular zum Bestellabschluss mitgesendet wurden, weswegen ich den Wert in der Bestellung nicht setzen kann.

Ich hoffe, dass mir da jemand weiterhelfen kann.

MfG,
Simon

Spannende Frage, würde mich auch interessieren. Ich hänge an einem vergleichbaren Problem.

Ich habe fast einen ganzen Tag herumexperimentiert und vieles ausprobiert und nun endlich eine Lösung gefunden.

Diese Lösung (getestet mit Shopware 6.5.7.3 ) deckt das Hinzufügen einer Checkbox zum Checkout Formular sowie das Verarbeiten dessen Werts ab. Interessant ist dieses Szenario vor allem, weil Checkboxen für Formulare ein richtiger pain in the you know what ist:

1. Das Custom Field

Zur Referenz, damit die nachfolgenden Schritte korrekt verstanden werden, zeige ich hier kurz das Custom Feld, welches ich bei der Installation meines Plugins der Order Entity hinzufüge:

public function addCustomFieldsToOrders($context): void {
        $this->customFieldSetRepository->create([
            [
                'name' => 'b2bbasics_order',
                'config' => [
                    'label' => [
                        'en-GB' => 'B2BBasics Order field set',
                        'de-DE' => 'B2BBasics Bestellzusatzfelder'
                        ],
                ],
                'relations' => [
                    [
                        'entityName' => 'order'
                    ]
                ],
                'customFields' => [
                    [
                        'name' => 'b2bbasics_order_allow_partial_delivery',
                        'type' => CustomFieldTypes::BOOL,
                        'config' => [
                            'type' => 'checkbox',
                            'componentName' => 'sw-field',
                            'customFieldType' => 'checkbox',
                            'label'=> [
                                'en-GB' => 'Allow partial delivery',
                                'de-DE' => 'Teillieferungen erlauben'
                            ],
                        ]
                    ]
                ]
            ]
        ], $context);
    }

Wie wir sehen ist es ein CustomField vom Typ Bool, welches mit einer Checkbox dargestellt wird und dem Kunden die Entscheidung gibt, ob Teillieferungen bei der Bestellungen tolleriert werden oder nicht.

Zur Vervollständigung hier der Funktionsaufruf bei der Plugin-Installation:

// src/custom/plugins/B2BBasics/src/B2BBasics.php
<?php declare(strict_types=1);

namespace B2BBasics;

use \Shopware\Core\Defaults;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use B2BBasics\Services\RepoCreator;


class B2BBasics extends Plugin
{

    public function install(InstallContext $context): void
    {
        $repoCreator = new RepoCreator($this->container->get('custom_field_set.repository'));
        $repoCreator->addCustomFieldsToOrders($context->getContext());
    }

    
}

2. Hinzufügen des Custom Fields zum Checkout Formular

Wie es beim Templating so ist, muss man die richtige Stelle finden, an der man das Feld hinzufügt. Ich habe mich in der Checkout Confirm Seite dazu entschieden, den Block page_checkout_additional für meine Bedürfnisse anzupassen und dort das Feld hinzuzufügen:

// src/custom/plugins/B2BBasics/src/Resources/views/storefront/page/checkout/confirm/index.html.twig
{% sw_extends '@Storefront/storefront/page/checkout/confirm/index.html.twig'%}

// Normalerweise ist der ganze Block so designt, dass dort ausschließlich das Customer Comment Field ist. 
// Deswegen müssen wir den gesamten Block umschreiben
{% block page_checkout_additional %}
	<div class="checkout-additional">
		<div class="card checkout-card">
			<div class="card-body">
				{% block page_checkout_finish_customer_comment %}
					{{ block('page_checkout_confirm_customer_comment_header')}}
					{{ block('page_checkout_confirm_customer_comment_control')}}
				{% endblock %}

// Ab hier beginnt der relevante Part, wo wir unser custom Field hinzufügen
				{% block page_checkout_additional_partial_delivery %}
					{% block page_checkout_additional_partial_delivery_input %}
						<input form="confirmOrderForm" //Der Form Name ist sehr wichtig
						data-partial-delivery-checkbox-handler // Attribut ist wichtig für Part 2b
						type="checkbox" class="form-check-input"
						name="allowPartialDelivery" value="1" id="allowPartialDelivery" // Name und ID sind auch wichtig
						checked> // So schlaue Füchse wie wir sind, ist das Feld standardmäßig angehakt
					{% endblock %}
					{% block page_checkout_additional_partial_delivery_label %}
						<label class="form-check-label custom-control-label" for="allowPartialDelivery">
							{{ "customFields.b2bbasics_order_allow_partial_delivery"|trans|sw_sanitize }}
						{% endblock %}
					{% endblock %}

				</div>
			</div>
		</div>
	{% endblock %}

2b. But wait, there’s more (Oder auch: Warum Checkboxen keinen Spaß machen)

Wie immer wäre es schön, wenn alles einfach funktionieren würde. Checkboxen gehören leider zu den Geschichten, welche das nicht tun. Aus folgenden Gründen:

  • Checkboxen haben einen fest definierten Value oder emitten „On“ wenn keiner gesetzt ist
  • Ja, eigentlich würde die Checkbox „Off“ emitten, jedoch wird eine ungecheckte Checkbox ohne Wert vom Formular gnadenlos ignoriert
  • Wir müssen unsere eigene Logik hinzuschreiben, welche den Wert der Checkbox abhängig von seinem Status ändert

Introducing: CheckboxHandler.js

CheckboxHandler.js ist ein Script, welches wir an Checkboxen anhängen können, um deren Value entsprechend ihrem Checked Status setzen zu können:

// src/custom/plugins/B2BBasics/src/Resources/App/storefront/src/B2BBasics/CheckboxHandler.js
import Plugin from 'src/plugin-system/plugin.class';

export default class CheckboxHandler extends Plugin {
    init() {
        this.el.value = this.el.checked; // Setzt den Value des Elements entsprechend des Checked Values
        this.el.addEventListener("click", this.changeCheckboxValueState.bind(this)); // Hier hören wir auf das Event, welches beim Interagieren mit der Checkbox ausgelöst wird (funktioniert auch mit Leertasten-Interaktion)
    }

    changeCheckboxValueState() {
        this.el.value = this.el.checked; // Setzt den Value Wert neu. Zur erhöhten Zuverlässigkeit negieren wir den Wert nicht einfach
    }
}

Damit dieser Spaß auch richtig an die Checkbox attached wird, müssen wir in unserer main.js das Plugin entsprechend registrieren:

// src/custom/plugins/B2BBasics/src/Resources/app/storefront/src/main.js

import CheckboxHandler from "./B2BBasics/CheckboxHandler"; // Importieren unseres Scripts

const PluginManager = window.PluginManager;
// Hier wird das Script an das Checkbox-Element attached. Man beachte das Attribut, welches wir zur Identifikation verwenden und auch im Template vorhanden ist
PluginManager.register('CheckboxHandler', CheckboxHandler, '[data-partial-delivery-checkbox-handler]');

Mit diesen Schritten wird der Wert der Checkbox nun richtig gesetzt und beim „Submitten“ der Checkout Confirmation wird der Wert entsprechend mitgesendet, sodass wir diesen verarbeiten können.

3. Rawdogging beim Order Event

Ich habe viele verschiedene Lösungsansätze zur Verarbeitung des Werts bei Bestellungen gesehen und teilweise ausprobiert und alle für bestenfalls mäßig befunden.

Mein Ansatz sieht wiefolgt aus:

  1. Wir hören auf das CheckoutOrderPlacedEvent, um entsprechend auf die Bestellerstellung reagieren zu können
  2. Wir fügen dem Subscriber Service noch entsprehcend den RequestStack als Eingabeparameter hinzu, weil das CheckoutOrderPlacedEvent keine Daten unserer Order Submit Request enthält (wäre nur zu praktisch)
  3. Wir lesen den Wert unserer Checkbox aus (oder versuchen es) und setzen im ´OrderRepository´ bei der entsprechenden Entity den Wert

Das sieht dann so aus:

// src/custom/plugins/B2BBasics/src/Subscriber/OrderPlacedSubscriber.php
<?php declare(strict_types=1);

namespace B2BBasics\Subscriber;

use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class OrderPlacedSubscriber implements EventSubscriberInterface {

    private $requestStack;
    private EntityRepository $orderRepo;
 
// Unser Service braucht sowohl den Request Stack, als auch das Order Repository
    public function __construct(RequestStack $requestStack, EntityRepository $orderRepo)
    {
        $this->requestStack = $requestStack;
        $this->orderRepo = $orderRepo;
    }

// Wir subscriben zu dem CheckoutOrderPlacedEvent
    public static function getSubscribedEvents()
    {
        return [
            CheckoutOrderPlacedEvent::class => 'onOrderPlaced', // Das Subscriben zu dem Event mit der Konstante `CartEvents::CHECKOUT_ORDER_PLACED` hat nicht funktioniert. Deswegen lieber die Klasse angeben 
        ];
    }

    public function onOrderPlaced(CheckoutOrderPlacedEvent $event): void {
// Da wir in unserem Event keine Informationen erhalten, welche bei der Request versendet wurden, müssen wir uns damit behelfen die Request selbst abzurufen und den Wert auszulesen
        $partialDeliveryAllowed = $this->requestStack->getCurrentRequest()->get('allowPartialDelivery'); // Man beachte die id der Checkbox im Template
// Sollte der Wert nicht mitgesendet worden sein, brechen wir ab
        if($partialDeliveryAllowed === null) {
            return;
        }
// Zum Glück müssen wir nicht komplett rawdoggen: Die OrderID können wir aus unserem Event nocht erhalten
        $orderId = $event->getOrder()->getId();
// Im Order Repository aktualiseren wir nun unsere Bestellung und setzen den entsprechenden Custom Field Wert. Man beachte, dass in diesem Code das CustomFields Array überschrieben werden würde. Sollte man Masochist sein und noch mehr Custom Fields Bestellungen hinzufügen wollen, müsste man den Code entsprechend ändern
        $this->orderRepo->update([['id' => $orderId, 'customFields' => ['b2bbasics_order_allow_partial_delivery' => filter_var($partialDeliveryAllowed, FILTER_VALIDATE_BOOLEAN)]]], $event->getContext());
    }
}

4. Den Service Registrieren

Zum Schluss müssen wir unseren Subscriber noch als Service registrieren und die entsprechenden Eingabeparameter übermitteln lassen:

// src/custom/plugins/B2BBasics/src/Resources/config/services.xml in abgespeckter Form
<?xml version="1.0"?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="B2BBasics\Subscriber\OrderPlacedSubscriber"> // Unser Subscriber
            <argument type="service" id="request_stack" /> // Eingabeparameter RequestStack
            <argument type="service" id="order.repository" /> // Eingabeparameter OrderRepository
            <tag name="kernel.event_subscriber"></tag> // Subscriber Tag setzen, damit der Bums auch funktioniert
        </service>
    </services>
</container>

5. Ganz viele Bilder

Zum Abschluss nun, wie das am Ende aussehen soll:

Unsere Checkbox wird nun auf der Checkout Confirmation Page dargestellt und ist standardmäßig angehakt (haha wir Schlingel)

Nach dem Absenden des Formulars können wir im Network Inspector von Firefox sehen, dass der Wert unserer Checkbox mit übermittelt wurde

Im Admin-Panel von Shopware können wir bei der entsprechenden Bestellung nun sehen, dass der Wert gesetzt wurde, wie wir es wollten.

6. Epilogue

Dies war eine gigantische Reise mit viel Frust und sehr viel Hirnschmalz, das geflossen ist. Ich hoffe, dieses Beispiel hilft anderen Entwicklern bei der Lösung zweier Probleme:

  • Wie man Checkboxen Formularen hinzufügt und dessen Wert verarbeitet
  • Wie man den Wert eines CustomFields bei Bestellungen setzt, welches vom User beim Checkout gesetzt werden kann

Zu beidem habe ich nämlich nie etwas gefunden und ich bin froh, wenn ich auch nur einer Person damit weiterhelfen konnte.

Beste Grüße und viel Erfolg bei aktuellen und zukünftigen Projekten,
Simon

4 „Gefällt mir“

Hey @SRach ,

auch wenn ich das selbst gerade nicht benötige. Ein riesen Dank für deine Mühe.
Von sowas lebt die Community.

Leider zeigts auch etwas sehr trauriges auf, der Aufwand um eine einfache Checkbox zu inkludieren dürfte viele abschrecken.

Grüße
Alex

2 „Gefällt mir“

@SRach Wow vielen Dank für die Mühe und das aufbereiten der Lösung.

Für eine verhältnismässig simple Checkbox erscheint es mir wirklich viel Aufwand, aber es kann gut sein, dass ich um eine solche Lösung auch nicht rumkomme. Vielen Dank für die Mühe, sollte ich es wirklich brauchen (hängt nicht von meiner Entscheidung ab), dann ist das eine sehr große Hilfe.

Ich schließe mich an von solchen posts lebt die Community :slight_smile:
Und ich schließe mich auch an, dass man bei Shopware 6 oft nichts oder sehr wenig findet, für Probleme die gefühlt alltäglicher Natur sein müssten.

2 „Gefällt mir“

Tatsächlich war ich auch sehr überrascht, was für ein Aufwand das Einfügen einer Checkbox ist. Beim Checkout ist es ja nochmal besonders schwierig, aber auch bei „banalen“ Formularen muss man das auch nochmal im Backend verarbeiten lassen.

Falls da ein Beispiel für eher „banalere“ Sachen gebraucht wird, hätte ich auch noch eins für das Registrierungsformular :slight_smile:

Habe das auch gerade gebraucht und das hat wirklich gut funktioniert. Alles bestens beschrieben. 1 zu 1 so umsetzbar.
Vielen Dank für die detaillierte Erklärung.

So kommt man A.) selber schnell zur Lösung und B.) versteht man wie es funktioniert. Hab jetzt wieder was über Subscriber und Repositories gelernt :+1:

1 „Gefällt mir“

Dieses Thema wurde automatisch 30 Tage nach der letzten Antwort geschlossen. Es sind keine neuen Antworten mehr erlaubt.