Skip to main content

See a declined donation, and tune the decline-rate alert

Free Intermediate

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 );

View on GitHub →

Hooks Used

Need this built rather than just documented? See our services →