A refund delivered twice looked like two refunds

Providers that send notifications without a delivery id defeated duplicate detection, so a repeated refund notification could be read as refunds exceeding the payment.

Status
Resolved
Project
PandaPayLens
Found
Found

Symptoms

No duplicate was reported for identical repeated records from providers without a delivery id, and the amount mismatch rule could report that refunds exceed the payment when the same refund notification arrived twice.

Expected and observed behavior

Expected
A repeated delivery is recognized as a repeat, and amounts are not counted twice.
Observed
Each copy was treated as a separate event.

Root cause

Repeat detection used only the adapter's deliveryKey. When a provider sends no delivery id, there was no key, so identical records never matched.

Resolution

The engine now computes a contentFingerprint for every event: a 64-bit FNV-1a pair over the provider, format, event path and the record content serialized with sorted keys. The rules use one key for "the same observation":

TypeScript
export function repeatKey(e: NormalizedEvent): string | undefined {
  if (e.deliveryKey) return `${e.provider}:key:${e.deliveryKey}`;
  if (e.contentFingerprint) return `${e.provider}:content:${e.contentFingerprint}`;
  return undefined;
}

Keyed repeats are reported as confirmed and content repeats as possible. Adapters set contentMayRepeat where the provider documents that identical records can be separate events (Paymentwall partial refunds and reversals, EBANX refund and chargeback notifications). Copies that disagree are reported by the conflicting observations rule.

Validation

New tests in packages/rules/tests/content-duplicates.test.ts; nine adapter tests updated. Pull request #50 reports 4,581 tests passing.

Remaining limitations

Identical content does not prove a repeated delivery: two separate events with exactly the same fields look the same. The supplied data also cannot show whether the receiving application processed the event more than once.

GitHub references

More

Theme