Der Zahlungsbericht zeigt 150,00. Der Analytics-Export zeigt ebenfalls 150,00. Beide enthalten drei unterschiedliche Bestell-IDs. Das wirkt wie eine funktionierende Integration.
Trotzdem können die Berichte unterschiedliche Bestellungen abbilden.
Dieses kleine Gegenbeispiel lässt sich ausführen. Es verwendet erfundene Daten, keinen Kundenvorfall und keine Behauptung über einen Analytics-Anbieter. Es zeigt, was ein Summenvergleich nicht belegen kann und welche Prüfung danach sinnvoll ist.
Gleiche Summen bedeuten nicht gleiche Datensätze
Ein Shop hat drei bezahlte Bestellungen: O101 über 40,00, O102 über 60,00 und O103 über 50,00. Ein Export roher Kaufereignisse enthält O101 zweimal, O102 einmal und eine nicht zugeordnete O999 über 10,00. Alle Beträge gehören zu derselben fiktiven Währung mit zwei Dezimalstellen.
| Bestell-ID | Bezahlter Bestellbetrag | Rohe Ereignisbeträge | Ereigniszeilen |
|---|---|---|---|
| O101 | 40,00 | 40,00 + 40,00 | 2 |
| O102 | 60,00 | 60,00 | 1 |
| O103 | 50,00 | Kein Ereignis | 0 |
| O999 | Keine Bestellung im Beispiel | 10,00 | 1 |
| Summe | 150,00 | 150,00 | 4 |
Das Duplikat fügt 40,00 hinzu, der nicht zugeordnete Datensatz weitere 10,00. Gemeinsam gleichen sie die fehlenden 50,00 genau aus. Auch die Zahl unterschiedlicher Bestell-IDs ist auf beiden Seiten drei. Gemeinsam sind ihnen aber nur zwei IDs.
Die Anzahl der Ereigniszeilen zeigt hier einen Unterschied. Summe und Anzahl unterschiedlicher IDs tun das nicht. Keiner dieser Gesamtwerte benennt die fehlende Bestellung.

Zuerst festlegen, was die Exporte bedeuten sollen
Ein Kaufereignis im Browser hält fest, was die Instrumentierung gemeldet hat. Ein bezahlter Bestelldatensatz steht für den Geschäftszustand im Bestell- oder Zahlungssystem. Beide helfen bei unterschiedlichen Fragen. Keiner sollte stillschweigend den anderen ersetzen.
Definieren Sie vor dem Vergleich: Shop oder Konto, Währung, gemeinsame Bestellkennung, berücksichtigter Zahlungsstatus, Betragsdefinition und Zeitraum. Entscheiden Sie, ob auf Bestell-, Zahlungsversuchs- oder Positionsebene verglichen wird. Zu einer Bestellung können mehrere Zahlungen oder Positionen gehören.
Bruttobestellwert, eingezogene Zahlungen, Nettoumsatz nach Erstattungen und Auszahlungen nach Gebühren sind nicht dasselbe. Auch Ereignis-, Zahlungs-, Eingangs- und Auszahlungszeit unterscheiden sich. Vereinbaren Sie einen Stichtag und berücksichtigen Sie die tatsächliche Verarbeitungsverzögerung. Ein neuer Datensatz, der noch nicht angekommen ist, ist nicht automatisch verloren.
Hier gilt bewusst ein enger Rahmen: ein vollständiger synthetischer Datenstand, drei bereits bezahlte Bestellungen, ein erwartetes Kaufereignis je Bestellung, keine Erstattungen, Steuern, Gebühren, Teilzahlungen oder Währungsumrechnung. Das sind rohe Ereignisdaten, keine Nachbildung der Deduplizierungsregeln eines Anbieters.
Vor dem Join die Ereignisse gruppieren
Das Beispiel läuft mit Python 3 und dessen sqlite3-Modul. Es erstellt eine Datenbank im Arbeitsspeicher, sendet keine Netzwerkanfragen und verändert keine Produktionsdaten. Beträge werden als ganze Zahlen in der kleinsten Einheit der Beispielwährung gespeichert. Der Vergleich hängt damit nicht von binärer Gleitkommaarithmetik ab.
import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE orders (order_id TEXT PRIMARY KEY, cents INTEGER NOT NULL);
CREATE TABLE events (event_id TEXT PRIMARY KEY, order_id TEXT NOT NULL,
cents INTEGER NOT NULL);
INSERT INTO orders VALUES ('O101', 4000), ('O102', 6000), ('O103', 5000);
INSERT INTO events VALUES ('E1', 'O101', 4000), ('E2', 'O101', 4000),
('E3', 'O102', 6000), ('E4', 'O999', 1000);
""")
for table in ("orders", "events"):
total, ids = db.execute(
f"SELECT SUM(cents), COUNT(DISTINCT order_id) FROM {table}"
).fetchone()
print(f"{table}: cents={total}, distinct_order_ids={ids}")
query = """
WITH observed AS (
SELECT order_id, COUNT(*) AS event_rows, SUM(cents) AS event_cents
FROM events GROUP BY order_id
), ids AS (
SELECT order_id FROM orders UNION SELECT order_id FROM events
)
SELECT ids.order_id, o.cents, COALESCE(e.event_rows, 0), e.event_cents
FROM ids
LEFT JOIN orders o ON o.order_id = ids.order_id
LEFT JOIN observed e ON e.order_id = ids.order_id
ORDER BY ids.order_id
"""
for row in db.execute(query):
print(row)
db.close()
Die Ausgabe lautet:
orders: cents=15000, distinct_order_ids=3
events: cents=15000, distinct_order_ids=3
('O101', 4000, 2, 8000)
('O102', 6000, 1, 6000)
('O103', 5000, 0, None)
('O999', None, 1, 1000)
Die Abfrage gruppiert Ereignisse zunächst zu einer Zeile je Bestell-ID. Sonst kann ein direkter Join einer Bestellzeile mit mehreren Ereigniszeilen den Bestellbetrag wiederholen und den Abgleich selbst verfälschen. Die Vereinigung aller IDs erhält außerdem nicht zugeordnete Datensätze beider Seiten. Ein Inner Join würde sie ausblenden.
O103 hat keine Ereigniszeile. Zu O999 gibt es im Beispiel keine Bestellung. O101 hat zwei Ereigniszeilen. Das sind drei unterschiedliche Beobachtungen, nicht eine unerklärte Summendifferenz.

Prüfen Sie bei echten Exporten fehlende Kennungen ebenfalls vor dem Gruppieren. Ersetzen Sie sie nicht stillschweigend durch einen gemeinsamen Wert wie unknown. Unabhängige Datensätze würden dann derselben Bestellung zugerechnet. Kennungen müssen zum richtigen Shop, Konto und zur richtigen Umgebung gehören. Verwenden Exporte unterschiedliche IDs, brauchen Sie eine Zuordnung, keinen Join anhand gleicher Beträge oder naher Zeitstempel.
Eine Abweichung ist ein Ausgangspunkt, keine Diagnose
Im konstruierten Beispiel kennen wir die Fehler, weil wir sie eingebaut haben. In Produktion kann eine nicht zugeordnete ID auch durch eine andere Tagesgrenze, eine Testumgebung, einen unvollständigen Export oder einen legitimen, nur in einem Bericht ausgeschlossenen Geschäftszustand entstehen. Halten Sie diese Möglichkeiten offen, bis die Datensätze geprüft sind.
Browserübermittlung und Zahlungsbestätigung haben unterschiedliche Ausfallmöglichkeiten. Stripes Checkout-Anleitung zur Bestellabwicklung erklärt, warum eine erfolgreiche Zahlung den Besuch der Rückkehrseite nicht garantiert. Ein Ereignis auf dieser Seite allein ist deshalb kein vollständiger Mechanismus zur Zahlungsbestätigung.
Auch serverseitige Übermittlung erfordert Sorgfalt. Stripe dokumentiert doppelte Webhook-Zustellungen. Eine Benachrichtigung zu erhalten ist nicht dasselbe, wie ihre Geschäftswirkung sicher genau einmal anzuwenden. Provider-Ereignis-ID und Bestell-ID lösen unterschiedliche Identitätsprobleme: Mehrere unterschiedliche Ereignisse können dieselbe Bestellung betreffen. Dieser Artikel implementiert keinen Zahlungs-Handler und empfiehlt keinen universellen Deduplizierungsschlüssel.
Ebenso bedeutet ein Rückgabewert true von sendBeacon(), dass der Browser die Daten zur Übertragung eingereiht hat. Er belegt nicht, dass die Analytics-Anwendung einen Kauf angenommen und gespeichert hat. Das ist die dokumentierte Bedeutung des Rückgabewerts, kein Nachweis für einen Ereignisverlust bei einem bestimmten Collector.

Vor der Attribution die Identitäten abgleichen
Ein hilfreicher Abgleich führt getrennte Listen: bezahlte Bestellungen ohne erwartetes Ereignis, Ereignisse ohne passende bezahlte Bestellung, wiederholte Ereigniszeilen und abweichende Beträge oder Währungen. Beobachten Sie diese Kategorien über die Zeit mit stabilen Vergleichsregeln. Lassen Sie nicht zugeordneten Umsatz sichtbar, statt einen Kanal zu erfinden, damit eine Attributionsgrafik aufgeht.
Eine reparierte Erfassung beweist nicht, dass ein Kanal einen Kauf verursacht hat. Attribution fragt danach, wie Erfolg zugerechnet wird. Diese Frage sollte davon getrennt bleiben, ob die Kaufdatensätze überhaupt übereinstimmen.
Die Lehre lautet nicht, dass jedes Dashboard falsch ist. Übereinstimmende Summen sind nur schwächere Belege, als sie wirken. Behalten Sie die Summenprüfung bei, ergänzen Sie aber einen Abgleich auf Datensatzebene, bevor eine Umsatzintegration als abgestimmt gilt.



