Login, Gastkauf und B2B

Unterstützen Sie anonyme Besucher, Gastkauf, Registrierung, Login und Firmenzugang ohne lokale Freischaltungslogik. Diese Seite passt, wenn Ihr B2B-Onlineshop Firmenpreise, Rechnungskauf, Kundengruppen, Ansprechpartner, Passwortwechsel oder freigegebene Lieferadressen abbilden soll. Der Server entscheidet, welcher kommerzielle Kontext gilt.

Nach der Umsetzung lädt der Client nach Login, Logout und Firmenwechsel die serverseitigen Kontexte neu und zeigt nur Preise, Zahlarten und Checkout-Wege, die Workspace für den aktuellen Besucher freigibt.

Voraussetzungen

Klären Sie vor Login-, Gastkauf- oder Firmenzugangsflows:

Die fachliche Einordnung von Kundengruppen, Commerce-Profilen und Zahlungsbedingungen steht in Handel- und Commerce-Fähigkeiten.

Grundregel

Anonyme Besucher nutzen den öffentlichen Standardkontext des Sales Channels. Nach Login oder Firmenwechsel können sich Preise, Steueranzeige, Verfügbarkeit, Checkout-Fähigkeit, Zahlarten und Angebotsfähigkeit ändern.

Laden Sie nach diesen Ereignissen neu:

Gastkauf

Gastkauf ist nur verfügbar, wenn der Storefront Context oder Checkout Eligibility ihn erlaubt.

Für Gastkauf senden Sie im Checkout:

  • customerEmail
  • billingAddress
  • shippingAddress

Diese Daten sind transaktionale Cart- und Order-Snapshots. Der Client legt daraus keine CRM-Person, kein CRM-Unternehmen und keine CRM-Adresse an.

Wenn Gastkauf nicht erlaubt ist, führen Sie Nutzer zu Login, Registrierung oder Angebotsanfrage.

Registrierung

Registrierung erzeugt zunächst einen normalen Account-Kontext. Sie schaltet keine vorhandenen Firmenpreise, keine Rechnung und keine B2B-Steuerlogik frei.

http
POST /api/v1/public/v1/auth/register
Content-Type: application/json

Senden Sie email und password im JSON-Body. Das Passwort muss mindestens acht Zeichen lang sein.

Nach erfolgreicher Registrierung:

  1. Laden Sie Storefront Context neu.
  2. Laden Sie Cart neu.
  3. Lösen Sie Pricing für sichtbare Produkte neu auf.
  4. Prüfen Sie Checkout Eligibility neu.

Wenn der Nutzer Firmenzugang benötigt, starten Sie eine Firmenzugangs-Anfrage. Leiten Sie aus Firmenname, E-Mail-Domain, Kundennummer oder USt-ID keine automatische Freischaltung ab.

Login, Logout und Passwort

Für Browser-Storefronts nutzen Sie die allgemeinen Auth-Endpunkte. Die öffentlichen Account-Endpunkte unter /api/v1/public/v1/account/... verwenden danach die aktive Browser-Sitzung.

http
POST /api/v1/auth/login
Content-Type: application/json

Senden Sie email und password im JSON-Body. Login erwartet E-Mail und Passwort. Logout beendet die aktuelle Sitzung.

http
POST /api/v1/auth/logout
Content-Type: application/json

{}

Passwort-Wiederherstellung startet mit dem Endpunkt forgot-password.

http
POST /api/v1/auth/forgot-password
Content-Type: application/json

{
  "mail": "kunde@example.test"
}

Der zugesandte Code wird über login-digits geprüft:

http
POST /api/v1/auth/login-digits
Content-Type: application/json

{
  "digits": "123456"
}

Speichern Sie Wiederherstellungscodes nicht in Logs, Analytics oder Support-Nachrichten.

Nach Login kann der Server einen anderen Kontext auflösen:

Nach Logout gilt wieder der öffentliche oder normale anonyme Kontext. Verwerfen oder aktualisieren Sie alle preis- und checkoutrelevanten Client-Daten.

Passwortwechsel ist ein angemeldeter Browser-Session-Vertrag, kein anonymer Storefront-Endpunkt:

http
POST /api/v1/profile/password
Content-Type: application/json

Senden Sie das neue Passwort im Feld password. Der Endpunkt benötigt eine aktive Browser-Sitzung und akzeptiert ein neues Passwort mit mindestens acht Zeichen. Nach einem reinen Passwortwechsel bleibt der kaufmännische Kontext gleich. Laden Sie Preise, Cart und Checkout Eligibility trotzdem neu, wenn der Flow gleichzeitig Login, Logout, Tenant-Auswahl oder Firmenkontext ändert.

Firmenzugang anfragen

Wenn ein Nutzer B2B-Zugang benötigt, senden Sie eine Firmenzugangs-Anfrage:

http
POST /api/v1/public/v1/account/company-access/request
Content-Type: application/json

{
  "companyName": "Beispiel GmbH",
  "contactEmail": "einkauf@beispiel.test",
  "customerNumber": "100123",
  "vatId": "DE123456789",
  "message": "Bitte Zugang prüfen.",
  "pageUrl": "https://shop.example.test/firmenzugang",
  "honeypot": "",
  "filledSeconds": 12
}

Beispielantwort:

json
{
  "state": "requested",
  "requestId": "uuid",
  "status": "pending_review",
  "nextAction": "await_approval"
}

Die Anfrage ist Review-Kontext. Sie wird intern als Firmenzugangs-Eingang und CRM-Lead gespeichert, ist aber keine Marketing- oder Consent-Einwilligung. Sie schaltet keine Preisliste, keine Rechnung und keine Steuerbefreiung frei. Der Server begrenzt zu viele Anfragen und bewertet einfache Spam-Signale wie gefüllte Honeypot-Felder, unrealistisch kurze Füllzeiten und URL-Muster. Senden Sie filledSeconds immer mit; fehlende oder zu niedrige Werte können als zu schnelle Submission gelten. Der Request-Body darf das konfigurierte Limit public.storefront.submission.max_bytes nicht überschreiten.

Freigegebene Firmen laden

Laden Sie verfügbare Firmenkontexte:

http
GET /api/v1/public/v1/account/companies

Beispielantwort:

json
{
  "items": [
    {
      "companyId": "uuid",
      "name": "Beispiel GmbH",
      "displayName": "Beispiel",
      "role": "buyer",
      "storefrontAddressScope": "selected",
      "storefrontAddressIds": ["uuid"]
    }
  ]
}

Wenn keine Firma verfügbar ist, bleibt der Nutzer im normalen Account-Kontext. Rollen steuern, was der Nutzer im Firmenkontext darf:

RolleBedeutung
viewerDarf freigegebene Firmenbestellungen sehen, aber nicht im Firmenkontext einkaufen.
buyerDarf Firmenbestellungen sehen und im Firmenkontext einkaufen.
adminDarf einkaufen und zusätzlich Änderungsanfragen für Firmenstammdaten erfassen.

storefrontAddressScope=all erlaubt alle Lieferadressen der Firma. storefrontAddressScope=selected erlaubt nur die IDs aus storefrontAddressIds. Verwenden Sie diese Felder nur zur Anzeige und Vorauswahl. Der Checkout validiert die Berechtigung serverseitig erneut.

Firmenkontext wählen

http
POST /api/v1/public/v1/account/company-context
<Cart-Token-Header>: opaque-cart-token
Content-Type: application/json

{
  "companyId": "uuid"
}

Beispielantwort:

json
{
  "companyId": "uuid",
  "state": "selected",
  "reviewRequired": true
}

Nach erfolgreicher Auswahl:

  1. Laden Sie Cart neu.
  2. Laden Sie Storefront Context neu.
  3. Lösen Sie Pricing neu auf.
  4. Prüfen Sie Checkout Eligibility neu.
  5. Zeigen Sie Review an, wenn reviewRequired=true.

Senden Sie für B2B-Kontext den vom Server bestätigten Firmenkontext. Erfinden Sie keine companyId und verwenden Sie keine Kundennummer als Firmenkontext.

Eine Identity mit aktivem Firmenzugang wird im Checkout als Firmen-Identity behandelt. Privater Einkauf mit Personenadressen ist für diese Identity nicht vorgesehen und kann mit COMPANY_CONTEXT_REQUIRED abgelehnt werden. Legen Sie für privaten Einkauf eine getrennte Identity ohne aktiven Firmenzugang an.

Einladung für Ansprechpartner annehmen

Ein Ansprechpartner bekommt eine eigene Identity. Es gibt keinen gemeinsamen Firmenlogin.

http
POST /api/v1/public/v1/account/company-invitations/accept
Content-Type: application/json

Senden Sie Einladungs-Token, E-Mail-Adresse und neues Passwort im JSON-Body. Das Passwort muss mindestens acht Zeichen lang sein.

Beispielantwort:

json
{
  "state": "accepted",
  "identityId": "uuid",
  "tenantId": "uuid"
}

Speichern Sie Einladungs-Tokens nicht in Logs oder Analytics. Akzeptieren Sie Einladungen nur mit dem dafür vorgesehenen Formular und der serverseitigen Prüfung.

Rechnung

invoice ist nur verfügbar, wenn der Server sie in Checkout Eligibility oder Storefront Context erlaubt.

Rechnung setzt typischerweise voraus:

Schalten Sie Rechnung nie lokal frei. Wenn invoice nicht erlaubt ist, zeigen Sie erlaubte Zahlarten wie Vorauskasse, PayPal, Kreditkarte oder Angebotsanfrage.

Kundenkonto

Angemeldete Nutzer können Bestellungen und eigene Profildaten über Public Account APIs lesen und pflegen:

http
GET /api/v1/public/v1/account/orders
GET /api/v1/public/v1/account/orders/{orderId}
POST /api/v1/public/v1/account/orders/{orderId}/reorder
GET /api/v1/public/v1/account/products/purchased
GET /api/v1/public/v1/account/product-lists
POST /api/v1/public/v1/account/product-lists
GET /api/v1/public/v1/account/product-lists/{listId}
PATCH /api/v1/public/v1/account/product-lists/{listId}
DELETE /api/v1/public/v1/account/product-lists/{listId}
POST /api/v1/public/v1/account/product-lists/{listId}/items
PATCH /api/v1/public/v1/account/product-lists/{listId}/items/{itemId}
DELETE /api/v1/public/v1/account/product-lists/{listId}/items/{itemId}
GET /api/v1/public/v1/account/profile/contact
PATCH /api/v1/public/v1/account/profile/contact
GET /api/v1/public/v1/account/profile/addresses
POST /api/v1/public/v1/account/profile/addresses
PATCH /api/v1/public/v1/account/profile/addresses/{addressId}
DELETE /api/v1/public/v1/account/profile/addresses/{addressId}
POST /api/v1/public/v1/account/company-change-requests

Nutzen Sie account/products/purchased für den Bereich "Gekaufte Produkte". Der Server leitet diese Liste aus Bestellungen ab und speichert dafür keinen zweiten Historienbestand.

Ohne Firmenkontext liefert der Endpunkt nur persönliche Bestellungen der aktuellen Person, bei denen keine companyId gesetzt ist. Mit dem konfigurierten Firmenkontext-Header liefert er ausschließlich Bestellungen dieser Firma. Der Account braucht dafür die Firmenrolle viewer, buyer oder admin.

Produktlisten bilden die Merkliste und B2B-Standardartikel ab. Persönliche Listen gehören der aktuellen Person. Firmenlisten gehören zur ausgewählten Firma:

  • viewer, buyer und admin dürfen Firmenlisten lesen.
  • Nur admin darf Firmenlisten erstellen, ändern oder löschen.
  • Persönliche Listen darf nur der Besitzer lesen und bearbeiten.

Listeneinträge speichern operative Kundendaten wie eigene Artikelnummer, Notiz, Standardmenge, eigenen Bestand und Zielbestand. Speichern Sie aktuelle Preise und Verfügbarkeit nicht lokal in der Liste. Laden Sie diese Werte bei Bedarf über den Pricing-Endpunkt:

http
POST /api/v1/public/v1/pricing/resolve

Nicht mehr öffentliche Produkte bleiben in gekauften Produkten und Listen auffindbar. Die API liefert dann productState=unavailable und nur gespeicherte Bestell- oder Public-Snapshots. Leiten Sie keine Daten aus internen PIM-, Commerce- oder Admin-Endpunkten ab.

Bereich "Meine Produkte" bauen

Bauen Sie den Kundenkonto-Bereich als Arbeitsansicht, nicht als Marketingseite. Eine robuste Struktur ist:

  • Gekauft: automatisch aus Bestellungen abgeleitete Produkte.
  • Listen: persönliche Listen und, bei aktivem Firmenkontext, Firmenlisten.
  • Listendetail: Tabelle mit gespeicherten Mengen, eigener Artikelnummer, Notiz, eigenem Bestand, Zielbestand und Warenkorb-Aktion.

Laden Sie zuerst den Account-Kontext:

http
GET /api/v1/public/v1/account/companies

Wenn der Nutzer eine Firma auswählt, setzen Sie den Firmenkontext im Shop und senden Sie danach bei Account-Produktlisten den konfigurierten Firmenkontext-Header:

http
<company-context-header>: <companyId>

Laden Sie danach die gekauften Produkte:

http
GET /api/v1/public/v1/account/products/purchased

Nutzen Sie die Antwort für eine tabellarische Ansicht. Wichtige Felder sind:

  • scope: personal oder company
  • companyId: gesetzt bei Firmenkontext
  • variantId
  • pricingTargetType
  • unitOfMeasure
  • sku
  • name
  • productState: public oder unavailable
  • lastOrderedAt
  • lastQuantity
  • totalQuantity
  • orderCount

Laden Sie Produktlisten separat:

http
GET /api/v1/public/v1/account/product-lists

Die Antwort liefert pro Liste visibility, type, name, itemsCount und canEdit. Blenden Sie Bearbeiten-Aktionen nur ein, wenn canEdit=true ist. Bei canEdit=false kann der Nutzer die Liste lesen, aber nicht ändern.

Listen anlegen und pflegen

Erstellen Sie eine persönliche Liste ohne Firmenkontext:

http
POST /api/v1/public/v1/account/product-lists
json
{
  "name": "Essen",
  "type": "wishlist"
}

Erstellen Sie eine Firmenliste mit Firmenkontext:

http
POST /api/v1/public/v1/account/product-lists
<company-context-header>: <companyId>
json
{
  "name": "Standardware",
  "type": "standard",
  "visibility": "company"
}

Nutzen Sie den Listennamen für operative Gruppierungen wie Getränke, Essen, Werkstatt, Filiale München oder Projekt Müller. Dafür brauchen Sie keine Produktkategorien und keine eigenen Produktattribute.

Fügen Sie ein Produkt zu einer Liste hinzu:

http
POST /api/v1/public/v1/account/product-lists/{listId}/items
json
{
  "variantId": "uuid",
  "pricingTargetType": "variant",
  "unitOfMeasure": "PC",
  "defaultQuantity": "6.0000",
  "customerStockQuantity": "12.0000",
  "targetStockQuantity": "24.0000",
  "customerSku": "KUNDE-4711",
  "note": "Standard für Standort München",
  "sortOrder": 10
}

Ändern Sie operative Daten eines Listeneintrags:

http
PATCH /api/v1/public/v1/account/product-lists/{listId}/items/{itemId}
json
{
  "defaultQuantity": "8.0000",
  "customerStockQuantity": "5.0000",
  "targetStockQuantity": "20.0000",
  "note": "Nur für Servicefahrzeuge verwenden"
}

Die API speichert Listeneinträge pro Liste. Dasselbe Produkt darf deshalb in mehreren Listen vorkommen. Innerhalb derselben Liste aktualisiert POST /items den vorhandenen aktiven Eintrag für dieselbe Variante, Pricing-Zielart und Einheit.

Preise, Verfügbarkeit und Warenkorb

Laden Sie Preise und Verfügbarkeit live, wenn Sie Listen anzeigen oder Mengen ändern:

http
POST /api/v1/public/v1/pricing/resolve
json
{
  "items": [
    {
      "variantId": "uuid",
      "pricingTargetType": "variant",
      "quantity": "6.0000",
      "unitOfMeasure": "PC"
    }
  ]
}

Legen Sie ein Produkt aus einer Liste über den normalen Cart-Vertrag in den Warenkorb:

http
POST /api/v1/public/v1/cart/items
json
{
  "variantId": "uuid",
  "pricingTargetType": "variant",
  "quantity": "6.0000",
  "unitOfMeasure": "PC"
}

Der Warenkorb prüft Preis, Verfügbarkeit, Steuer, Versand und Checkout erneut. Die Produktliste gibt dafür keine Freigabe und keinen Preis-Snapshot.

Layout-Idee

Nutzen Sie im Desktop eine dichte Tabellenansicht:

SpalteQuelle
Artikelname, sku, productState
KundenartikelnummercustomerSku
Letzter KauflastOrderedAt aus gekauften Produkten
BestellmengelastQuantity oder defaultQuantity
Eigener BestandcustomerStockQuantity
ZielbestandtargetStockQuantity
Preis / Verfügbarkeitpricing/resolve
AktionWarenkorb, Merken, Entfernen

Auf Mobilgeräten eignet sich eine kompakte Listenzeile pro Artikel:

  • erste Zeile: Artikelname und Verfügbarkeitsstatus
  • zweite Zeile: SKU und Kundenartikelnummer
  • dritte Zeile: Menge, eigener Bestand, Zielbestand
  • Aktionen: Menge ändern, in den Warenkorb legen, aus Liste entfernen

Zeigen Sie leere Zustände handlungsorientiert:

  • Gekauft: "Noch keine gekauften Produkte" mit Link zur Bestellhistorie oder zum Katalog.
  • Listen: "Noch keine Liste" mit Aktion "Liste anlegen".
  • Liste ohne Einträge: "Diese Liste enthält noch keine Produkte" mit Suche oder Kataloglink.

Behandeln Sie productState=unavailable als eigenen Zustand. Zeigen Sie den gespeicherten Namen und die SKU, aber deaktivieren Sie direkte Warenkorb-Aktion oder führen Sie den Nutzer zu einer Anfrage beziehungsweise Ersatzartikelsuche.

Nachbestellung erzeugt immer einen neuen Warenkorb. Der Server übernimmt keine historischen Preise, Rabatte oder Checkout-Entscheidungen, sondern berechnet Preis, Steuer, Verfügbarkeit und Eligibility neu.

Eigene Kontakt- und Adressendpunkte ändern nur die mit der Identity verknüpfte Person. Sie ändern keine Company und keine CompanyAddress. Firmenänderungen laufen über company-change-requests und benötigen die Rolle admin.

USt-ID und Reverse Charge

USt-ID, Kundennummer und Firmenname sind Eingaben für Review und Firmenkontext. Der Browser entscheidet nicht über Reverse Charge, Steuerfreiheit, B2B-Preise oder Rechnung.

Für EU-B2B gilt:

  • Steuerlogik kommt aus dem Serverkontext.
  • USt-ID-Status muss serverseitig gültig oder akzeptiert sein, bevor eine abweichende Steuerlogik gilt.
  • Nicht verfügbare oder unbekannte Prüfung führt nicht automatisch zu Steuerfreiheit.
  • Der Client zeigt nur die serverseitig berechneten Steuer- und Gesamtwerte.

Sicherheitsregeln

  • Kein automatisches Matching auf Firmenname allein.
  • Kein automatisches Matching auf E-Mail-Domain allein.
  • Kein Preislistenwechsel ohne bestätigten Firmenkontext.
  • Kein Rechnungskauf ohne Serverfreigabe.
  • Keine CRM-Trefferlisten in öffentlichen Antworten anzeigen.
  • Keine gemeinsamen Firmenlogins verwenden.
  • Firmen-Rechnungsadresse nicht mit privaten oder fremden Lieferadressen mischen.
  • Keine internen Prüfgründe oder Rohdaten in Fehlermeldungen ausgeben.