parseWebhookEventData function

WebhookEvent? parseWebhookEventData(
  1. String raw
)

Pure parser exported for tests so the SSE-frame → WebhookEvent path can be validated without spinning up a real HTTP listener.

Implementation

WebhookEvent? parseWebhookEventData(String raw) {
  if (raw.isEmpty) return null;
  Map<String, dynamic>? decoded;
  try {
    final value = jsonDecode(raw);
    if (value is Map<String, dynamic>) decoded = value;
  } catch (_) {
    return null;
  }
  if (decoded == null) return null;
  if (!decoded.containsKey('id') ||
      !decoded.containsKey('type') ||
      !decoded.containsKey('occurredAt') ||
      !decoded.containsKey('receivedAt')) {
    return null;
  }
  // `purchaseToken` is required for every event type *except*
  // TestNotification — Apple ASN v2 / Google RTDN test payloads carry
  // no transaction, so kit emits the event with `purchaseToken` unset.
  // Hard-requiring the field surfaced valid test webhooks as
  // MALFORMED_EVENT in Flutter and never reached listeners.
  if (decoded['type'] != 'TestNotification' &&
      !decoded.containsKey('purchaseToken')) {
    return null;
  }
  // The wire format kit currently emits uses GraphQL enum identifiers
  // (PascalCase, e.g. `AppleAppStoreServerNotificationsV2`). The
  // generated Dart `fromJson` factories only accept the kebab-case
  // wire form (`apple-app-store-server-notifications-v2`). Normalize
  // each enum field here so consumers don't have to know about the
  // representational difference. PR #123 (https://github.com/hyodotdev/openiap/pull/123) review caught this drift.
  return _decodeWithFallback(decoded);
}