- PHP 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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>
|
||
| _config | ||
| lang | ||
| src | ||
| .gitattributes | ||
| .gitignore | ||
| composer.json | ||
| LICENSE | ||
| README.md | ||
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: '' # 8–10 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
ShipmentServicehat 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 deshalbfalse, 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.