Kundenadressen

Von Sascha Silbermann, 16. August 2026

Technische Dokumentation zur Adressbuch-Schnittstelle des Moduls gfs_api. Alle Endpoints operieren auf der xt:Commerce-Tabelle customers_addresses und werden über die Domain-Klassen Customer, CustomerAddresses und CustomerAddress gekapselt.

Controller: src/Controller/CustomerApiController.php Collection: src/Shop/Customer/CustomerAddresses.php Entität: src/Shop/Customer/CustomerAddress.php
1. Endpoint-Übersicht
Methode Pfad Handler Permission
GET /rest/customer/{id}/addresses getCustomerAddresses read customer
GET /rest/customer/{id}/address/{addressId} getCustomerAddress edit customer
POST /rest/customer/{id}/address createCustomerAddress edit customer
PATCH /rest/customer/{id}/address/{addressId} updateCustomerAddress edit customer
PATCH /rest/customers/{id}/addresses updateCustomerAddresses edit customer
DELETE /rest/customer/{id}/address/{addressId} deleteCustomerAddress edit customer

{id} = xt:Commerce-Kundennummer (customers.customers_id), {addressId} = customers_addresses.address_book_id.

Alle Endpoints setzen OAuth2-Auth voraus (Standard des Moduls). 401 kommt bei fehlendem/abgelaufenem Token, 403 bei fehlender Permission.


2. Datenmodell 2.1 API-Feld ↔ DB-Spalte
API-Feld DB-Spalte Typ Pflicht
id address_book_id int serverseitig
customerId customers_id int ✔ (auto-gesetzt)
gender customers_gender string (m/f/"")
birthday customers_dob string|null
company customers_company string
company2 customers_company_2 string
company3 customers_company_3 string
firstname customers_firstname string
lastname customers_lastname string
street customers_street_address string (auto-berechnet aus streetName+streetNumber)
streetName customers_address_street_name string
streetNumber customers_address_street_no string
suburb customers_suburb string
postcode customers_postcode string
city customers_city string
country customers_country_code string (ISO-alpha-2, z.B. DE)
state customers_federal_state_code int|null
phone customers_phone string
fax customers_fax string
mobile customers_mobile_phone string
notice bemerkung string
type address_class string siehe §2.2
default address_default int (0/1)
created date_added string (Y-m-d H:i:s) auto-gesetzt beim POST
modified last_modified string (Y-m-d H:i:s) auto-gesetzt bei save

Pflichtfelder (CustomerAddress::REQUIRED_FIELDS): customerId, firstname, lastname, city, country, postcode, streetName, streetNumber.

2.2 Adress-Typ (type) und Default-Kennzeichen type = 'default' → Rechnungsadresse. In customers_addresses gilt die Konvention: nur eine Adresse pro Kunde darf address_default = 1 tragen. Die Entität CustomerAddress::save() setzt vor dem eigenen UPDATE automatisch alle anderen Adressen des Kunden auf address_default = 0, wenn type = 'default' gesetzt wird. Vorsicht — die Konsistenz wird nur beim save()-Pfad garantiert, nicht bei direkten DB-Änderungen. Andere type-Werte (typischerweise 'shipping', '') sind Zusatz-/ Lieferadressen. 2.3 street vs. streetName/streetNumber

Beim Konstruieren einer CustomerAddress wird street immer neu aus trim(streetName . ' ' . streetNumber) gebaut. Ein direkt gelieferter street-Wert wird also überschrieben. Für Änderungen immer streetName und streetNumber senden.


3. Endpoints im Detail 3.1 GET /rest/customer/{id}/addresses — alle Adressen

Response 200:

{
  "addresses": {
    "1001": { "id": 1001, "customerId": 42, "type": "default", ... },
    "1002": { "id": 1002, "customerId": 42, "type": "shipping", ... }
  },
  "code": 200
}

Der äußere addresses-Wert ist ein Objekt (assoziativ) mit address_book_id als Schlüssel — nicht ein Array. toArray() in CustomerAddresses:166-172.

404 (Kunde unbekannt):

{ "error": "Customer not found", "code": 404, "customerId": 42 }

3.2 GET /rest/customer/{id}/address/{addressId} — einzelne Adresse

Response 200:

{
  "address": { "id": 1001, "customerId": 42, ... },
  "code": 200
}

404-Varianten:

Kunde unbekannt → { "error": "Customer not found", "customerId": 42, "code": 404 } Adresse existiert nicht bei diesem Kunden → { "error": "Address not found", "addressId": 1001, "code": 404 }

Die Adresse muss dem Kunden gehören — es werden nur Adressen aus getAddresses() geliefert, d.h. Filter über customers_id (siehe CustomerAddresses::load()).


3.3 POST /rest/customer/{id}/address — neue Adresse

Request-Body (JSON): alle Pflichtfelder aus §2.1 setzen. customerId wird serverseitig auf {id} überschrieben; created/modified werden auto-gesetzt, wenn nicht mitgeliefert.

{
  "type": "shipping",
  "gender": "m",
  "firstname": "Max",
  "lastname": "Mustermann",
  "streetName": "Musterstraße",
  "streetNumber": "12a",
  "postcode": "12345",
  "city": "Musterstadt",
  "country": "DE"
}

Response 201:

{
  "message": "Address created successfully",
  "address": { "id": 1003, "customerId": 42, ... },
  "code": 201
}

Fehler:

404 wenn Kunde nicht existiert. 500 bei DB-Fehler; Exception-Message wird durchgereicht ("Failed to create address: ..."). Häufigste Ursache: fehlende Pflichtfelder oder Constraint-Violations in customers_addresses.
3.4 PATCH /rest/customer/{id}/address/{addressId} — Adresse ändern

Request-Body (JSON, alle Felder optional): nur die zu ändernden Felder senden. Unbekannte Schlüssel werden verworfen (Filter über array_key_exists($key, $address->getProperties())).

{
  "streetName": "Neue Straße",
  "streetNumber": "5",
  "postcode": "54321"
}

modified wird auto-gesetzt. Ein type = "default"-Update triggert das in §2.2 beschriebene Reset-Update aller anderen Adressen — der Kunde hat danach garantiert genau eine Default-Adresse.

Response 200:

{
  "message": "Address updated successfully",
  "address": { "id": 1001, ... },
  "code": 200
}

Fehler: 404 (Kunde/Adresse), 500 (DB).


3.5 PATCH /rest/customers/{id}/addresses — Bulk-Replace ⚠

WICHTIG — destruktive Semantik: Anders als der PATCH auf eine einzelne Adresse ist dieser Endpoint kein partieller Merge, sondern ein kompletter Ersatz aller Adressen des Kunden. CustomerAddresses::setEntities() löscht zuerst ALLE bestehenden Zeilen aus customers_addresses (per DELETE ... WHERE customers_id = {id}) und legt anschließend die übergebenen Adressen neu an.

Bei id-Angabe im Request wird die ursprüngliche address_book_id beibehalten, sofern sie vor dem Delete existierte — sonst vergibt MySQL eine neue.

Request-Body: JSON-Array von Adress-Objekten (jeweils wie §3.3).

[
  { "id": 1001, "type": "default", "firstname": "Max", ... },
  { "id": 1002, "type": "shipping", "firstname": "Max", ... },
  { "type": "shipping", "firstname": "Max", ... }
]

Response 200:

{
  "message": "Addresses updated successfully",
  "addresses": { "1001": {...}, "1002": {...}, "1003": {...} },
  "code": 200
}

Empfohlen: Diesen Endpoint nur nutzen, wenn die WaWi/das externe System den vollständigen Adressbestand konsistent hält. Für inkrementelle Änderungen ist der Einzel-PATCH (§3.4) sicherer.


3.6 DELETE /rest/customer/{id}/address/{addressId} — Adresse löschen

Löscht die Adresse aus customers_addresses (DELETE ... WHERE customers_id = {id} AND address_book_id = {addressId}) und entfernt sie aus der In-Memory-Collection (CustomerAddresses::deleteAddress()).

Response 200:

{ "message": "Address deleted successfully", "code": 200 }

Fehler: 404 (Kunde/Adresse), 500 (DB).

Nicht abgefangen (bekannte Einschränkung):

Löschen der aktuellen Default-Adresse hinterlässt einen Kunden ohne Default-Adresse. Konsumenten sollten in diesem Fall selbst eine neue Default setzen (§3.4 mit type = "default"). Referenzielle Integrität zu Bestellungen: orders-Zeilen halten Adressen als Snapshot separat, ein Löschen einer Adresse beeinträchtigt bestehende Bestellungen nicht.
4. Verhalten und Grenzen im Überblick
Verhalten Ort
customerId wird beim POST immer auf den Path-Parameter überschrieben CustomerApiController.php:222
created und modified werden auto-gesetzt (Y-m-d H:i:s) CustomerApiController.php:216-221, CustomerAddress.php:216-233
Nur bekannte Felder aus EDITABLE_FIELDS werden geschrieben CustomerAddress.php:164-190, CustomerApiController.php:281-285
street wird immer aus streetName + streetNumber gebaut CustomerAddress.php:203-209
Genau eine Adresse pro Kunde darf default = 1 sein — type=default triggert Reset CustomerAddress.php:258-266
Bulk-PATCH löscht vorher ALLE Adressen des Kunden CustomerAddresses.php:134-144
Adressen werden bei Kundenanlage nicht implizit erzeugt

5. Statuscodes
Code Bedeutung
200 GET / PATCH / DELETE erfolgreich
201 POST erfolgreich (neue Adresse angelegt)
401 Kein/ungültiges OAuth2-Token
403 Token hat nicht die erforderliche Permission (read customer bzw. edit customer)
404 Kunde oder Adresse nicht gefunden — Response enthält customerId bzw. addressId
500 Interner Fehler beim Schreiben/Löschen — Response enthält Exception-Message unter error

6. Beispiel-Sequenz (curl)
TOKEN="..."   # OAuth2-Bearer-Token
BASE="https://api.gfs-topshop.de"
CID=42

# 1) Alle Adressen holen
curl -H "Authorization: Bearer $TOKEN" \
     "$BASE/rest/customer/$CID/addresses"

# 2) Neue Lieferadresse anlegen
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{
       "type": "shipping",
       "firstname": "Max", "lastname": "Mustermann",
       "streetName": "Musterstraße", "streetNumber": "12a",
       "postcode": "12345", "city": "Musterstadt", "country": "DE"
     }' \
     "$BASE/rest/customer/$CID/address"

# 3) Adresse aktualisieren (nur geänderte Felder)
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{ "streetNumber": "12b" }' \
     "$BASE/rest/customer/$CID/address/1003"

# 4) Adresse löschen
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
     "$BASE/rest/customer/$CID/address/1003"
Kategorie