Tracking, Cross-Sell und eigene Blöcke in der Hyvä Order-Success-Page
Die Order-Success-Page ist der Moment mit der höchsten Aufmerksamkeit im gesamten Checkout: Der Kunde hat gerade gekauft, ist aber noch auf der Seite. Wer diesen Moment für Conversion-Tracking, Cross-Sell und Kundenbindung nutzt, ohne Knockout.js, jQuery oder UI-Components, sondern mit sauberen Hyvä-Patterns aus ViewModels, Layout-XML und Alpine.js, gewinnt spürbar mehr aus der Bestellbestätigungsseite als mit der Luma-Standardausgabe.
Inhaltsverzeichnis
- 1. Aufbau der Order-Success-Page in Hyvä
- 2. Tracking-Events CSP-konform einbinden
- 3. Eigene Blöcke per Layout-XML hinzufügen
- 4. Cross-Sell und Produktempfehlungen
- 5. Bestelldetails erweitern
- 6. Newsletter-Signup und Kundenkonto-Erstellung
- 7. Rechnungs-Download und zusätzliche Links
- 8. Multi-Store- und Mehrsprachigkeit-Besonderheiten
- 9. Sicherheit und Robustheit
- 10. Zusammenfassung
- 11. FAQ
1. Aufbau der Order-Success-Page in Hyvä
Die Order-Success-Page wird in Magento über das Layout-Handle checkout_onepage_success gerendert, das von Magento_Checkout definiert und in Hyvä über app/design/frontend/Mironsoft/default/Magento_Checkout/layout/checkout_onepage_success.xml überschrieben wird. Anders als bei Luma gibt es hier keine Knockout-Templates und keine uiComponent-Deklarationen: Der komplette Inhalt landet im Block Magento\Checkout\Block\Onepage\Success, dessen Template in Hyvä unter Magento_Checkout/templates/onepage/success.phtml liegt. Dieses Template ist reines PHTML mit direktem PHP-Zugriff auf den Block, kein Datenbinding über data-bind und keine asynchron nachgeladenen Knockout-Components.
Die Order-Daten selbst kommen nicht aus einem REST-Call zur Laufzeit, sondern werden serverseitig über Magento\Checkout\Model\Session ausgelesen. Der Block ruft $this->getOrderId(), $this->getOrder() und darüber Magento\Sales\Model\Order-Methoden wie getIncrementId(), getCustomerEmail() oder getGrandTotal() auf. Weil checkout_onepage_success beim ersten Aufruf nach dem Redirect gerendert wird, bevor die Checkout-Session invalidiert wird, ist der Zugriff auf Magento\Checkout\Model\Session::getLastRealOrder() die zuverlässigste Quelle für die zuletzt aufgegebene Bestellung.
Für eigene Erweiterungen der Bestellbestätigungsseite bedeutet das: Man arbeitet in der Regel nicht direkt am success.phtml, sondern fügt zusätzliche Blöcke über Layout-XML in den Container checkout.success ein. Dieser Container ist in Hyvä als einfacher <div>-Wrapper im Template vorhanden und iteriert per $block->getChildNames() über alle Kind-Blöcke, exakt wie im restlichen Hyvä-Blocksystem. Das erlaubt saubere Erweiterungen ohne den Kern-Block zu überschreiben.
<!-- app/design/frontend/Mironsoft/default/Magento_Checkout/templates/onepage/success.phtml (Auszug) -->
<?php
/** @var \Magento\Checkout\Block\Onepage\Success $block */
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
?>
<div class="not-prose bg-white rounded-2xl border border-gray-200 p-6 sm:p-8">
<h1 class="text-2xl font-bold text-gray-900 mb-2">
<?= $block->escapeHtml(__('Thank you for your order!')) ?>
</h1>
<p class="text-gray-600 mb-6">
<?= $block->escapeHtml(__('Your order number is: %1', $block->getOrderId())) ?>
</p>
<!-- Iterate over child blocks added via layout XML, standard Hyvä pattern -->
<?php foreach ($block->getChildNames() as $childName): ?>
<?= $block->getChildHtml($childName) ?>
<?php endforeach; ?>
</div>
2. Tracking-Events CSP-konform einbinden
Conversion-Tracking auf der Order-Success-Page ist die häufigste Anforderung von Kunden, und gleichzeitig die häufigste Quelle von CSP-Verstößen und doppelt gezählten Conversions. Hyvä setzt standardmäßig eine strikte Content Security Policy über das Modul Hyva_CspCompatibility, die Inline-Scripts ohne Registrierung blockiert. Jedes GA4- oder Google-Ads-Conversion-Event, das per dataLayer.push() in ein <script>-Tag geschrieben wird, muss deshalb mit $hyvaCsp->registerInlineScript() im PHTML-Template freigegeben werden, sonst greift der Browser-Nonce nicht und das Script wird stumm blockiert.
Die zweite Herausforderung ist die doppelte Zählung von Conversions bei Seiten-Reload. Da die Bestellbestätigungsseite unter derselben URL erneut aufgerufen werden kann (Browser-Reload, Zurück-Button, Lesezeichen), würde ein naives dataLayer.push() im Template bei jedem Reload dieselbe Order-ID erneut an GA4 senden. Die zuverlässige Lösung ist ein serverseitiger Zustand: Sobald Magento\Checkout\Model\Session::getLastOrderId() gelesen und das Event ausgegeben wurde, wird clearHelperData() aufgerufen beziehungsweise ein eigenes Flag in der Session gesetzt, damit ein zweiter Aufruf des Success-Blocks kein Event mehr auslöst.
Für GA4 nutzt man das purchase-Event mit den Standard-Ecommerce-Parametern (transaction_id, value, currency, items), die aus $order->getAllVisibleItems() aufgebaut werden. Google-Ads-Conversion-Tracking läuft parallel über ein zweites dataLayer.push() mit der Conversion-ID, idealerweise über denselben serverseitig gerenderten JSON-Block, damit keine zusätzliche Race-Condition zwischen zwei Inline-Scripts entsteht.
<?php
/** app/design/frontend/Mironsoft/default/Magento_Checkout/templates/onepage/success/tracking.phtml */
/** @var \Magento\Checkout\Block\Onepage\Success $block */
/** @var \Hyva\Theme\ViewModel\HyvaCsp $hyvaCsp */
$order = $block->getOrder();
$items = [];
foreach ($order->getAllVisibleItems() as $item) {
$items[] = [
'item_id' => $item->getSku(),
'item_name' => $item->getName(),
'price' => (float) $item->getPrice(),
'quantity' => (int) $item->getQtyOrdered(),
];
}
$payload = [
'event' => 'purchase',
'ecommerce' => [
'transaction_id' => $order->getIncrementId(),
'value' => (float) $order->getGrandTotal(),
'currency' => $order->getOrderCurrencyCode(),
'items' => $items,
],
];
?>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(<?= /* @noEscape */ json_encode($payload, JSON_UNESCAPED_UNICODE) ?>);
</script>
<?= /* @noEscape */ $hyvaCsp->registerInlineScript() ?>
3. Eigene Blöcke per Layout-XML hinzufügen
In Luma wäre der naheliegende Weg, eine eigene Block-Klasse zu schreiben, die von \Magento\Framework\View\Element\Template erbt und Geschäftslogik in Getter-Methoden kapselt. In Hyvä ist das ViewModel-Pattern (Magento\Framework\View\Element\Block\ArgumentInterface) der bevorzugte Weg: Ein ViewModel wird per arguments in Layout-XML an ein generisches Magento_Checkout::template.phtml oder ein eigenes Template gebunden, ohne dass für jeden neuen Block eine eigene PHP-Klasse mit Konstruktor-Boilerplate für Context, Data und Template-Pfad nötig ist.
Die Positionierung im checkout.success-Container wird über das before- oder after-Attribut im <block>-Tag gesteuert, exakt wie bei jedem anderen Hyvä-Block. Wichtig ist, dass eigene Blöcke immer als Kind von checkout.success deklariert werden, nicht als eigenständiger Container außerhalb der bestehenden Struktur, sonst greift die getChildNames()-Iteration im success.phtml nicht.
<!-- app/design/frontend/Mironsoft/default/Magento_Checkout/layout/checkout_onepage_success.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.success">
<!-- Custom block using the ViewModel pattern instead of a dedicated Block class -->
<block class="Magento\Framework\View\Element\Template"
name="mironsoft.success.order.recommendations"
template="Mironsoft_OrderSuccess::recommendations.phtml"
after="-">
<arguments>
<argument name="view_model" xsi:type="object">
Mironsoft\OrderSuccess\ViewModel\RecommendationsViewModel
</argument>
</arguments>
</block>
</referenceBlock>
</body>
</page>
4. Cross-Sell und Produktempfehlungen auf der Success-Page
Produktempfehlungen auf der Bestellbestätigungsseite funktionieren in Hyvä am saubersten über GraphQL statt über schwere Collection-Loads im Block. Ein ViewModel ermittelt zunächst die Kategorien der zuletzt bestellten Produkte aus $order->getAllVisibleItems(), sammelt daraus die category_ids und übergibt sie als Filter an eine products-Query gegen Magento\GraphQl. Diese Query wird nicht serverseitig im PHP-Prozess über den GraphQL-Resolver ausgeführt, sondern clientseitig per fetch() in einer Alpine-Komponente, sodass das Rendering der Success-Page selbst nicht durch den zusätzlichen Produktabruf verzögert wird.
Die Darstellung erfolgt als Tailwind-Grid mit grid-cols-2 sm:grid-cols-3 lg:grid-cols-4, wobei jede Produktkarte per x-data ihren eigenen Ladezustand verwaltet. Für das Lazy-Loading der Empfehlungen nutzt man x-intersect aus dem Alpine Intersect-Plugin, das in Hyvä-Projekten üblicherweise bereits über Hyva_Theme registriert ist: Erst wenn der Cross-Sell-Container in den Viewport scrollt, wird die GraphQL-Query tatsächlich abgeschickt. Das reduziert die initiale Serverlast auf der Order-Success-Page erheblich, weil viele Besucher die Seite verlassen, bevor sie überhaupt bis zum Empfehlungsbereich scrollen.
Für die Kategorie-Auswahl gilt: Der letzte gekaufte Artikel liefert selten die beste Empfehlung, weil der Kunde genau dieses Produkt gerade erworben hat. Bewährt hat sich, die übergeordnete Kategorie zu nehmen und das gerade gekaufte SKU per sku_not_in-Filter aus dem Ergebnis auszuschließen, damit auf der Bestellbestätigungsseite keine bereits gekauften Artikel erneut beworben werden.
# GraphQL query executed client-side via fetch() after the recommendations
# container enters the viewport (Alpine x-intersect)
query CrossSellAfterOrder($categoryIds: [String], $excludeSku: String) {
products(
filter: {
category_id: { in: $categoryIds }
sku: { neq: $excludeSku }
}
pageSize: 8
sort: { relevance: DESC }
) {
items {
sku
name
url_key
small_image { url label }
price_range {
minimum_price {
final_price { value currency }
}
}
}
}
}
5. Bestelldetails erweitern
Sehr häufig braucht die Order-Success-Page zusätzliche Angaben, die nicht Teil des Standard-Order-Objekts sind, etwa ein gewünschtes Lieferdatum, eine Referenznummer aus dem B2B-Checkout oder eine Kommission. Diese Werte werden über eine db_schema.xml-Erweiterung als zusätzliche Spalte auf sales_order abgelegt, deklarativ statt per InstallScript, und über ein eigenes Repository oder direkt über Magento\Sales\Model\Order::getData('custom_reference') ausgelesen. Wichtig: Für den Zugriff über den generischen getData()-Aufruf reicht die deklarative Schema-Erweiterung, ein zusätzliches Extension-Attribute-Interface ist nur nötig, wenn das Feld auch über die REST/GraphQL-Sales-API verfügbar sein soll.
Bei der Ausgabe von Preisen und Steuern auf der Bestellbestätigungsseite ist die manuelle String-Formatierung mit number_format() ein häufiger Fehler, weil sie Währungssymbol, Dezimaltrennzeichen und Rundung des jeweiligen Store-Views ignoriert. Stattdessen liefert Magento\Framework\Pricing\Helper\Data::currency() beziehungsweise das bereits im Block verfügbare $block->getOrder()->formatPrice() die korrekt lokalisierte Darstellung inklusive Store-spezifischer Währung. Für Steuerbeträge liefert $order->getTaxAmount() in Kombination mit derselben Price-Utility konsistente Ausgaben, ohne dass die Steuerlogik im Template dupliziert werden muss.
Für strukturierte Zusatzfelder, die in mehreren Ansichten wiederverwendet werden (Success-Page, PDF-Rechnung, Kundenkonto-Order-View), lohnt sich ein eigenes ViewModel, das die Order entgegennimmt und typisierte Getter für die individuellen Felder bereitstellt. Das vermeidet, dass getData('custom_reference') mit seinem ungenauen Rückgabetyp direkt im PHTML verstreut wird.
6. Newsletter-Signup und Kundenkonto-Erstellung nach Gast-Checkout
Nach einem Gast-Checkout bietet die Bestellbestätigungsseite die letzte realistische Gelegenheit, den Besucher zur Newsletter-Anmeldung oder zur nachträglichen Kundenkonto-Erstellung zu bewegen. Das Formular selbst wird als Alpine-Komponente mit x-data für E-Mail-Feld, Opt-in-Checkbox und Ladezustand umgesetzt, ganz ohne Knockout-Validierung. Die eigentliche Verarbeitung läuft serverseitig über einen eigenen Controller unter Mironsoft_OrderSuccess/Controller/Newsletter/Subscribe oder alternativ über einen Web-API-Endpunkt, der Magento\Newsletter\Model\SubscriberFactory beziehungsweise Magento\Customer\Api\AccountManagementInterface::createAccount() aufruft.
Für die DSGVO-konforme Opt-in-Logik gilt: Die Checkbox darf nicht vorbelegt sein, der Zustimmungstext muss auf die Datenschutzerklärung verlinken, und der Zeitpunkt der Einwilligung wird zusammen mit der IP-Adresse serverseitig protokolliert, damit im Streitfall ein Nachweis existiert. Bei der nachträglichen Kundenkonto-Erstellung übernimmt AccountManagementInterface::createAccount() automatisch die Rechnungs- und Lieferadresse aus der Gastbestellung, sodass der Kunde nicht erneut Adressdaten eintippen muss.
Der Controller validiert zusätzlich, dass die übergebene Order-ID tatsächlich zur aktuellen Checkout-Session gehört, bevor irgendeine Aktion ausgeführt wird. Ohne diese Prüfung ließe sich über eine manipulierte Order-ID versuchen, ein Konto für eine fremde Bestellung anzulegen, ein Angriffsvektor, der bei der Erweiterung der Success-Page leicht übersehen wird.
// Alpine component for the post-checkout newsletter/account form,
// no Knockout, no jQuery
document.addEventListener('alpine:init', () => {
Alpine.data('postCheckoutSignup', (orderId) => ({
email: '',
optIn: false,
createAccount: false,
loading: false,
success: false,
error: null,
async submit() {
if (!this.optIn) {
this.error = 'Please confirm the opt-in to continue.';
return;
}
this.loading = true;
this.error = null;
try {
const response = await fetch('/rest/V1/mironsoft-order-success/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId: orderId,
email: this.email,
newsletterOptIn: this.optIn,
createAccount: this.createAccount,
}),
});
if (!response.ok) {
throw new Error('Request failed');
}
this.success = true;
} catch (e) {
this.error = 'Something went wrong. Please try again.';
} finally {
this.loading = false;
}
},
}));
});
7. Rechnungs-Download und zusätzliche Links
Der Link zur PDF-Rechnung auf der Order-Success-Page nutzt in Magento das Modul Magento_Sales mit dem Controller Magento\Sales\Controller\Order\PrintInvoice beziehungsweise die Route sales/order/printInvoice mit der Order-ID als Parameter. Ein eigener Block bindet diesen Link über $this->getUrl('sales/order/printInvoice', ['order_id' => $order->getId()]) ein, sofern bereits eine Rechnung (Magento\Sales\Model\Order\Invoice) zur Bestellung existiert. Existiert noch keine Rechnung, etwa bei Vorkasse-Zahlarten mit späterer Fakturierung, sollte der Link im Template konditional ausgeblendet werden, statt auf eine leere PDF zu verweisen.
Social-Sharing-Hinweise auf der Bestellbestätigungsseite werden in vielen Projekten über externe JS-Snippets von Facebook oder X eingebunden, was in Hyvä an der CSP scheitert und zusätzliche Drittanbieter-Requests erzeugt. Die CSP-konforme Alternative sind einfache <a>-Links mit vorbefüllten Share-URLs (https://twitter.com/intent/tweet?text=...), die ohne jedes externe Script auskommen und dem Besucher die Wahl lassen, ob er den Link tatsächlich öffnet.
Weitere sinnvolle Links auf der Success-Page sind der direkte Sprung zur Sendungsverfolgung, sobald ein Versandlabel existiert, sowie ein Link zur Kundenkonto-Order-Ansicht für eingeloggte Kunden. Beide Links lassen sich über dasselbe ViewModel-Pattern realisieren, das bereits für die Rechnungs-Verlinkung verwendet wird.
<!-- Invoice download and CSP-safe social sharing, no external JS -->
<?php if ($block->hasInvoice()): ?>
<a class="inline-flex items-center gap-2 text-orange-700 hover:underline text-sm font-semibold"
href="<?= $block->escapeUrl($block->getUrl('sales/order/printInvoice', ['order_id' => $block->getOrder()->getId()])) ?>"
target="_blank" rel="noopener">
<?= $block->escapeHtml(__('Download invoice (PDF)')) ?>
</a>
<?php endif; ?>
<!-- Plain link, no third-party script, no tracking pixel -->
<a class="inline-flex items-center gap-2 text-slate-600 hover:underline text-sm"
href="https://twitter.com/intent/tweet?text=<?= $block->escapeUrl(__('Just placed an order!')) ?>"
target="_blank" rel="noopener">
<?= $block->escapeHtml(__('Share on X')) ?>
</a>
8. Multi-Store- und Mehrsprachigkeit-Besonderheiten
In Multi-Store-Setups muss die Bestellbestätigungsseite store-abhängige Inhalte korrekt ausspielen, etwa unterschiedliche Cross-Sell-Kategorien pro Website oder abweichende Rechtstexte beim Newsletter-Opt-in. Der ViewModel-Layer liest dafür Magento\Store\Model\StoreManagerInterface::getStore() aus und verzweigt die Logik nicht im Template, sondern im ViewModel selbst, damit das PHTML store-unabhängig bleibt und dieselbe Datei für alle Store-Views funktioniert.
Übersetzungen der eigenen Blöcke auf der Order-Success-Page laufen über die üblichen i18n-CSV-Dateien unter app/design/frontend/Mironsoft/default/Mironsoft_OrderSuccess/i18n/de_DE.csv und en_US.csv. Jeder Text im Template wird über __('Text') ausgegeben und in der jeweiligen CSV mit der übersetzten Variante hinterlegt. Wichtig für Custom-Module: Der i18n-Ordner muss im Modulkontext liegen, nicht im Theme, wenn die Übersetzung unabhängig vom aktiven Theme funktionieren soll, andernfalls bricht die Übersetzung bei einem Theme-Wechsel.
Bei mehrsprachigen Shops mit unterschiedlichen Store-Views pro Sprache sollte auch das Tracking-Skript aus Abschnitt 2 store-abhängig konfigurierbar sein, etwa über unterschiedliche GA4-Property-IDs pro Store-View, die über system.xml-Konfiguration mit Scope website statt global hinterlegt werden.
| Aufgabe auf der Success-Page | Luma-Ansatz | Hyvä-Ansatz | Vorteil |
|---|---|---|---|
| Eigene Logik einbinden | Eigene Block-Klasse mit Konstruktor-Boilerplate | ViewModel (ArgumentInterface) via Layout-XML | Weniger Boilerplate, klare Trennung von Template und Logik |
| Formular-Interaktivität | Knockout-Bindings, uiComponent-Deklaration | Alpine.js x-data direkt im Template | Kein JS-Build-Schritt für Komponenten nötig |
| Produktempfehlungen laden | Server-Collection im Block, blockierendes Rendering | GraphQL-Query per fetch(), x-intersect Lazy-Load | Schnelleres initiales Rendering der Order-Success-Page |
| Tracking-Script einbinden | Inline-Script ohne CSP-Prüfung | registerInlineScript() mit Nonce | Läuft mit strikter CSP, kein Browser-Block |
| Preise formatieren | number_format() manuell im Template | Price-Utility / formatPrice() | Korrekte Store-Währung und Lokalisierung |
9. Sicherheit und Robustheit
Ein klassisches Problem der Order-Success-Page ist die Order-ID-Wiederverwendung nach einem Reload: Ruft der Kunde die Seite ein zweites Mal auf, ohne dass eine neue Bestellung existiert, würde Magento ohne zusätzliche Absicherung dieselbe Order erneut anzeigen oder im schlimmsten Fall Tracking-Events erneut auslösen. Magento löst das über Magento\Checkout\Model\Session::getLastRealOrder() in Kombination mit einem gezielten unsetData('last_order_id'), nachdem alle Bestellbestätigungs-Blöcke gerendert wurden. Eigene Erweiterungen sollten diesen Session-Wert lesen, aber niemals selbst frühzeitig löschen, sonst sehen nachfolgende Blöcke in derselben Anfrage keine Order-Daten mehr.
Der Zugriffsschutz für nicht eingeloggte Besucher ist bei der Success-Page eine Gratwanderung: Die Seite muss für Gäste erreichbar sein, darf aber nicht durch einfaches Hochzählen der Order-ID fremde Bestellungen preisgeben. Magento sichert das ab, indem die Order-ID auf der Seite selbst nicht aus der URL, sondern ausschließlich aus der Session gelesen wird. Eigene Blöcke, die zusätzliche Order-Daten laden, müssen genau dieselbe Session-Order verwenden und dürfen niemals eine Order anhand eines URL-Parameters direkt aus der Datenbank laden, ohne die Zugehörigkeit zur aktuellen Session zu prüfen.
Für die Teststrategie der Bestellbestätigungsseite empfiehlt sich eine Kombination aus MFTF-Funktionstests, die den kompletten Checkout-Flow bis zur Success-Page durchlaufen, sowie gezielten PHPUnit-Tests für jedes neue ViewModel. Für das Tracking-Script lohnt sich zusätzlich ein einfacher End-to-End-Test, der prüft, dass das purchase-Event bei einem zweiten Seitenaufruf mit derselben Order-ID nicht erneut im dataLayer landet.
Mironsoft
Hyvä-Entwicklung, Checkout-Optimierung und Conversion-Tracking
Mehr aus eurer Order-Success-Page herausholen?
Wir erweitern eure Bestellbestätigungsseite um CSP-konformes Tracking, GraphQL-basierten Cross-Sell und eigene Blöcke, sauber über ViewModels und Layout-XML, ohne Knockout, ohne jQuery, ohne UI-Components.
Tracking-Audit
GA4- und Google-Ads-Events auf CSP-Konformität und doppelte Zählung prüfen
Cross-Sell-Integration
GraphQL-Produktempfehlungen mit Lazy-Loading auf der Success-Page umsetzen
Custom-Blöcke
ViewModel-basierte Erweiterungen für Order-Details, Rechnung und Newsletter-Signup
10. Zusammenfassung
Die Order-Success-Page ist in Hyvä kein starres Luma-Template, sondern ein normaler Block-Container, der sich über Layout-XML, ViewModels und Alpine.js gezielt erweitern lässt. Der Grundaufbau bleibt einfach: checkout_onepage_success.xml steuert das Layout, Magento\Checkout\Model\Session liefert die Order-Daten, und success.phtml iteriert über Kind-Blöcke im checkout.success-Container. Wer eigene Tracking-Events, Cross-Sell-Bereiche oder Formulare hinzufügt, sollte konsequent auf ViewModels statt Block-Klassen setzen und jedes Inline-Script über $hyvaCsp->registerInlineScript() freigeben.
Besonders wichtig für eine robuste Bestellbestätigungsseite: Tracking-Events dürfen bei einem Reload nicht erneut feuern, Order-Daten müssen ausschließlich aus der Session stammen statt aus URL-Parametern, und Cross-Sell-Empfehlungen sollten per GraphQL und Lazy-Loading nachgeladen werden, damit das initiale Rendering der Seite nicht leidet. Wer diese Punkte beachtet, macht aus der Order-Success-Page mehr als eine reine Bestätigungsseite: einen Kanal für Kundenbindung, Conversion-Tracking und zusätzlichen Umsatz.
Order-Success-Page in Hyvä anpassen, das Wichtigste auf einen Blick
Layout und Session
checkout_onepage_success.xml steuert die Bestellbestätigungsseite, Order-Daten kommen aus Magento\Checkout\Model\Session, kein Knockout-Binding.
Tracking ohne Doppelzählung
dataLayer.push() nur mit registerInlineScript(), Order-ID nach Ausgabe aus der Session entfernen, um Reload-Duplikate zu vermeiden.
ViewModel statt Block-Klasse
Eigene Blöcke als ArgumentInterface-ViewModel über Layout-XML in den checkout.success-Container einhängen.
Cross-Sell per GraphQL
Produktempfehlungen clientseitig per fetch() und x-intersect nachladen, damit die Order-Success-Page schnell bleibt.