Skip to main content

Keep a CRM in step with manual grace-period decisions

Premium Intermediate

Clearing and extending a grace period are decisions a human makes in the Promotion History panel, one subscriber at a time — the podcaster has checked the destination plugin, or has decided somebody deserves longer. Those decisions leave no trace outside the database on their own.

benecaster_promote_grace_cleared and benecaster_promote_grace_extended fire after each one so an external system can mirror it: close the support ticket that was opened when the migration started, cancel a scheduled “your access ends soon” campaign, or record the extension against the account so the next person to look does not undo it.

Notes

$promotion is the row as it was BEFORE the operation, and for _cleared that is the only reason it is useful. Clearing nulls both promote_grace_period_ends_at and promoted_to_bridge, so a listener that re-read the row itself would find two nulls and would have lost the two facts worth knowing: which plugin the subscriber was moved to, and what deadline was just cancelled. The same applies to _extended$promotion['promote_grace_period_ends_at'] is the old deadline; the new one is the third argument.

$new_expiry_ts is a UTC Unix timestamp, not a formatted string. That is deliberate: there is no clock to mistake and no format to parse. Format it yourself with wp_date() if you are showing it to a human — date_i18n() expects the site offset already baked in and would render this real UTC epoch as if it were already site-local.

Neither action fires when the underlying write fails, and neither fires for a subscription row that does not exist — so a listener never records an operation that did not happen locally.

The cron expiry path fires neither of these. A grace period that simply runs out is benecaster_promote_grace_expired, and an expiry vetoed through benecaster_promote_grace_expiry_should_revoke fires nothing at all. These two are specifically the manual operations a podcaster performs.

Code

<?php
add_action( 'benecaster_promote_grace_cleared', function ( array $promotion, int $user_id ): void {
    my_crm_client()->close_task( 'podcast_migration_reauth', [
        'user_id'   => $user_id,
        'show_id'   => $promotion['show_id'],
        'tier_slug' => $promotion['tier_slug'],
        // Still readable here - the row is the pre-clear snapshot.
        'moved_to'  => $promotion['promoted_to_bridge'],
    ] );
}, 10, 2 );

add_action( 'benecaster_promote_grace_extended', function ( array $promotion, int $user_id, int $new_expiry_ts ): void {
    my_crm_client()->reschedule_reminder( $user_id, $new_expiry_ts );
}, 10, 3 );

View on GitHub →

Hooks Used

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