12 KiB
Packstation Return-Label Restriction
Datum: 23.01.2026
Status: ✅ Implementiert
Problem
User-Feedback:
"Wenn ich ein Label habe aus einer Packstation, kann ich kein Return Label erzeugen."
Fehler:
Fehler beim Erstellen des Retourenlabels: DHL API error (400): 0 of 1 shipment successfully printed.
Root Cause
DHL Packstation Limitierung
Packstationen sind Einwegadressen:
- ✅ Sendungen AN eine Packstation: Erlaubt
- ❌ Sendungen VON einer Packstation: Nicht erlaubt
Return-Label Logik
Bei einem Return-Label werden Sender/Empfänger vertauscht:
Original-Label:
Sender: Shop (Musterstraße 1, Berlin)
Empfänger: Kunde (Packstation 145, Hamburg) ✅
Return-Label (vertauscht):
Sender: Kunde (Packstation 145, Hamburg) ❌
Empfänger: Shop (Musterstraße 1, Berlin)
Problem: Packstation kann nicht als Sender verwendet werden!
DHL API Verhalten
Request Payload (Fallback via ShippingService)
{
"profile": "STANDARD_GRUPPENPROFIL",
"shipments": [
{
"product": "V01PAK",
"shipper": {
"name1": "Max Mustermann",
"addressStreet": "Packstation",
"addressHouse": "145",
"postalCode": "20095",
"city": "Hamburg",
"country": "DEU",
"postNumber": "1234567890" // ❌ Packstation als Sender!
},
"consignee": {
"name1": "Mein Shop",
"addressStreet": "Musterstraße",
"addressHouse": "1",
"postalCode": "10115",
"city": "Berlin",
"country": "DEU"
}
}
]
}
DHL API Response
{
"Status": {
"statusCode": 400,
"statusText": "Bad Request"
},
"labelData": "",
"shipmentNumber": "",
"errors": [
{
"message": "0 of 1 shipment successfully printed."
}
]
}
Lösung
1. Backend-Validation (Controller)
File: app/Http/Controllers/DhlShipmentController.php
public function createReturnLabel(Request $request, DhlShipment $shipment): JsonResponse
{
// ... existing checks ...
// Check if recipient is a Packstation (cannot be used as return sender)
$recipient = is_string($shipment->recipient)
? json_decode($shipment->recipient, true)
: $shipment->recipient;
$hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? '');
if ($hasPostNumber) {
return response()->json([
'success' => false,
'message' => 'Retourenlabels können nicht für Packstation-Sendungen erstellt werden. ' .
'DHL erlaubt keine Sendungen VON einer Packstation. ' .
'Der Kunde muss die Retoure über eine normale Adresse versenden.',
], 422);
}
// ... continue with return label creation ...
}
Prüflogik:
- ✅ Prüft ob
postnumberoderpostNumberim Empfänger-JSON existiert - ✅ Gibt HTTP 422 (Unprocessable Entity) zurück
- ✅ Klare, verständliche Fehlermeldung
2. UI - DataTable (Cockpit)
File: app/Http/Controllers/DhlShipmentController.php
->addColumn('actions', function ($shipment) {
// ...
// Return label button (only for non-Packstation outbound shipments)
if ($shipment->type == 'outbound' && ! $shipment->returns()->count()) {
$recipient = is_string($shipment->recipient)
? json_decode($shipment->recipient, true)
: $shipment->recipient;
$hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? '');
if (!$hasPostNumber) {
// Regular address: Show active button
$buttons .= '<button type="button" class="btn btn-sm btn-outline-info create-return-btn"
data-shipment-id="' . $shipment->id . '"
data-toggle="tooltip"
title="Retourenlabel erstellen">
<i class="fas fa-undo"></i>
</button>';
} else {
// Packstation: Show disabled button with explanation
$buttons .= '<button type="button" class="btn btn-sm btn-outline-secondary"
disabled
data-toggle="tooltip"
title="Retourenlabels nicht möglich für Packstation-Sendungen">
<i class="fas fa-undo text-muted"></i>
</button>';
}
}
// ...
})
UI-Verhalten:
- ✅ Packstation: Disabled Button mit Tooltip
- ✅ Normale Adresse: Aktiver Button
- ✅ Icon wird grau dargestellt bei Packstation
3. UI - Order Detail View
File: resources/views/admin/sales/_detail_dhl_shipments.blade.php
@if($shipment->type === 'outbound' && !$shipment->returns->count())
@php
$recipient = is_string($shipment->recipient)
? json_decode($shipment->recipient, true)
: $shipment->recipient;
$hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? '');
@endphp
@if(!$hasPostNumber)
<button type="button"
class="btn btn-outline-info dhl-create-return-btn"
data-shipment-id="{{ $shipment->id }}"
title="Retourenlabel erstellen">
<i class="fas fa-undo"></i>
</button>
@else
<button type="button"
class="btn btn-outline-secondary"
disabled
title="Retourenlabels nicht möglich für Packstation-Sendungen">
<i class="fas fa-undo text-muted"></i>
</button>
@endif
@endif
4. UI - Shipment Detail View
File: resources/views/admin/dhl/show.blade.php
@if($shipment->type == 'outbound' && !$shipment->returns->count())
@php
$recipient = is_string($shipment->recipient)
? json_decode($shipment->recipient, true)
: $shipment->recipient;
$hasPostNumber = !empty($recipient['postnumber'] ?? $recipient['postNumber'] ?? '');
@endphp
@if(!$hasPostNumber)
<button type="button"
class="btn btn-info mr-2"
id="create-return-btn"
data-shipment-id="{{ $shipment->id }}">
<i class="fas fa-undo"></i> Retourenlabel erstellen
</button>
@else
<button type="button"
class="btn btn-secondary mr-2"
disabled
title="Retourenlabels nicht möglich für Packstation-Sendungen">
<i class="fas fa-undo"></i> Packstation-Sendung
</button>
<small class="text-muted d-block mb-2">
<i class="fas fa-info-circle"></i>
Retourenlabels können nicht für Packstation-Sendungen erstellt werden,
da DHL keine Sendungen VON einer Packstation erlaubt.
</small>
@endif
@endif
Besonderheit Detail-View:
- ✅ Zusätzlicher Info-Text unter dem Button
- ✅ Icon-Indikator für besseres Verständnis
Packstation-Erkennung
Datenstruktur
DhlShipment Model:
$shipment->recipient = '{
"firstname": "Max",
"lastname": "Mustermann",
"street": "Packstation 145",
"postalcode": "20095",
"city": "Hamburg",
"country": "DE",
"postnumber": "1234567890" // ← Identifikator!
}'
Detection Logic
$recipient = is_string($shipment->recipient)
? json_decode($shipment->recipient, true)
: $shipment->recipient;
// Check both possible keys (case-insensitive)
$hasPostNumber = !empty(
$recipient['postnumber'] ??
$recipient['postNumber'] ??
''
);
if ($hasPostNumber) {
// This is a Packstation delivery
}
Keys geprüft:
postnumber(lowercase)postNumber(camelCase)
Testing
Test 1: Packstation-Sendung - API Call blockiert
# Via Tinker
$shipment = DhlShipment::find(24); // Packstation-Sendung
$controller = new App\Http\Controllers\DhlShipmentController();
$response = $controller->createReturnLabel(new Illuminate\Http\Request(), $shipment);
# Expected Response:
{
"success": false,
"message": "Retourenlabels können nicht für Packstation-Sendungen erstellt werden.
DHL erlaubt keine Sendungen VON einer Packstation.
Der Kunde muss die Retoure über eine normale Adresse versenden."
}
# HTTP Status: 422
Test 2: Packstation-Sendung - Button disabled
Schritte:
- Cockpit öffnen
- Packstation-Sendung suchen (ID 24)
- ✅ Return-Button ist grau/disabled
- ✅ Tooltip zeigt "Retourenlabels nicht möglich für Packstation-Sendungen"
- ✅ Icon ist grau (
text-muted)
Test 3: Normale Sendung - Return funktioniert
Schritte:
- Normale Outbound-Sendung auswählen (kein postNumber)
- ✅ Return-Button ist aktiv/blau
- Button klicken
- ✅ Return-Label wird erfolgreich erstellt
Test 4: Detail-View mit Info-Text
Schritte:
- Packstation-Sendung Detail-View öffnen (ID 24)
- ✅ "Packstation-Sendung" Button ist disabled
- ✅ Info-Text sichtbar unter dem Button
- ✅ Erklärt warum Return nicht möglich ist
Database Check
-- Find all Packstation shipments
SELECT
id,
type,
dhl_shipment_no,
JSON_EXTRACT(recipient, '$.postnumber') as postnumber,
JSON_EXTRACT(recipient, '$.street') as street
FROM dhl_package_shipments
WHERE
type = 'outbound'
AND (
JSON_EXTRACT(recipient, '$.postnumber') IS NOT NULL
OR JSON_EXTRACT(recipient, '$.postNumber') IS NOT NULL
);
Example Results:
id | type | dhl_shipment_no | postnumber | street
----|----------|---------------------------|--------------|------------------
24 | outbound | 0034043333301020021029524 | 1234567890 | "Packstation 145"
User Experience
Vorher (Fehler)
1. User klickt "Retourenlabel erstellen"
2. API Call wird gesendet
3. ❌ DHL API error (400): 0 of 1 shipment successfully printed
4. ❌ Kryptische Fehlermeldung
5. ❌ User weiß nicht warum es fehlschlägt
Nachher (Proaktive Verhinderung)
1. User öffnet Packstation-Sendung
2. ✅ Button ist disabled mit Tooltip
3. ✅ Info-Text erklärt warum nicht möglich
4. ✅ Kein fehlgeschlagener API Call
5. ✅ Klarheit für den User
Alternative Lösungen (nicht implementiert)
Option 1: Shop-Adresse als Return-Empfänger
Idee: Bei Packstation-Sendungen automatisch Shop-Adresse als Rücksendeadresse verwenden.
Problem:
- Kunde muss Paket zur Post/DHL-Filiale bringen
- Nicht transparent für Kunden
- Komplexere Logik
Entscheidung: ❌ Nicht umgesetzt
Option 2: QR-Code Return ohne Label
Idee: DHL QR-Code Return für Packstation-Kunden.
Problem:
- Erfordert separates DHL API Produkt
- Nicht in allen Accounts verfügbar
- Separate Implementierung nötig
Entscheidung: ❌ Nicht umgesetzt (könnte zukünftig ergänzt werden)
Geänderte Dateien
-
✅
app/Http/Controllers/DhlShipmentController.php- Zeile 427-437: Backend-Validation für Packstation-Check
- Zeile 224-236: DataTable Button-Logik mit Packstation-Check
-
✅
resources/views/admin/sales/_detail_dhl_shipments.blade.php- Zeile 108-127: Blade-Logic für Return-Button (disabled bei Packstation)
-
✅
resources/views/admin/dhl/show.blade.php- Zeile 161-182: Detail-View mit disabled Button + Info-Text
Status
✅ Backend-Validation: Implementiert
✅ UI-Anpassungen (Cockpit): Implementiert
✅ UI-Anpassungen (Order Detail): Implementiert
✅ UI-Anpassungen (Shipment Detail): Implementiert
✅ Dokumentation: Erstellt
Nächste Schritte
- Testen: Packstation-Sendung im Cockpit prüfen
- Testen: Return-Button sollte disabled sein
- Testen: Normale Sendung kann weiterhin Return-Label erstellen
- Optional: Kunde über alternative Return-Optionen informieren (Email)
Lessons Learned
- Proaktive UI-Validierung: Buttons disablen ist besser als Fehler zeigen
- Klare Fehlermeldungen: User sollten verstehen WARUM etwas nicht geht
- DHL Limitierungen: Packstationen sind Einweg-Adressen (nur AN, nicht VON)
- Multiple Keys prüfen:
postnumbervspostNumber- beide abfangen