Playbook
Bezahlt, aber keine Bestellung: Heilung (Heilung Bezahlt Aber Keine Bestellung)
Ein Incident-Playbook: Der Kunde wurde belastet, aber es gibt keine Bestellung; das Multi-Intent-Cart-Problem; und warum Dedup vorsichtig bereinigt werden muss.
Verteilte Payment Engine (Distributed Payment Engine)
Teil 15 von 22
Serie zur verteilten Payment-Architektur — die Lücke zwischen Capture und Complete.
Der vorherige Teil hat gezeigt, wie ein Abgleich-Worker Drift erkennt. Dieser Teil behandelt die unangenehmste Form dieser Drift: Der Kunde wurde beim PSP tatsächlich belastet, und dennoch existiert lokal keine Bestellung.
Dieses Szenario erzeugt Panik, weil beide naheliegenden Lösungen verlockend und beide für sich allein falsch sind: sofort erstatten (der Kunde wollte die Bestellung vielleicht wirklich, das löst eine unnötige Erstattungs-Retry-Schleife aus) oder still eine neue Bestellung erzeugen (ohne zu wissen, zu welchem Warenkorb oder Intent das Geld gehört — Risiko einer falschen Bestellung).
PSP-Datensatz: Charge #789 → Succeeded, amount: 249.00
Lokaler Datensatz: (keine Order- oder Payment-Zeile vorhanden)
│
▼
Bestimmen, zu welchem Warenkorb dieses Geld gehört
│
▼
Bestellung erzeugen (Heal) ODER sicher erstatten
Wo die Begriffe zuerst auftauchen
📦 Orphan Charge
Eine beim PSP erfolgreiche Belastung, die sich keinem lokalen Datensatz zuordnen lässt.
📦 Multi-Intent Cart
Ein Zustand, in dem für denselben Warenkorb mehr als ein Payment Intent erzeugt wurde (z. B. Seiten-Refresh, Doppelklick).
📦 Heal
Die rückwirkende Verknüpfung eines Orphan Charge mit dem richtigen Warenkorb oder der richtigen Bestellung.
📦 Correlation ID
Eine zu Beginn des Zahlungsflusses erzeugte Kennung, die ein Payment Intent mit dem Warenkorb oder der Anfrage verknüpft, die es ausgelöst hat.
Ein Orphan Charge entsteht meist dadurch, dass eine Correlation-ID irgendwo verloren geht: Sie wurde bei der Erzeugung des Intents gespeichert, doch der Prozess wurde unterbrochen, bevor der Schritt der Bestellungserstellung erreicht wurde.
Das Incident-Playbook: die ersten Schritte
Wird ein Orphan Charge entdeckt (meist durch den Abgleich-Worker oder eine Kundenbeschwerde), ist der erste Schritt niemals eine Aktion — sondern die Correlation-Kette wiederherzustellen:
1. Die Correlation-ID aus den Metadaten des PSP-Charges extrahieren
2. Im lokalen System nach einem Cart-/Intent-Datensatz mit dieser ID suchen
3. Gefunden → genau bestimmen, an welchem Schritt der Prozess unterbrochen wurde
4. Nicht gefunden → Richtung Erstattung tendieren (es gibt kein Ziel zum Heilen)
Die Correlation-ID muss immer beim Erzeugen des Charges in die Metadaten des PSP geschrieben werden — diese eine Entwurfsentscheidung macht Orphan Charges überhaupt rückwirkend lösbar.
Multi-Intent Carts: welches Geld zu welcher Bestellung gehört
Hat ein Kunde die Checkout-Seite zweimal geöffnet (Tab-Refresh, Doppelklick, erneuter Versuch nach Netzwerkverzögerung), können für denselben Warenkorb zwei getrennte Payment Intents entstehen. Sind beide beim PSP erfolgreich, lautet die Frage nicht mehr 'eine verlorene Zahlung', sondern 'welche Zahlung gewinnt, und was passiert mit der anderen'.
Cart #A
├─ Intent #1 → PSP: Succeeded
└─ Intent #2 → PSP: Succeeded (derselbe Warenkorb, zwei getrennte Charges)
Das richtige Verhalten ist, den Warenkorb zu sperren (damit kein neues Intent dagegen erzeugt wird) und genau ein Intent als 'Gewinner' zu wählen, während das andere automatisch erstattet wird — beide mit der Bestellung zu verknüpfen würde eine Doppelbelastung erzeugen.
Dedup vorsichtig bereinigen: warum 'aggressiv' riskant ist
Die größte Falle bei der Bereinigung von Orphan Charges ist, die Dedup-Logik auf lose Kriterien wie 'gleicher Kunde, gleicher Betrag, zeitlich nah' zu stützen. Dieses Kriterium kann fälschlich zwei tatsächlich getrennte Käufe (der Kunde hat wirklich zwei verschiedene Dinge gekauft) als einen behandeln.
❌ Lose Dedup
Gleicher Kunde + gleicher Betrag + innerhalb von 5 Minuten → als eine Bestellung zählen
✓ Strikte Dedup
Gleiche Correlation-ID ODER gleicher Idempotency Key → als eine Bestellung zählen
Eine Dedup-Entscheidung sollte immer auf einer vom System selbst erzeugten Kennung beruhen (Correlation-ID, Idempotency Key); indirekte Signale wie Betrag und Zeit sollten höchstens eine ergänzende Bestätigungsebene sein, nie das primäre Kriterium.
Heilen oder erstatten: die Entscheidungstabelle
| Situation | Richtige Aktion |
|---|---|
| Correlation-ID passt zu einem unabgeschlossenen Warenkorb | Heilen — Bestellung erzeugen, Zahlung verknüpfen |
| Correlation-ID passt zu keinem Datensatz | Erstatten — es gibt kein Ziel zum Heilen |
| Zwei erfolgreiche Intents für denselben Warenkorb | Eines heilen, das andere erstatten |
| Warenkorb bereits durch eine andere Zahlung abgeschlossen | Erstatten — sonst Doppelbelastung |
Jede Heilungsaktion sollte einen eigenen Audit-Datensatz erzeugen: wer bzw. welcher Prozess, zu welcher Zeit, auf welcher Beweisgrundlage diese Bestellung erzeugt hat. Dieser Datensatz verhindert auch, dass sich dasselbe Szenario unbemerkt wiederholt.
Häufig verwechselte Unterschiede
❌ Ein Orphan Charge sollte immer erstattet werden
✓ Passt eine Correlation-ID zu einem echten Ziel, ist Heilen oft die richtige Wahl
❌ Gleicher Betrag + gleicher Kunde = gleiche Bestellung
✓ Dedup muss auf einer vom System selbst erzeugten Kennung beruhen
❌ Multi-Intent ist ein Fehlerzustand, der ignoriert werden kann
✓ Multi-Intent ist normales Nutzerverhalten (Refresh, Doppelklick) und muss entworfen werden
Vergleich Heilen vs. Erstatten
| Kriterium | Heilen | Erstatten |
|---|---|---|
| Correlation-ID-Treffer | vorhanden | fehlt oder unklar |
| Kundenerfahrung | Bestellung erscheint, keine spürbare Unterbrechung | Geld kommt zurück, keine Bestellung |
| Risiko | Risiko einer falschen Zuordnung | Risiko der Kundenunzufriedenheit |
Checkliste für die Incident-Reaktion
- Enthalten die PSP-Metadaten des Orphan Charge eine Correlation-ID? Falls nicht, ist das die eigentliche Entwurfslücke.
- Beruht die Dedup-Entscheidung auf einem indirekten Signal (Betrag, Zeit) oder auf einer vom System erzeugten Kennung?
- Wird der Warenkorb im Multi-Intent-Szenario nach dem ersten erfolgreichen Intent gesperrt?
- Erzeugt jede Heilungsaktion einen Audit-Datensatz mit wer/wann/auf welcher Beweisgrundlage?
- Wird vor einer Erstattung mehr als einmal geprüft, dass wirklich kein Ziel gefunden wurde?
Was bleiben soll
- Der Schlüssel zur Lösung von Orphan Charges ist eine Correlation-ID, die zuverlässig ganz am Anfang des Zahlungsflusses erzeugt wird.
- Ein Multi-Intent-Cart ist ein normales Szenario; es muss mit Warenkorbsperrung und einem einzigen gewinnenden Intent entworfen werden.
- Eine Dedup-Entscheidung sollte niemals auf indirekten Signalen wie Betrag oder Zeit beruhen, sondern immer auf einer Kennung.
- Ob geheilt oder erstattet wird, sollte eine beweisbasierte Entscheidung sein, keine reflexhafte Aktion.
Die sicherste Aktion in Panik ist keine schnelle Korrektur — es ist, nichts zu tun, bis der richtige Beweis gefunden ist.
Der nächste Teil geht der Wurzel dieser gesamten Drift-Klasse nach: warum eine verteilte Transaktion (2PC) über PSP, Bestellung und Finanzwesen eine Falle ist, und warum Saga plus Abgleich die eigentliche Antwort sind.
FAQ
Häufige Fragen
Was ist Orphan Charge?
Eine beim PSP erfolgreiche Belastung, die sich keinem lokalen Datensatz zuordnen lässt.
Was ist Multi-Intent Cart?
Ein Zustand, in dem für denselben Warenkorb mehr als ein Payment Intent erzeugt wurde (z. B. Seiten-Refresh, Doppelklick).
Stimmt es, dass „Ein Orphan Charge sollte immer erstattet werden“?
Passt eine Correlation-ID zu einem echten Ziel, ist Heilen oft die richtige Wahl
Was legt dieser Teil fest?
Dieses Szenario erzeugt Panik, weil beide naheliegenden Lösungen verlockend und beide für sich allein falsch sind: sofort erstatten (der Kunde wollte die Bestellung vielleicht wirklich, das löst eine unnötige Erstattungs-Retry-Schleife aus) oder still eine neue Bestellung erzeugen (ohne zu wissen, zu welchem Warenkorb oder Intent das Geld gehört — Risiko einer falschen Bestellung). Der Schlüssel zur Lösung von Orphan Charges ist eine Correlation-ID, die zuverlässig ganz am Anfang des Zahlungsflusses erzeugt wird. Der vorherige Teil hat gezeigt, wie ein Abgleich-Worker Drift erkennt. Dieser Teil behandelt die unangenehmste Form dieser Drift: Der Kunde wurde beim PSP tatsächlich belastet, und dennoch existiert lokal keine Bestellung.
Gelernte Engineering-Prinzipien
- Der Schlüssel zu Orphan Charges ist eine zuverlässig früh erzeugte Correlation-ID.
- Dedup sollte immer auf einer Kennung beruhen, nie auf indirekten Signalen wie Betrag oder Zeit.
- Die sicherste Aktion in Panik ist, nichts zu tun, bis der richtige Beweis gefunden ist.
Weiterlesen
Weiterlesen
Nachster Teil der Serie
Warum Eventual Consistency verteilten Transaktionen überlegen ist
Eine 2PC über PSP, Bestellung und Finanzwesen aufzubauen ist eine Falle. Saga plus Abgleich ist die eigentliche Antwort, auf die dieser achtteilige Bogen…
Nachster Teil der Serie
Aufbau eines Zahlungsabgleich-Workers
Wie Sweeper Drift heilen: Der PSP meldet erfolgreich, während der lokale Datensatz abgelaufen ist — und wie gealterte FinalizePending-Einträge aufgelöst…
Aus derselben Serie
Optimistische Concurrency unter Webhooks
Wenn Webhook und synchrone Antwort dieselbe Zahlung gleichzeitig berühren: Wie lösen Version Token und Lease das Rennen — und warum kann ein veralteter…