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.
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":
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.