Commit Graph
3 Commits
Author SHA1 Message Date
Nick SzaunigandClaude Opus 5 9dd71ffb9a feat: Packstation ueber DHL-Standortsuche als Lieferziel waehlbar
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m8s
Im Checkout laesst sich unter "Versand" jetzt zwischen der eigenen
Anschrift und einer DHL-Packstation waehlen. Postleitzahl eingeben,
suchen, Packstation aus der Trefferliste anklicken, DHL-Postnummer
eintragen — die Auswahl wird an der Bestellung gespeichert.

Aufbau:

- lib/dhl-locations.ts: Client fuer die DHL Location Finder Unified API
  (GET /find-by-address). Bewusst getrennt von lib/dhl.ts — das ist ein
  anderer DHL-Dienst mit anderer Authentifizierung: nur der API-Key im
  Header, kein OAuth und kein Geschaeftskundenvertrag. Die
  Packstation-Auswahl funktioniert damit auch, solange die
  Versandmarken-Erstellung noch nicht eingerichtet ist.
- api/shop/packstationen: serverseitiger Zwischenschritt, damit der
  DHL-API-Key nicht ins Browser-Bundle geraet. Streng rate-limitiert
  (15/Minute statt 60 wie die Produktsuche) und mit 15-Minuten-Cache:
  dahinter steht ein FREMDES Kontingent von 500 Aufrufen pro Tag, das ein
  ungebremstes Suchfeld an einem Nachmittag aufbrauchen koennte.
- Order/Address: packstationNumber + postNumber (Migration
  20260915222938_add_packstation_delivery, beide nullable).
- checkoutSchema: Postnummer als 6-10 Ziffern, plus zwei refine-Regeln —
  ohne Postnummer keine Packstation (DHL verlangt sie zwingend), und eine
  Packstation nur bei Versand, nicht bei Abholung.

Zwei Entscheidungen, die Folgearbeit ersparen:

- Bei Packstation-Auswahl tragen street/zip/city zusaetzlich die lesbare
  Anschrift ("Packstation 145", PLZ, Ort). Dadurch zeigen Rechnung,
  Bestaetigungsmail, Sendungsverfolgung und Admin-Ansicht ohne jede
  Aenderung etwas Sinnvolles; packstationNumber/postNumber tragen daneben
  das, was DHL technisch braucht.
- lib/dhl.ts baut daraus einen Locker-Empfaenger statt einer
  Hausanschrift. DHL kennt fuer den Empfaenger zwei sich ausschliessende
  Formen; eine Packstation als Strassentext zu schicken ergaebe eine Marke
  auf eine Anschrift, die es nicht gibt.

Die Option erscheint nur, wenn DHL_API_KEY gesetzt ist — sonst wuerde
Kundschaft in eine Suche laufen, die immer fehlschlaegt.

Geprueft: sechs Tests fuer das Umsetzen der DHL-Antwort (die
Packstation-Nummer steht in location.keywordId, NICHT in
location.ids[0].locationId — letzteres ist ein Systemcode, mit dem weder
Kundschaft noch DHL etwas anfangen kann). Dazu am laufenden
Produktions-Build: ohne Key liefert der Endpunkt 503 und der Checkout
zeigt die Option gar nicht; mit Key 400 bei ungueltiger PLZ und ein echter
Aufruf an api.dhl.com, der mit 401 quittiert wird — URL, Header und
Anfrageformat stimmen also. Im Browser durchgeklickt: Umschalten blendet
das Strassenfeld aus und Postnummer samt Suche ein, die Suche ruft die
Route und zeigt die Fehlermeldung an.

Feldnamen beider DHL-APIs vorab gegen developer.dhl.com geprueft. Ein
Lauf gegen einen echten Schluessel steht noch aus.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-16 00:37:00 +02:00
Nick SzaunigandClaude Sonnet 5 8b5d5d095c fix: harden payment/shipping paths against partial-failure edge cases
Dispatched a read-only silent-failure audit (silent-failure-hunter
agent) over the checkout/webhook/DHL/mail code paths, then verified
each finding by hand before fixing. Four confirmed, real gaps:

- Stripe webhook (fulfillOrder): Payment/Invoice creation and the
  confirmation email ran after the order's status flip to "BEZAHLT",
  outside any try/catch. If any of those three failed (DB blip), the
  handler threw uncaught -> Stripe retried -> the retry's
  status==="NEU" guard now found 0 rows and silently skipped the
  retry forever, leaving a paid order with no invoice, no payment
  record, and no confirmation email, with nothing logged. Fixed by
  allowing "BEZAHLT" as a valid retry entry point (idempotent
  upserts) and wrapping the post-transaction block in try/catch that
  logs loudly and re-throws (so Stripe still retries).

- DHL label creation: the already-billed tracking number's DB write
  sat outside the try/catch that protects the (paid) DHL API call.
  A failed write there lost the tracking number with zero record of
  it, while the claim sentinel permanently blocked any retry through
  the UI. Fixed by wrapping the write, logging the tracking number
  for manual reconciliation, and deliberately leaving the claim in
  place (not resettable) to avoid triggering a second, separately
  billed label.

- Checkout route: a DB write failure for the (currently unread,
  write-only) stripeCheckoutSessionId field discarded an already
  live, payable Stripe session and showed the customer a generic
  error, risking a duplicate order + stock reservation on retry.
  Made that specific write best-effort/non-fatal.

- DHL fetch calls had no request timeout, so a hung (non-erroring)
  DHL API could block an admin action indefinitely. Added a 20s
  AbortSignal.timeout to both.

Verified: tsc clean, 151/151 existing tests still pass. No dedicated
DB-integration test harness exists yet for route/webhook handlers in
this codebase (only pure-function unit tests) — building one is a
separate, larger undertaking than this fix warrants.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-07 13:35:04 +02:00
Nick SzaunigandClaude Sonnet 5 b6db9254a9 fix: fehlende Dateien aus vorherigem Commit nachreichen
CSRF-Schutz, DHL-Versandmarken, Entra-ID-SSO, ApiCore-Sync und die
Harvest-isPublished-Migration waren gestaged aber nicht im letzten
Commit enthalten — Gitea-Actions-Build brach mit "Module not found"
fuer genau diese Dateien ab.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 20:10:50 +02:00