See a declined donation, and tune the decline-rate alert
⚠ The action is the only way to observe a declined donation. Nothing is written to benecaster_listener_support_donations on a failure, deliberately: that table is money received, and every reader of it — the admin donation list, the Donations dashboard panel, the Analytics Dashboard add-on — would count a failure row as a donation. There is no failure table and none is planned.
It fires only for Listener Support donations — the handler ignores any Stripe intent without a benecaster_show_id, so membership and buy-up failures on the same Stripe account do not reach it. And it fires only when the show’s Stripe webhook is configured, because the event is the signal. A show with no webhook produces no declines, no counters and no alert — silence there is absence of data, not health.
Core counts both outcomes over a rolling 24 hours per show and warns the podcaster in the notification bell at ≥10 attempts and ≥40% declines. Tune it per show with benecaster_donation_decline_alert_thresholds.
Both halves of the threshold matter, and min_attempts is the one people delete. One genuinely expired card on a quiet day is a 100% decline rate. An alert that fires on that is an alert the podcaster learns to ignore, which costs more than it saves — core clamps the floor to a minimum of 1 for exactly that reason.
None of this detects card testing; it raises the cost and reports the shape. The thing that recognises an attack is Stripe Radar’s card-testing rules, on the podcaster’s own Stripe account. Any surface you build on this action should say so rather than reading as the defence.
Code
<?php
/**
* @param string $decline_code Stripe's `decline_code` when present
* (`stolen_card`, `insufficient_funds`, ...),
* otherwise its generic `code`, else ''.
*/
add_action( 'benecaster_donation_payment_failed', function ( int $show_id, string $intent_id, string $decline_code ): void {
if ( in_array( $decline_code, [ 'stolen_card', 'lost_card', 'pickup_card' ], true ) ) {
error_log( "benecaster: hostile decline on show {$show_id} ({$decline_code})" );
}
}, 10, 3 );
add_filter( 'benecaster_donation_decline_alert_thresholds', function ( array $thresholds, int $show_id ): array {
// A high-traffic show can afford to alert sooner.
return 42 === $show_id
? [ 'min_attempts' => 25, 'rate' => 0.25 ]
: $thresholds;
}, 10, 2 );
Hooks Used
Need this built rather than just documented? See our services →