Warenkorb, Checkout und Payment
Bauen Sie Warenkorb, Versand, Checkout und Payment nur aus öffentlichen Storefront-Antworten. Der Server berechnet Preise, Steuern, Versand, Eligibility und Zahlarten.
Voraussetzungen
Klären Sie vor dem ersten Warenkorb- oder Checkout-Aufruf:
- Der Zielmandant wurde mit Tenant Init vorbereitet. Für Checkout müssen mindestens Lokalisierung, Commerce und PIM initialisiert sein. System > Betriebsbereitschaft zeigt keine blockierenden systemweiten Hinweise.
- Der aktuelle Storefront-Kontext lädt für Host, Site, Sales Channel, Locale und Währung.
- Commerce-Profil, Kundengruppe, Preisliste und Verkaufspreise sind für den erwarteten Kontext gepflegt.
- Steuer-Jurisdiktionen, Lieferzonen, Versandkosten und Versandmethoden decken die geplanten Lieferländer und Adressfälle ab. Die Pflege ist in Lieferzonen und Versandkosten pflegen beschrieben.
- Zahlungsbedingungen, Zahlarten und technische Zahlungsanbieter sind fachlich freigegeben.
- Rechtliche Checkout-Hinweise, Erklärungsregeln und Produkt-Rechtsprofile sind für den Kanal geprüft. Die fachliche Einordnung steht in Handel- und Commerce-Fähigkeiten.
Wenn eine Voraussetzung fehlt, zeigen Sie einen klaren nächsten Schritt und geben Sie den Befund an Administration oder Commerce-Team zurück. Bauen Sie keine lokale Steuer-, Versand-, Preis- oder Zahlungslogik als Ersatz.
Warenkorb erstellen
Erstellen Sie einen Cart:
POST /api/v1/public/v1/cart
Content-Type: application/json
{}Beispielantwort:
{
"cartId": "uuid",
"tokenIssued": true,
"cartToken": "opaque-cart-token",
"status": "active",
"currency": "EUR",
"itemsCount": 0,
"totalNet": { "amount": "0", "currency": "EUR" },
"totalGross": { "amount": "0", "currency": "EUR" },
"totalTax": { "amount": "0", "currency": "EUR" },
"recalculated": false,
"reviewRequired": false
}Speichern Sie cartToken wie ein Credential. Senden Sie ihn nur im konfigurierten Cart-Token-Header. Verwenden Sie ihn nicht in URLs, Logs, Analytics oder Support-Nachrichten.
Der Headername kommt aus commerce.cart_token_header_name. Der aktuelle Runtime-Default ist X-Cart-Token. Die Beispiele verwenden <Cart-Token-Header> als Platzhalter, damit Integrationen den für ihre Instanz konfigurierten Namen einsetzen.
Warenkorb lesen
GET /api/v1/public/v1/cart
<Cart-Token-Header>: opaque-cart-tokenWenn der Server 404 liefert, verwerfen Sie den lokalen Token und erstellen einen neuen Cart. Bearbeiten Sie einen Cart mit status=converted nicht weiter.
Position hinzufügen
POST /api/v1/public/v1/cart/items
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"variantId": "uuid",
"quantity": "1",
"unitOfMeasure": "PC",
"configuration": {}
}Für ein verkaufbares Mischgebinde senden Sie dieselbe Trägervariante wie im Pricing und setzen pricingTargetType auf assortment_pack:
{
"variantId": "uuid-der-traegervariante",
"pricingTargetType": "assortment_pack",
"quantity": "1",
"unitOfMeasure": "PC",
"configuration": {}
}Der Warenkorb zeigt die kaufmännische Mischgebinde-Position. Der Server reserviert und prüft Bestand aber anhand der enthaltenen Varianten und ihrer Positionsmengen. Senden Sie keine eigenen Komponentenlisten aus dem Shop; die Positionen kommen aus dem gepflegten PIM-Mischgebinde.
Übernehmen Sie nach jedem Add-to-Cart die vollständige Cart-Response. Senden Sie keine Client-Preise, Rabatte, Steuerwerte, Shipping Totals, Customer Group, Price List, Tenant oder Sales Channel.
Wenn derselbe Artikel mit derselben Konfiguration bereits im Warenkorb liegt, kann der Server die vorhandene Position erhöhen. Unterschiedliche Konfigurationen bleiben eigene Positionen.
Nutzen Sie Mengen und Einheiten so, wie sie an der Variante veröffentlicht sind. Tage, Stunden, Meter oder Stück werden über quantity und unitOfMeasure abgebildet. Zusatzleistungen aus Product Associations legen Sie als eigene Cart-Positionen an. Die Modellierungsregeln finden Sie in Produktmodellierung: Entscheidungshilfe.
Menge ändern und Position entfernen
PATCH /api/v1/public/v1/cart/items/{itemId}
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"quantity": "3"
}DELETE /api/v1/public/v1/cart/items/{itemId}
<Cart-Token-Header>: opaque-cart-tokenÜbernehmen Sie Servermenge, Totals und Review-Hinweise aus der Antwort. Verwenden Sie keine alten Totals weiter.
Coupons und Review
POST /api/v1/public/v1/cart/coupons
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"code": "SOMMER2026"
}Wenn der Server COUPON_REVIEW_REQUIRED oder einen anderen Review-Code liefert, zeigen Sie Review an. Implementieren Sie keine lokale Rabattlogik.
Versand berechnen und wählen
POST /api/v1/public/v1/cart/shipping/recalculate
<Cart-Token-Header>: opaque-cart-tokenPOST /api/v1/public/v1/cart/shipping/select
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"methodId": "uuid"
}Beispiel für Versandoptionen:
{
"options": [
{
"methodId": "uuid",
"methodName": "Standard",
"carrierCode": "DHL",
"serviceLevel": "parcel",
"serviceKind": "parcel",
"exposure": "visible",
"priceState": "visible",
"amount": { "amount": "5.9000", "currency": "EUR" },
"deliveryTerm": {
"kind": "incoterm",
"incotermCode": "DAP",
"version": "2020",
"location": "DE 20354 Hamburg"
}
}
]
}Wenn Preise versteckt sind, kann amount fehlen. Verwenden Sie dann exposure, priceState, totalsAvailable und totalsState. Berechnen Sie Versandkosten nicht lokal.
serviceKind beschreibt die generische Service-Art der Versandmethode, zum Beispiel parcel, freight, courier, own_fleet, pickup oder other. deliveryTerm kann fehlen. Wenn es vorhanden ist, zeigen Sie den Lieferterm aus der Serverantwort an und leiten Sie daraus keine eigene Incoterm-Logik ab.
Wenn der Server SHIPPING_REVIEW_REQUIRED liefert, kann Workspace keinen verbindlichen Versandpreis berechnen oder der Warenkorb braucht eine interne Prüfung. Zeigen Sie dann eine Anfrage- oder Review-Strecke an und lösen Sie keinen zahlungspflichtigen Checkout aus.
Checkout Eligibility prüfen
GET /api/v1/public/v1/checkout/eligibility
<Cart-Token-Header>: opaque-cart-tokenBeispielantwort:
{
"eligible": false,
"checkoutAllowed": false,
"quoteAllowed": true,
"loginRequired": false,
"guestCheckoutAllowed": true,
"reviewRequired": false,
"flow": "guest",
"paymentMethods": ["prepayment"],
"paymentMethodOptions": [
{ "method": "prepayment", "allowed": true },
{
"method": "invoice",
"allowed": false,
"reasonCode": "company_invoice_entitlement_required"
}
],
"blockers": ["customer_email_required", "addresses_required"],
"blockerDetails": [
{ "code": "customer_email_required", "field": "customerEmail" },
{ "code": "addresses_required", "field": "billingAddressId" }
]
}Aktivieren Sie Checkout nur bei checkoutAllowed=true. Nutzen Sie paymentMethodOptions für die Zahlartauswahl. Behandeln Sie eligible, paymentMethods und blockers als Kompatibilitätsfelder.
Wenn physische Artikel nicht ausreichend reserviert werden können, meldet Eligibility inventory_insufficient_stock oder inventory_unavailable. Führen Sie den Nutzer dann zurück zum Warenkorb und lösen Sie place-order nicht aus.
Commerce-Legal-Felder auswerten
Checkout Eligibility liefert auch den rechtlichen Kontext für den aktuellen Warenkorb. Verwenden Sie diese Felder direkt aus der Antwort und leiten Sie keine eigenen B2B-, B2C-, Länder- oder Fristenregeln im Shop-Code ab.
{
"legalContext": {
"customerLegalClassification": "consumer",
"relationshipKind": "b2c",
"legalClassificationSource": "default_consumer",
"companyContext": "none",
"countryCode": "DE",
"productLegalClasses": ["physical_goods"],
"resolvedAt": "2026-05-29T10:00:00Z"
},
"legalDeclarations": [
{
"key": "terms_privacy",
"text": "Ich akzeptiere die AGB und Datenschutzinformationen.",
"textVersion": "2026-05.v1",
"required": true,
"blocking": true,
"sortOrder": 10,
"noticeRefs": [
{
"key": "terms",
"version": "2026-05.v1",
"title": "AGB",
"linkUrl": "https://shop.example.test/agb",
"locale": "de",
"countryCode": "DE"
}
],
"ruleKeys": ["consumer_terms_physical_de"],
"productLegalClasses": ["physical_goods"]
}
]
}Nutzen Sie legalContext für Anzeige und Debugging im Checkout, nicht als lokale Entscheidungsquelle. Die rechtliche Bewertung entsteht serverseitig und wird bei der Bestellung als Evidence gespeichert.
Werten Sie legalDeclarations[] so aus:
| Feld | Verwendung im eigenen Shop |
|---|---|
key | Stabiler Schlüssel der Erklärung. Senden Sie ihn in acceptedDeclarations[].key zurück. |
text | Sichtbarer Erklärungstext am Checkout. Ändern oder übersetzen Sie ihn nicht lokal. |
textVersion | Version der angezeigten Erklärung. Senden Sie exakt diese Version zurück. |
required / blocking | Zeigen Sie eine Pflicht-Checkbox nur an, wenn mindestens eines der Felder true ist und operatorAttentionRequired nicht true ist. |
noticeRefs[] | Verlinken oder zeigen Sie die referenzierten Hinweise, damit der Nutzer die Grundlage lesen kann. |
operatorAttentionRequired | Erzeugt keine Kundenzustimmung. Behandeln Sie den Fall als Betreiberhinweis, nicht als Checkout-Blocker. |
configurationIssueCodes[] | Technische Codes für Betreiberprobleme, zum Beispiel legal_notice_missing. Loggen Sie sie redigiert oder zeigen Sie sie nur in internen Operator-Oberflächen. |
Rendern Sie Pflicht-Erklärungen in der Reihenfolge sortOrder, danach key. Lassen Sie die Checkbox initial leer. Aktivieren Sie den finalen Bestellbutton erst, wenn alle sichtbaren Pflicht-Erklärungen aktiv akzeptiert sind und Checkout Eligibility weiterhin checkoutAllowed=true meldet.
Wenn eine Declaration operatorAttentionRequired=true trägt, zeigen Sie dem Kunden keine zusätzliche Checkbox und kein technisches Problem. Lassen Sie den Checkout weiterlaufen. Workspace speichert die spätere Bestellung mit unvollständiger Legal-Bewertung und Attention-Event, damit Betreiber die Konfiguration korrigieren können.
Checkout vorbereiten
Für Gastkauf senden Sie E-Mail und Adress-Snapshots:
POST /api/v1/public/v1/checkout/prepare
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"customerEmail": "kunde@example.test",
"billingAddress": {
"firstName": "Ada",
"lastName": "Buyer",
"companyName": "Beispiel GmbH",
"street": "Rechnungsstraße 1",
"postalCode": "50667",
"city": "Köln",
"country": "DE"
},
"shippingAddress": {
"firstName": "Ada",
"lastName": "Buyer",
"street": "Lieferstraße 2",
"postalCode": "10115",
"city": "Berlin",
"country": "DE"
}
}Für eingeloggte Kontexte verwenden Sie serverbestätigte IDs:
{
"customerEmail": "kunde@example.test",
"billingAddressId": "uuid",
"shippingAddressId": "uuid"
}Senden Sie pro Adressrolle nie ID und Objekt gleichzeitig. Werten Sie danach Eligibility erneut aus.
Für Firmen-Carts müssen billingAddressId und shippingAddressId CompanyAddress-IDs derselben Firma sein. Senden Sie keine Personenadresse als Lieferadresse für einen Firmen-Cart. Der Server lehnt fremde oder private Adressen mit CHECKOUT_ADDRESS_INVALID ab.
Wenn der Mitarbeiter nur für ausgewählte Lieferadressen freigeschaltet ist, akzeptiert der Server nur IDs aus seinem storefrontAddressIds-Umfang und antwortet sonst mit CHECKOUT_ADDRESS_NOT_ALLOWED. Eine Identity mit aktivem Firmenzugang kann einen privaten Personen-Cart mit Adress-IDs mit COMPANY_CONTEXT_REQUIRED abgelehnt bekommen. Verwenden Sie für privaten Einkauf eine getrennte Identity ohne aktiven Firmenzugang.
Zahlart wählen
POST /api/v1/public/v1/checkout/payment-method
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"method": "prepayment"
}Verfügbare Methoden:
| Methode | Bedingung |
|---|---|
prepayment | Providerlose Vorauskasse, wenn der Server sie erlaubt. |
invoice | Nur mit serverbestätigtem Firmenkontext, Customer Group und Payment Terms. |
paypal | Auswahl-Alias für PayPal Wallet, nur wenn ein aktiver PayPal-Provider konfiguriert und erlaubt ist. |
mollie_card | Serverseitige Auswahl für eine Karten-Order über Mollie, nur wenn der Server sie anbietet. |
Bieten Sie nur Methoden aus Storefront Context oder Checkout Eligibility an. Schalten Sie Rechnung, PayPal oder Kreditkarte nie lokal frei.
Behandeln Sie method im Public Checkout als Auswahlwert oder Alias. Antworten liefern die kanonische Payment-Semantik als paymentMethod, paymentInstrument und paymentProvider. Für PayPal schreibt der Server paymentMethod=wallet, paymentInstrument=paypal_wallet und paymentProvider=paypal. Für mollie_card verspricht die Public API keinen eigenen Mollie-Handoff. Nutzen Sie diese Methode nur als servererlaubte Auswahl und führen Sie danach den vom Server angebotenen Checkout-Ablauf fort.
Bestellung auslösen
Checkout-Erklärungen übermitteln
Lesen Sie unmittelbar vor dem Abschluss erneut checkout/eligibility und rendern Sie legalDeclarations[] dynamisch. Wählen Sie Erklärungen nie voraus. Senden Sie im place-order-Body nur Erklärungen, die der Nutzer aktiv akzeptiert hat, inklusive der vom Server gelieferten Textversion.
{
"declaredContractingCapacity": "professional",
"acceptedDeclarations": [
{
"key": "non_consumer_status",
"textVersion": "tenant-notice-version",
"accepted": true
}
]
}Der Server unterscheidet aktuell diese Fälle:
| Fall | Voraussetzung |
|---|---|
| Verifizierter Firmenkontext | Der Cart trägt eine serverseitig bestätigte companyId; eine passende Legal Declaration ist Pflicht. |
| Selbstdeklaration | Der Checkout enthält eine Firmenangabe und der Nutzer erklärt eine berufliche oder gewerbliche Abschlusskapazität. |
Ein Gast mit Firmenangabe bleibt ohne aktive Erklärung als Verbraucher klassifiziert. Bei fehlender Pflichterklärung antwortet der Server mit LEGAL_DECLARATIONS_REQUIRED. Bei fehlender oder abweichender Textversion antwortet er mit LEGAL_DECLARATION_INVALID.
Setzen Sie declaredContractingCapacity nur, wenn der Nutzer im Checkout bewusst als beruflich, gewerblich oder öffentlich-rechtlich handelnd auftreten will. Nutzen Sie für normale Verbraucher keinen künstlichen B2B-Wert. Fragen Sie bei Selbstdeklaration mindestens eine Firmenangabe ab; der Server lehnt eine berufliche Abschlusskapazität ohne Firmenkontext ab.
Wenn eine Legal Declaration wegen Betreiber-Konfiguration nicht vollständig auflösbar ist, zum Beispiel wegen fehlender referenzierter Hinweise, liefert der Server die Erklärung nicht als kundenseitigen Hardblock aus. Die betroffene Declaration kann operatorAttentionRequired=true und configurationIssueCodes[] enthalten. Rendern Sie daraus keine zusätzliche Pflicht-Checkbox. Lassen Sie den Checkout weiterlaufen und zeigen Sie nur Erklärungen als Pflicht an, die der Server mit required=true oder blocking=true liefert und nicht operatorAttentionRequired=true tragen.
Ein robustes Frontend kann die Erklärungsliste so vorbereiten:
function checkoutDeclarationsForUI(legalDeclarations = []) {
return legalDeclarations
.filter((item) => !item.operatorAttentionRequired)
.filter((item) => item.required || item.blocking)
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || a.key.localeCompare(b.key))
.map((item) => ({
key: item.key,
label: item.text,
textVersion: item.textVersion,
noticeRefs: item.noticeRefs ?? []
}));
}
function acceptedDeclarationsFromState(requiredItems, checkedByKey) {
return requiredItems
.filter((item) => checkedByKey[item.key] === true)
.map((item) => ({
key: item.key,
textVersion: item.textVersion,
accepted: true
}));
}Prüfen Sie vor dem Senden, dass jede sichtbare Pflicht-Erklärung akzeptiert ist. Wenn nach einem Refresh neue oder andere textVersion-Werte erscheinen, werfen Sie alte lokale Checkbox-Zustände weg und lassen Sie den Nutzer erneut zustimmen.
Verwenden Sie Text und Textversion aus Checkout Eligibility beziehungsweise aus der Commerce-Legal-Konfiguration. Stimmen Sie die finalen Rechtstexte vor dem produktiven Einsatz rechtlich ab.
POST /api/v1/public/v1/checkout/place-order
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"declaredContractingCapacity": "professional",
"acceptedDeclarations": [
{
"key": "non_consumer_status",
"textVersion": "tenant-notice-version",
"accepted": true
}
]
}Beispielantwort:
{
"orderId": "uuid",
"orderNumber": "ORD-2026-000001",
"status": "placed"
}Zeigen Sie nach Erfolg eine Bestätigung. Markieren oder löschen Sie den lokalen Cart Token. Starten Sie danach den passenden Zahlungs- oder Danke-Seiten-Flow.
Behandeln Sie Fehler nach Verantwortlichkeit:
| Code | Reaktion im eigenen Shop |
|---|---|
CHECKOUT_NOT_ELIGIBLE | Lesen Sie Eligibility neu und führen Sie den Nutzer zu Adresse, E-Mail, Zahlart, Bestand oder Review zurück. |
LEGAL_DECLARATIONS_REQUIRED | Lesen Sie Eligibility neu, rendern Sie die aktuellen Pflicht-Erklärungen und verlangen Sie aktive Zustimmung. |
LEGAL_DECLARATION_INVALID | Verwerfen Sie lokale Zustimmungsversionen und lassen Sie den Nutzer die aktuellen Texte erneut akzeptieren. |
PRICE_MISMATCH / CART_RECALCULATION_REQUIRED | Aktualisieren Sie den Cart aus der Serverantwort oder führen Sie den Nutzer zurück zum Warenkorb. |
INVENTORY_RESERVATION_EXPIRED / INVENTORY_INSUFFICIENT_STOCK | Führen Sie den Nutzer zum Warenkorb zurück und zeigen Sie Bestandsänderung oder erneute Reservierung an. |
ADDRESS_INVALID | Führen Sie den Nutzer zur Adressauswahl oder Adresseingabe zurück. |
Zeigen Sie bei Public-API-Fehlern keine internen Details, SQL-Meldungen oder Konfigurationsnamen. Nutzen Sie die stabilen Codes für UI-Zustände und senden Sie technische Diagnosedaten nur in Ihre interne, redigierte Fehlererfassung.
Bestellstatus ohne Login anzeigen
Nutzen Sie für öffentliche Bestellstatus-Seiten ausschließlich den Storefront-Endpunkt. Verwenden Sie keine Account-, Admin- oder Commerce- Endpunkte aus dem Browser.
POST /api/v1/public/v1/storefront/orders/status
Content-Type: application/json
{
"orderNumber": "ORD-2026-000001",
"email": "kundin@example.com",
"postalCode": "10115"
}Der Server prüft die Bestellung im aktuellen Storefront-Kontext gegen Bestellnummer, E-Mail und Postleitzahl. Zeigen Sie bei 404 oder ORDER_STATUS_NOT_FOUND eine neutrale Meldung wie „Wir konnten diese Kombination nicht zuordnen“ und fragen Sie keine weiteren personenbezogenen Daten ab.
Beispielantwort:
{
"orderNumber": "ORD-2026-000001",
"orderStatus": "processing",
"paymentStatus": "paid",
"fulfillmentStatus": "partial",
"trackingUrl": "https://tracking.example/status",
"updatedAt": "2026-06-10T12:00:00Z"
}Rendern Sie nur diese Statusdaten. Erwarten Sie keine Positionen, Preise, Rechnungen, Kundendaten oder Adressen in der Antwort. Behandeln Sie 429 als temporären Schutz gegen zu viele Versuche.
Workspace stellt nach erfolgreichem place-order eine Bestelleingangsbestätigung in die Mail-Warteschlange, wenn die Bestellung aus einem Cart stammt. Für invoice und prepayment passiert das direkt nach dem Bestelleingang. Für PayPal wartet Workspace auf einen gespeicherten ausstehenden oder erfolgreichen PayPal-Zahlungsstatus. Der API-Erfolg wartet nicht auf SMTP-Zustellung.
Die Mail ist idempotent pro Bestellung. Wiederholte Browser-Retrys oder doppelte Payment-Callbacks erzeugen keine zweite aktive Bestelleingangsbestätigung. Bei Verbraucherbestellungen enthält die Mail Widerrufsbelehrung und Muster-Widerrufsformular als Anhänge.
Danke-Seite, UX und Mailvertrag
Trennen Sie Browser-UX und Mailversand sauber. Der Storefront-Client zeigt nach place-order eine Danke-Seite oder einen nächsten Payment-Schritt. Er erzeugt keine eigene Bestelleingangsmail, keinen lokalen Mail-Fallback und keine Kopie der serverseitigen Mailvorlage.
Stimmen Sie Frontend, Design und Texte so ab:
- Die Danke-Seite darf sagen, dass Workspace die Bestellung erhalten hat und eine Bestelleingangsbestätigung folgen kann.
- Bei Rechnung und Vorauskasse darf die Danke-Seite nächste Schritte anzeigen, aber keine Rechnung oder Auftragsannahme versprechen.
- Bei PayPal zeigen Sie eine Zahlungsbestätigung erst nach erfolgreichem Capture. Vorher bleibt der Zahlungsstatus ausstehend.
- Nutzen Sie dieselbe fachliche Terminologie wie die Mail:
Bestelleingang,Zahlungsstatus,Vorauskasse,Rechnung,PayPal. - Vermeiden Sie Layouts, die den Nutzer glauben lassen, die Mailzustellung sei Teil der HTTP-Antwort.
Texter pflegen dauerhafte Mailinhalte in der Benachrichtigungsvorlage commerce/order/receipt, nicht im Storefront-Code. Designer prüfen die Danke-Seite im Frontend und die gerenderte Mail getrennt, weil beide Oberflächen unterschiedliche Medien und Zustellwege haben. Konfiguration, Preview und Platzhalterliste sind in Mail und Benachrichtigungen beschrieben.
Widerruf digital ermöglichen
Bestellrechte und Fristen anbinden
Lesen Sie nach erfolgreicher Bestellung die ausübbaren Rechte serverseitig. Der Server liefert nur Rechte, die aus der gespeicherten Legal-Regime-Konfiguration der Bestellung entstanden sind. Interpretieren Sie Fristen nicht im Storefront-Code neu.
Rufen Sie die Rechte erst nach erfolgreichem place-order auf. Vor der Bestellung existiert noch keine rechtliche Order-Evidence und damit auch keine verlässliche Frist.
Für Gastbestellungen bleibt der Zugriff an den Cart Token gebunden:
GET /api/v1/public/v1/storefront/orders/{orderId}/legal-rights
<Cart-Token-Header>: opaque-cart-tokenFür eingeloggte Konten nutzen Sie den Account-Kontext:
GET /api/v1/public/v1/account/orders/{orderId}/legal-rights
Authorization: Bearer <session>Beispielantwort:
{
"items": [
{
"rightKey": "withdrawal",
"status": "active",
"countryCode": "DE",
"legalRegimeKey": "eu_consumer_rights",
"legalRegimeVersion": "2026-05-29.v1",
"startTrigger": "receipt_mail_sent",
"startsAt": "2026-05-29T10:00:00Z",
"endsAt": "2026-06-12T10:00:00Z"
}
]
}Verwenden Sie die Statuswerte so:
| Status | Verhalten im eigenen Shop |
|---|---|
active | Zeigen Sie die Ausübungsfunktion an, wenn das Recht zum aktuellen Kontext passt. |
pending_start | Zeigen Sie höchstens einen Hinweis, dass die Frist noch nicht begonnen hat. Senden Sie keine Ausübung. |
expired | Zeigen Sie keine Ausübungsfunktion. |
exercised | Zeigen Sie den eingegangenen Antrag oder eine neutrale Bestätigung, wenn exerciseSubmittedAt vorhanden ist. |
not_applicable / excluded | Zeigen Sie keine Ausübungsfunktion. Nutzen Sie exclusionReasonKey nur für interne oder rechtlich freigegebene Hinweise. |
Zeigen Sie Fristen aus startsAt und endsAt an, wenn beide Werte vorhanden sind. Fehlt endsAt, versprechen Sie keine lokale Ersatzfrist. Bei startTrigger=shipment_delivered, receipt_mail_sent oder einem anderen serverseitigen Trigger wartet Workspace auf den passenden Lifecycle-Fakt und aktualisiert das Recht danach.
Zeigen Sie eine Rechtsausübung nur für passende aktive Rechte an. Senden Sie den Wunsch des Nutzers an den Server und fragen Sie nur die nötigen Kontaktdaten ab:
POST /api/v1/public/v1/storefront/orders/{orderId}/legal-rights/{rightKey}/exercise
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"submitterEmail": "kunde@example.test",
"submitterName": "Ada Buyer",
"comment": "Ich widerrufe den Vertrag."
}Die Account-Variante nutzt denselben Body:
POST /api/v1/public/v1/account/orders/{orderId}/legal-rights/{rightKey}/exercise
Authorization: Bearer <session>
Content-Type: application/jsonBei Erfolg speichert Workspace die Ausübung, markiert das Recht als ausgeübt und reiht eine Bestätigungsmail über die Vorlage commerce/order/legal-right-exercise-confirmation ein. Behandeln Sie LEGAL_RIGHT_NOT_FOUND, LEGAL_RIGHT_NOT_EXERCISABLE, LEGAL_RIGHT_ALREADY_SUBMITTED und LEGAL_EXERCISE_SUBMITTER_REQUIRED als stabile Client-Codes.
Nach 201 Created können Sie die Rechte erneut lesen. Die Antwort enthält dann exerciseRecordId und exerciseSubmittedAt. Speichern Sie keine zweite lokale Ausübungsakte im Shop-System; Workspace ist die führende Quelle für Rechtsausübung, Status und Bestätigungsmail.
Commerce-Legal-Integration testen
Testen Sie die Legal-Integration vor dem Livegang mit echten Storefront-API- Aufrufen und einer fachlich freigegebenen Testkonfiguration. Nutzen Sie keine lokal nachgebauten Regeln, weil Sie damit andere Ergebnisse erzeugen können als der spätere Server-Checkout.
Prüfen Sie mindestens diese Szenarien:
- Verbraucherbestellung mit physischem Artikel: Eligibility liefert die erwarteten Pflicht-Erklärungen,
place-orderakzeptiert nur die aktuelletextVersion, und die Bestellung zeigt in Workspace eine vollständige Legal-Zusammenfassung. - Verifizierter Firmenkontext: Der Cart trägt eine bestätigte
companyId, der Shop zeigt die passenden B2B-Erklärungen, und fehlende Zustimmung führt zuLEGAL_DECLARATIONS_REQUIRED. - Selbstdeklaration: Der Gast gibt eine Firma an, wählt eine berufliche Abschlusskapazität und akzeptiert die passende Erklärung. Ohne Firmenangabe oder mit falscher Textversion lehnt der Server den Abschluss ab.
- Betreiber-Konfigurationslücke: Entfernen Sie in einer Testumgebung eine referenzierte Legal Notice. Der Shop darf daraus keine Pflicht-Checkbox bauen und darf den Checkout nicht blockieren. Die Bestellung muss danach in Workspace als unvollständige Legal-Bewertung mit Betreiberhinweis sichtbar sein.
- Friststart: Erzeugen Sie ein Recht mit
order_placed,receipt_mail_sentodershipment_deliveredals Startauslöser und prüfen Sie, dassstartsAt,endsAtundstatuserst aus der Serverantwort angezeigt werden. - Rechtsausübung: Senden Sie eine Ausübung für ein aktives Recht und prüfen Sie danach
exerciseRecordId,exerciseSubmittedAtund die eingereihte Bestätigungsmail.
Führen Sie diese Tests erneut aus, wenn Sie Rechtstexte, Textversionen, Regime-Regeln, Produkt-Rechtsprofile, Länder- oder Steuerlogik oder Storefront-Rendering ändern. Prüfen Sie zusätzlich die Commerce-Legal-Readiness im Admin, bevor Sie einen Kanal produktiv öffnen.
PayPal-Handoff ausführen
Führen Sie den PayPal-Handoff nur aus, wenn der Nutzer PayPal als Zahlart gewählt hat, ein aktiver Zahlungsanbieter konfiguriert ist und place-order erfolgreich war. Der Flow bleibt an den Cart Token und die erzeugte Bestellung gebunden:
- Rufen Sie Setup für die Bestellung auf.
- Nutzen Sie die PayPal SDK-Daten aus der Antwort und lassen Sie PayPal die Order freigeben.
- Senden Sie die PayPal
orderIdan Capture. - Zeigen Sie erst nach erfolgreichem Capture die Zahlungsbestätigung.
Setup:
POST /api/v1/public/v1/checkout/paypal/setup
<Cart-Token-Header>: opaque-cart-token
X-Idempotency-Key: optional-opaque-key
Content-Type: application/json
{
"orderId": "uuid"
}Beispielantwort:
{
"orderId": "uuid",
"provider": "paypal",
"actionType": "REQUIRE_PAYPAL_BUTTONS",
"idempotencyKey": "uuid",
"amount": {
"value": "10.00",
"currency": "EUR"
},
"paypal": {
"clientId": "public-client-id",
"orderId": "PAYPAL-ORDER-ID"
}
}Setup darf X-Idempotency-Key mitführen. Speichern Sie die PayPal orderId aus der Antwort nur für den laufenden Checkout-Schritt und übergeben Sie sie an das PayPal SDK.
Nach erfolgreichem Setup kann Workspace die Bestelleingangsbestätigung mit ausstehendem PayPal-Zahlungsstatus einreihen. Zeigen Sie im Frontend trotzdem erst nach PayPal-Freigabe und erfolgreichem Capture eine Zahlungsbestätigung.
Capture:
POST /api/v1/public/v1/checkout/paypal/capture
<Cart-Token-Header>: opaque-cart-token
X-Idempotency-Key: opaque-capture-key
Content-Type: application/json
{
"orderId": "uuid",
"paypalOrderId": "PAYPAL-ORDER-ID"
}X-Idempotency-Key ist für Capture Pflicht. Verwenden Sie für wiederholte Capture-Versuche derselben PayPal-Approval denselben Key. Erzeugen Sie keinen neuen Key für Browser-Retry, Reload oder Netzwerk-Wiederholung, solange sich orderId und paypalOrderId nicht ändern.
Nach erfolgreichem Capture versucht Workspace erneut, die Bestelleingangsbestätigung zu korrelieren. Wenn bereits ein Mail-Job aus dem PayPal-Setup existiert, bleibt dieser bestehen und es entsteht kein Duplikat.
PayPal-Webhooks konfigurieren
Hinterlegen Sie in der PayPal-App die Webhook-URL Ihrer Workspace-Instanz:
https://<workspace-domain>/api/v1/webhooks/payment/paypalWählen Sie nicht All Events. Aktivieren Sie für den Standard-Checkout diese Events:
CHECKOUT.ORDER.APPROVEDCHECKOUT.ORDER.COMPLETEDPAYMENT.CAPTURE.COMPLETEDPAYMENT.CAPTURE.DENIEDPAYMENT.CAPTURE.PENDINGPAYMENT.CAPTURE.REFUNDEDPAYMENT.CAPTURE.REVERSED
Aktivieren Sie Refund-Events nur, wenn Sie Refunds über PayPal auswerten:
PAYMENT.REFUND.COMPLETEDPAYMENT.REFUND.DENIEDPAYMENT.REFUND.PENDINGPAYMENT.REFUND.FAILED
Aktivieren Sie Dispute-Events nur, wenn Sie Disputes im Commerce-Prozess nachverfolgen:
CUSTOMER.DISPUTE.CREATEDCUSTOMER.DISPUTE.UPDATEDCUSTOMER.DISPUTE.RESOLVED
Wenn PayPal statt einzelner Events nur Kategorien zeigt, wählen Sie Checkout, Payments & Payouts und bei Bedarf Customer dispute. Wählen Sie auch dann nicht All Events.
Angebot statt Checkout
Nutzen Sie Quote From Cart, wenn Checkout nicht erlaubt ist, Preise nur auf Anfrage verfügbar sind oder ein manueller Review nötig ist:
POST /api/v1/public/v1/quotes/from-cart
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json
{
"contactEmail": "kunde@example.test",
"contactName": "Ada Buyer",
"companyName": "Beispiel GmbH",
"message": "Bitte Angebot erstellen.",
"consentAccepted": true,
"honeypot": "",
"filledSeconds": 12
}Beispielantwort:
{
"quoteId": "uuid",
"reference": "REQ-12345678",
"status": "open"
}Behandeln Sie den Cart nach erfolgreicher Angebotsanfrage als eingereicht. Bearbeiten Sie ihn nicht weiter und nutzen Sie den alten Cart Token nicht für weitere Checkout- oder Warenkorbänderungen.
Der Server prüft den Cart Token, bevor gemeinsame Submission-Buckets belastet werden. Behandeln Sie 429 Too Many Requests als neutrale temporäre Ablehnung und 413 Payload Too Large als Hinweis auf eine zu große Anfrage. Zeigen Sie keine Rate-Limit-, Bucket- oder Risikodetails an.
Leiten Sie aus Formularangaben keine CRM-Firma, Preisliste oder Angebotsnummer ab. Der Server validiert und erstellt den Review-Kontext.