Skip to main content

React when a lapsed licence stops (and later restarts) subscriber billing

Premium Intermediate

When a podcaster’s own Benecaster licence reaches Day-30 hard cutoff, Benecaster pauses collection on every built-in-membership Stripe subscription so nobody is charged for a feed that has stopped, and resumes exactly that cohort when the licence recovers. Both sweeps announce their outcome. Hook them if your add-on mirrors subscriber state anywhere else — a CRM, an analytics warehouse, a Slack channel, your own emails.

Four things about these hooks that are not obvious.

Pause fires at cutoff, not at grace start. During the 30-day grace window subscribers receive the full paid product, so they keep paying for it. Nothing billing-related fires anywhere in the grace lifecycle — if you need a signal earlier, hook benecaster_license_grace_started instead and treat it as a warning rather than a state change.

Resume touches only what Benecaster paused. A subscription the podcaster cancelled or paused themselves during the outage is never resumed, because the cohort is selected by a paused_reason tag Benecaster writes and by the row still being chargeable. Do not “help” by resuming the rest — you would restart billing the podcaster deliberately stopped.

$skipped_shows is not a rounding error, and 'test_mode' is the one to read carefully. A show in test mode cannot be reached through its live keyset, and nothing records which mode created a given subscription — so those subscribers are still being charged and Benecaster cannot stop it. If your add-on surfaces billing state anywhere, surface these too.

Only built-in membership is ever affected. Subscriptions in MemberPress, WooCommerce Subscriptions, PMPro, RCP or Patreon are not ours to pause and Benecaster never touches them. If your add-on bridges one of those, the podcaster stopping their own billing is a manual step and these hooks will not fire for it.

There are matching benecaster_cutoff_billing_pause_started / benecaster_cutoff_billing_resume_started actions carrying just the candidate count, fired before any Stripe call, so a sweep that dies mid-way is still bounded in your logs.

Code

<?php
add_action( 'benecaster_cutoff_billing_pause_complete', 'my_addon_billing_paused', 10, 3 );
add_action( 'benecaster_cutoff_billing_resume_complete', 'my_addon_billing_resumed', 10, 3 );

/**
 * @param int   $paused        Subscriptions successfully paused and tagged.
 * @param array $failures      [ [ 'subscription_id' => string, 'show_id' => int, 'message' => string ], … ]
 * @param array $skipped_shows show_id => 'test_mode' | 'no_keys'
 */
function my_addon_billing_paused( int $paused, array $failures, array $skipped_shows ): void {
    // The interesting argument is $failures, not $paused. A failure means
    // that subscriber is STILL BEING CHARGED for a feed returning 403 —
    // the one outcome worth waking somebody up for.
    if ( [] === $failures && [] === $skipped_shows ) {
        return;
    }

    my_addon_alert( sprintf(
        'Benecaster paused %d subscriptions; %d failed, %d shows unreachable.',
        $paused,
        count( $failures ),
        count( $skipped_shows )
    ) );
}

function my_addon_billing_resumed( int $resumed, array $failures, array $skipped_shows ): void {
    // A resume failure is the inverse harm: those subscribers still have
    // access and are NOT being billed for it.
    my_addon_sync_billing_state();
}

View on GitHub →

Hooks Used

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