leseWebhookEreignis function
Prueft die Signatur und liest das Ereignis -- in dieser Reihenfolge. Wer zuerst liest und dann prueft, hat den fremden Rumpf schon durch seinen Code laufen lassen.
Entdoppeln nicht vergessen: dieselbe Zustellung kann mehrfach ankommen
(Wiederholung nach einer Antwort, die unterwegs verloren ging). Massgeblich
ist ereignis.id, nicht der Kopf X-Kasseneck-Delivery -- der ist bei
Wiederholungen derselbe, beantwortet aber "habe ich dieses Ereignis schon
verarbeitet?" nur fuer genau diesen Endpunkt.
Innerhalb von zehn Sekunden mit 2xx antworten und die Arbeit danach erledigen; sonst wiederholt Kasseneck bis zu fuenfmal (kWebhookWiederholungSek) und gibt dann auf.
Implementation
WebhookEreignisErgebnis leseWebhookEreignis({
required List<String> secrets,
required String? signaturKopf,
required List<int> rumpf,
int? jetztSek,
int toleranzSek = kWebhookToleranzSek,
}) {
final geprueft = pruefeWebhookSignatur(
secrets: secrets,
signaturKopf: signaturKopf,
rumpf: rumpf,
jetztSek: jetztSek,
toleranzSek: toleranzSek,
);
if (!geprueft.ok) return WebhookEreignisErgebnis.nein(geprueft.grund!);
Object? roh;
try {
roh = jsonDecode(utf8.decode(rumpf));
} on Object {
return const WebhookEreignisErgebnis.nein(WebhookAblehnung.rumpfKeinJson);
}
if (roh is! Map) return const WebhookEreignisErgebnis.nein(WebhookAblehnung.rumpfKeinEreignis);
final e = Map<String, dynamic>.from(roh);
final id = e['id'];
final type = e['type'];
if (id is! String || type is! String) {
return const WebhookEreignisErgebnis.nein(WebhookAblehnung.rumpfKeinEreignis);
}
return WebhookEreignisErgebnis.ok(
PartnerWebhookEreignis(
id: id,
type: type,
createdAt: alsZahlOderNull(e['createdAt']) ?? 0,
partnerId: alsText(e['partnerId']),
// Nur ein ausdrueckliches `true` ist eine Probe. Alles andere -- auch
// ein fehlendes Feld -- ist der Ernstfall; im Zweifel lieber einmal zu
// viel gearbeitet als eine echte Kasse fuer eine Probe gehalten.
test: e['test'] == true,
data: alsMap(e['data']),
),
geprueft.zeitstempelSek,
);
}