feat: DHL-Sendungsverlauf direkt auf der Tracking-Seite anzeigen

/bestellung-verfolgen holt den Sendungsverlauf serverseitig ueber die
DHL Shipment Tracking - Unified API und zeigt ihn im eigenen Layout an,
statt Kundschaft auf dhl.de weiterzuleiten.

- lib/dhl-tracking.ts: Abfrage mit API-Key, Zod-Validierung, Timeout,
  Cache pro Sendung (unterwegs 10 min, zugestellt 12 h, Fehler 2 min)
- Abfrage erst nach erfolgreicher Pruefung von Bestellnummer + E-Mail,
  damit die Seite nicht als offener Proxy zu DHL dient
- Ohne freigeschalteten Key bleibt die Seite unveraendert (Nummer + Link)
- Optional DHL_TRACKING_API_KEY, falls das Tracking in eigener DHL-App liegt
- Datenschutzerklaerung um die Statusabfrage bei DHL ergaenzt

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 03:27:58 +02:00
1 parent 2659db64bd
commit 01eaca39dc
10 files changed
+674 -5

No files matched your search

+29 -1
View File
@@ -4,7 +4,9 @@ import { Container, SectionHeading, Button } from "@/components/ui";
import { OrderStatusBadge, OrderStatusTimeline } from "@/components/account-ui";
import { getOrderForTracking } from "@/lib/account-data";
import { formatPrice, formatDate } from "@/lib/format";
import { getCarrierTrackingUrl } from "@/lib/shipment-tracking";
import { getCarrierTrackingUrl, isDhlCarrier } from "@/lib/shipment-tracking";
import { getDhlTracking, type DhlTrackingResult } from "@/lib/dhl-tracking";
import { ShipmentTrackingHistory } from "@/components/shipment-tracking-history";
import { rateLimit, getClientIp } from "@/lib/rate-limit";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
@@ -32,6 +34,18 @@ export default async function TrackOrderPage(props: PageProps<"/bestellung-verfo
}
}
// Erst NACH erfolgreicher Pruefung von Bestellnummer + E-Mail bei DHL
// nachfragen: sonst liesse sich diese Seite als offener Proxy fuer beliebige
// Sendungsabfragen missbrauchen und das DHL-Kontingent leerraeumen.
let dhlTracking: DhlTrackingResult = { kind: "disabled" };
if (order?.shipment?.trackingNumber && isDhlCarrier(order.shipment.carrier)) {
dhlTracking = await getDhlTracking({
trackingNumber: order.shipment.trackingNumber,
postalCode: order.zip,
locale,
});
}
return (
<div className="py-20">
<Container className="max-w-xl">
@@ -150,6 +164,20 @@ export default async function TrackOrderPage(props: PageProps<"/bestellung-verfo
</p>
) : null}
<ShipmentTrackingHistory
result={dhlTracking}
locale={locale}
labels={{
heading: t.shipmentInfo.historyHeading,
ariaLabel: t.shipmentInfo.historyAriaLabel,
estimatedDelivery: t.shipmentInfo.estimatedDelivery,
notYetScanned: t.shipmentInfo.notYetScanned,
unavailable: t.shipmentInfo.historyUnavailable,
dataSource: t.shipmentInfo.dataSource,
statuses: t.shipmentInfo.statuses,
}}
/>
<div className="mt-5 flex items-center justify-between gap-4">
<p className="text-forest">{formatPrice(order.totalCents, order.currency, locale)}</p>
{order.invoice ? (