leseWebhookEreignis function

WebhookEreignisErgebnis leseWebhookEreignis({
  1. required List<String> secrets,
  2. required String? signaturKopf,
  3. required List<int> rumpf,
  4. int? jetztSek,
  5. int toleranzSek = kWebhookToleranzSek,
})

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,
  );
}