No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
MoritzSauer cafd348e83 Befunde aus dem Code-Review behoben
Fehler:
- de.yml verschluckte {label} in TestEnvironment; der Verbindungstest sagte
  nicht mehr, ob er auf Stage oder Produktion zeigt.
- login() fing die eigene CarrierException aus createClient() per
  catch (Throwable) wieder ein und ersetzte die WSDL-Meldung samt URL durch
  das nichtssagende "DPD war nicht erreichbar.". createClient() steht jetzt
  vor dem try.
- text() lief bei ungueltigem UTF-8 auf trim(null): das Pflichtfeld fiel leer
  aus, clean() entfernte es, und DPD antwortete mit einem rohen
  cvc-complex-type. Die Kodierung wird jetzt vorher als Latin-1 gelesen.

Verhalten:
- Adressfelder brechen ab, wenn die Umschreibung nach ISO 8859-1 Zeichen
  verliert ("Moskwa" kyrillisch wurde zu "??????" und ergab ein gedrucktes,
  aber unzustellbares Etikett - bei DPD nicht stornierbar). Referenz, Telefon
  und Kundennummer bleiben nachsichtig.
- testConnection() behauptet keine Anmeldung mehr, wenn es die verwahrte
  Sitzung genutzt hat, sondern sagt genau das (neu: hasStoredSession()).
- Eigener Zweig .NoParcel statt der Meldung "die Paketscheinnummer  ist
  verbraucht" mit leerer Nummer.

Log:
- Rohantworten werden gefiltert: alles ueber 512 Bytes oder ohne gueltiges
  UTF-8 wird durch eine Groessenangabe ersetzt. Ein Etikett belegte im Fall
  .NoDocument sonst mehrere hundert Kilobyte Logzeile.
- .LoginIncomplete zaehlt die erhaltenen Felder auf, statt die Antwort samt
  authToken zu protokollieren.

Dokumentation:
- Klassenkommentar von DpdClient benennt, dass Drossel und Tageszaehler ohne
  Sperre arbeiten und eine Sammelaktion in mehreren Prozessen sie unterlaeuft.
- Kommentar an MAX_LOGINS_PER_DAY sagt jetzt, was der Code tut: er schoepft
  die zehn Anmeldungen aus, statt einen Puffer zu lassen.
- ext-mbstring in composer.json ergaenzt; README-Beispiel auf das tatsaechliche
  "Lodz" mit erhaltenem o-Akut korrigiert.

Die Envelope-Struktur wurde dabei gegen die Stage-WSDL geprueft (Sequenzen von
parcel, address, addressWithType, generalShipmentData, printOption sowie die
Grenzen von volume und weight) - sie war und ist korrekt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:20:05 +02:00
_config DPD Deutschland ueber die SOAP-Webdienste 2026-08-05 16:19:01 +02:00
lang Befunde aus dem Code-Review behoben 2026-08-06 10:20:05 +02:00
src Befunde aus dem Code-Review behoben 2026-08-06 10:20:05 +02:00
.gitattributes DPD Deutschland ueber die SOAP-Webdienste 2026-08-05 16:19:01 +02:00
.gitignore DPD Deutschland ueber die SOAP-Webdienste 2026-08-05 16:19:01 +02:00
composer.json Befunde aus dem Code-Review behoben 2026-08-06 10:20:05 +02:00
LICENSE DPD Deutschland ueber die SOAP-Webdienste 2026-08-05 16:19:01 +02:00
README.md Befunde aus dem Code-Review behoben 2026-08-06 10:20:05 +02:00

tietge/silverstripe-shipping-dpd

DPD Deutschland für tietge/silverstripe-shipping — Etiketten über die SOAP-Webdienste (Web Connect), ShipmentService/V4_5 und LoginService/V2_0.

Handgeschrieben: Es gibt kein offizielles SDK, die brauchbaren Community-Pakete sind winzig oder aufgegeben. Der Client deckt genau zwei Aufrufe ab — getAuth und storeOrders.

Status in diesem Projekt: vollständig gebaut und gegen die Stage-Umgebung verifiziert (Anmeldung, Depot, echtes Etikett mit !DEMO! und korrektem Gewicht). Produktiv fehlt allein die Kundennummer. Da der Versand auf UPS umgestellt wurde, ruht das Modul — es meldet sich nur an, wenn Zugangsdaten gepflegt sind, und steht sonst nirgends im Weg.

Installation

composer require tietge/silverstripe-shipping-dpd
vendor/bin/sake db:build --flush

Braucht ext-soap und ext-iconv.

Zugangsdaten

Tietge\Shipping\Dpd\DpdCarrier:
  sandbox: true
  delis_id: ''           # 810 Zeichen
  password: ''
  customer_number: ''    # Versender-Kundennummer, numerisch
  products: ['CL']
  message_language: 'de_DE'
  • DelisID und Passwort für die Sandbox stehen im Entwicklerportal (esolutions.dpd.com/entwickler/dpdwebservices.aspx) unter „Benutzerdaten ändern". Kein Antrag nötig.
  • Produktive Zugangsdaten über cit@dpd.de — und das ist keine Datenanfrage, sondern eine Abnahme: „Each development using the DPD Web Connect service must be validated and approved by DPD." Erst gegen Stage fertig werden, dann beantragen.
  • Kundennummer ≠ DelisID. Wer hier die DelisID einträgt, bekommt keinen Anmeldefehler — der Auftrag wird erst beim ersten Etikett abgelehnt.
  • Die Depotnummer wird nicht konfiguriert. Sie kommt aus der Anmeldeantwort und taucht als Präfix der Paketscheinnummer wieder auf.

Ausführlich: docs/versandmodul-dpd-einrichtung.md.

Test und Produktion haben getrennte Nutzerverwaltungen: public-ws-stage.dpd.com gegenüber public-ws.dpd.com. Testetiketten tragen einen !DEMO!-Wasserdruck.

Betriebsregeln, die im Code stecken

DPDs API Developer Guidelines sind mit Sanktion formuliert: „Failure to follow these guidelines may cause an application ban." Drei Regeln schlagen unmittelbar durch:

Regel Grenze Wie das Modul damit umgeht
Anmeldungen 10 pro DelisID und Tag, „calling the login service in combination with each shipment service … is specifically prohibited" Sitzung im PSR-16-Cache; das Modul zählt mit und verweigert die elfte, statt eine Sperre zu riskieren
Neuanmeldung nur bei LOGIN_5 / LOGIN_6 genau diese beiden Codes lösen einen einmaligen zweiten Versuch aus
Token-Laufzeit ein Geschäftstag, endet gegen 3 Uhr Cache läuft passend zum Geschäftstag ab; der Tageszähler ebenfalls, damit er nicht um Mitternacht zehn zusätzliche Anmeldungen freigibt
Versandaufrufe 1/Sekunde, 60/Minute, 3.600/Stunde, immer nacheinander Drossel im Client, damit eine spätere Sammelaktion sie nicht umgehen kann

Praktische Folge: Nie „Verbindung testen" in Serie drücken. Jeder Test verbraucht eine Anmeldung.

Fallstricke, die den Code prägen

Gewicht in 10-Gramm-Einheiten. 300 bedeutet 3 kg. Aus der Schemadoku: „Parcel weight in grams rounded in 10 gram units without decimal point." Ein naives „Gramm einsetzen" bucht das Zehnfache — und das fällt erst auf der Frachtrechnung auf. Am gedruckten Etikett gegengeprüft: 2450 g gesendet als weight: 245, gedruckt „2,45 kg".

HTTP 200 bedeutet nicht Erfolg. DPD legt Beanstandungen als faults in eine formal erfolgreiche Antwort — bis zu drei je Auftrag. Es kann dabei eine Paketscheinnummer ohne PDF zurückkommen; die Nummer ist dann verbraucht. Der Adapter prüft deshalb beides: keine faults und ein vorhandenes Dokument. Die verbrauchte Nummer steht in der Fehlermeldung, damit niemand später eine Sendung sucht, die es nie gab.

Elementreihenfolge ist bindend. Alles ist xsd:sequence; ein Feld an falscher Stelle liefert keinen Fachfehler, sondern einen rohen cvc-complex-type-Fault. Die Reihenfolge ist im Adapter fest kodiert, nicht aus einer Wertetabelle erzeugt.

Feldlängen: Schema ≠ Fachregel. name1, street und city erlauben im XSD 50 Zeichen, die Fachregel nennt 35 — die Schemadoku sagt es selbst. Gekürzt wird auf 35, sonst COMMON_3.

Nur ISO 8859-1 für Adressdaten. „Łódź" wird zu „Lódz", bevor es ins Envelope geht; sonst lehnt DPD mit COMMON_4 ab. Umlaute und nordische Zeichen bleiben, Latin-1 kennt sie — das ó überlebt deshalb, nur ł und ź werden umgeschrieben. Daten, die gar kein gültiges UTF-8 sind, werden vorher als Latin-1 gelesen; sonst fiele das Feld leer aus und DPD meldete einen Schemafehler statt eines Adressfehlers.

splitByParcel=true, damit das PDF immer unter parcelInformation[].output steht — ohne den Schalter liegt es je nach Fall an zwei verschiedenen Stellen im Antwortbaum.

Was DPD nicht kann

  • Kein Storno. Die deutsche ShipmentService hat in allen Fassungen (V3_2, V4_4, V4_5) genau eine Operation: storeOrders. Aus der WSDL ausgelesen, nicht vermutet. Ein Etikett verfällt, wenn das Paket nie eingeliefert wird — supportsCancellation() meldet deshalb false, und der Storno-Knopf erscheint bei DPD-Sendungen gar nicht erst.
  • Kein REST für den Versand. „The shipment service is not available via the REST interface."

Kaufmännisch offen: ob DPD je erzeugtem Etikett oder je eingeliefertem Paket abrechnet. Üblich ist Letzteres, belegt ist es nicht — und weil es kein Storno gibt, ist das der Unterschied zwischen „Fehldruck kostet nichts" und „Fehldruck kostet".

Produktkatalog

CL (DPD CLASSIC) ist der Standardfall. Gültig in V4_5: CL, E830, E12, E18, IE2, PL, MAIL, MAX. E10 und PM4 gibt es nicht mehr — die standen nur bis V4_4 im Enum.

Etikettenformat (A4 oder A6) im CMS unter Website-Einstellungen → Shop → Versand → DPD.