fix(notifications): P2 extreme-audit P1 fixes before deploy
Independent audit verdict was NOT SAFE; all findings fixed:
- dispatcher Idempotency-Key is now per-attempt (${id}:${attempts}),
not row id alone, so a transient-failure response cached by Resend
cannot permanently suppress every retry and silently lose an email.
- enqueueDues is fully try/catch wrapped so a notifications-table or
render failure can never throw into / wedge the Stripe money path.
- dues_lapsed dedupe falls back to the subscription id when periodEnd
is null (canceled/deleted subs), so future lapses no longer collapse
to one key and get suppressed. New test; suite 499.
- documented the dispatcher's once/min concurrency assumption.
This commit is contained in:
@@ -37,12 +37,26 @@ export function notificationDedupeKey(
|
||||
// Renewal + payment-failed are keyed by the Stripe invoice (one mail per
|
||||
// invoice outcome). Lapse is keyed by member + the period it lapsed for, so a
|
||||
// member can lapse again next cycle but not be re-mailed for the same one.
|
||||
// periodEnd can be absent (canceled/deleted subscriptions carry no item
|
||||
// current_period_end); without a fallback every lapse would collapse to
|
||||
// `dues_lapsed:<member>` and suppress every future lapse notice. Fall back to
|
||||
// the subscription id (a new comeback subscription has a new id, so distinct
|
||||
// lapses stay distinct).
|
||||
export function duesDedupeKey(
|
||||
type: DuesNotificationType,
|
||||
ref: { invoiceId?: string | null; memberId?: string | null; periodEnd?: string | null },
|
||||
ref: {
|
||||
invoiceId?: string | null
|
||||
memberId?: string | null
|
||||
periodEnd?: string | null
|
||||
subscriptionId?: string | null
|
||||
},
|
||||
): string {
|
||||
if (type === 'dues_lapsed') {
|
||||
return notificationDedupeKey(['dues_lapsed', ref.memberId, ref.periodEnd])
|
||||
return notificationDedupeKey([
|
||||
'dues_lapsed',
|
||||
ref.memberId,
|
||||
ref.periodEnd || ref.subscriptionId,
|
||||
])
|
||||
}
|
||||
return notificationDedupeKey([type, ref.invoiceId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user