Skip to main content

BridgeInterface

Benecaster\Bridge\BridgeInterface

The contract that every subscription bridge must implement. All subscription queries, tier lookups, and event callbacks in Benecaster pass through this interface. Core never calls a membership plugin directly — it only calls bridge methods.

Implementing this interface is all that’s required to support a membership plugin not in the core bridge set. Register your bridge via benecaster_boot and Benecaster’s core, token system, and subscription events all work automatically.

Connecting a Bridge to a Show

After instantiating your bridge, activate it for a show with BridgeManager::set_active_bridge():

add_action( 'benecaster_boot', function ( \Benecaster\Container $container ) {
    $bridge = new \MyPlugin\MyCustomBridge(
        $container->make( \Benecaster\Show\TierMapRepository::class )
    );
    $container->make( \Benecaster\Bridge\BridgeManager::class )
              ->set_active_bridge( $show_id, 'my-custom-slug' );
} );

Once set, BridgeManager::get_active_bridge( $show_id ) returns your bridge instance and Benecaster routes all subscription queries through it.

Methods

get_plugin_slug(): string

Returns the stable slug identifying this bridge.

Used by BridgeManager for bridge detection and persistence. The slug is stored in _benecaster_show_active_bridge post meta to identify which bridge is active for a given show. Must be unique across all bridges — collisions cause unpredictable bridge selection.

Choose a slug that matches your plugin’s identity and won’t change: memberpress, woocommerce-subscriptions, my-custom-plugin. Avoid generic strings.


get_user_tier( int $user_id, int $show_id ): ?string

Returns the active internal tier slug for a user on a specific show, or null if the user has no active subscription.

The returned slug must match a row in the benecaster_tier_map table for the given show. If it doesn’t match any mapped row, Benecaster has no tier to work with and treats the user as unsubscribed.

Parameters:

Parameter Type Description
$user_id int WordPress user ID
$show_id int Benecaster show post ID

Returns: string|null — internal tier slug, or null if not subscribed


is_user_active( int $user_id, int $show_id ): bool

Returns whether the user has any active subscription for the show.

The default implementation delegates to get_user_tier() — it returns true if get_user_tier() returns a non-null value. Override this only if your membership plugin provides a more efficient active-status check that avoids a full tier lookup.

Parameters:

Parameter Type Description
$user_id int WordPress user ID
$show_id int Benecaster show post ID

Returns: bool


get_all_tiers( int $show_id ): array

Returns all membership levels or products available as candidates for tier mapping.

Used by the tier mapping UI to populate the dropdown of levels that can be assigned to Benecaster tiers. Each entry must include id and name. Bridges should also include price when the membership plugin exposes it — the tier mapping UI uses price to auto-populate is_free_tier (tiers with price == 0 are marked free and excluded from license tier counting). Return null for price when the membership plugin does not expose pricing at the tier level.

Site-wide membership plugins (MemberPress, Paid Memberships Pro) may ignore $show_id — their membership levels are not per-show. Show-scoped plugins should filter by show.

Parameters:

Parameter Type Description
$show_id int Benecaster show post ID (may be ignored for site-wide plugins)

Returns: array — list of array{id: int, name: string, price: float|null} entries; price should be included when the membership plugin exposes tier pricing, null otherwise


get_all_user_tiers( int $user_id, int $show_id ): array

Returns all active tier slugs for a user on a specific show.

Used by the feed delivery pipeline to determine which tiers’ episode lists to include in the subscriber’s feed. Unlike get_user_tier(), which returns the first matching tier, this method returns all matching tiers — allowing a subscriber who holds multiple concurrent active memberships to receive a merged feed covering all of them.

The default implementation calls get_user_tier() and returns the result wrapped in an array ([$tier_slug] or [] if null). Override this in bridges that can efficiently retrieve all active memberships for a user in one query rather than stopping at the first match.

Why this matters for feed delivery: The feed is assembled from one cached episode list per tier. A subscriber with a “Newsletter” tier and a “Podcast” tier gets a merged feed covering both. A subscriber with one tier gets only that tier’s feed — identical to the previous single-tier behaviour.

Parameters:

Parameter Type Description
$user_id int WordPress user ID
$show_id int Benecaster show post ID

Returns: array — list of active internal_tier_slug strings; empty array if the user has no active mapped membership for this show.


on_tier_saved( callable $callback ): void

Registers a callback to fire when a membership tier (level or product) is created or updated in the membership plugin.

Used by Benecaster to detect new unmapped tiers and price changes without waiting for the daily sync. When the callback fires, Benecaster checks whether the tier already exists in benecaster_tier_map:

  • Not yet mapped — fires benecaster_tier_unmapped; surfaces an admin notice so the podcaster can map the tier before any subscribers sign up on it.
  • Already mapped, price changed — updates is_free_tier immediately rather than waiting for the daily sync. A tier switching from paid to free (or vice versa) affects license tier counting; immediate detection prevents counting errors between the save event and the next daily run.

If your membership plugin has no reliable tier-save event, implement this method as a no-op (empty method body). The daily sync — which calls get_all_tiers() once per day — serves as the fallback and detects both new tiers and price changes within 24 hours.

For most membership plugins, the same underlying save hook fires for both tier creation and tier updates — implement this as a single hook registration rather than two separate ones.

Callback signature: fn( array $tier ): void$tier is array{id: int, name: string, price: float|null}, the same shape as a single get_all_tiers() entry.


on_subscription_activated( callable $callback ): void

Registers a callback to fire when a subscription becomes active.

Hook into your membership plugin’s activation event and call $callback when it fires. The callback receives the user ID, show ID, tier slug, and a source string.

Callback signature: fn( int $user_id, int $show_id, string $tier_slug, string $source ): void

Source values:

Value Meaning
'new' First-time subscription
'resubscribe' Reactivation of a previously cancelled subscription
'migration' Created via Benecaster’s migration flow

on_subscription_cancelled( callable $callback ): void

Registers a callback to fire when a subscription is cancelled.

Call $callback when your membership plugin fires a cancellation event. Benecaster marks the subscriber’s token inactive in response.

Callback signature: fn( int $user_id, int $show_id ): void


on_subscription_changed( callable $callback ): void

Registers a callback to fire when a subscriber moves between tiers.

Callback signature: fn( int $user_id, int $show_id, string $old_tier_slug, string $new_tier_slug ): void

If your membership plugin has no native tier-change event, implement this method as a no-op (empty method body). Tier changes in that case happen via a cancel + re-subscribe flow, which fires on_subscription_cancelled followed by on_subscription_activated in sequence. This is a documented characteristic of some bridges — see MemberPressBridge for an example.


on_subscription_renewed( callable $callback ): void

Registers a callback to fire on each successful recurring payment.

Callback signature: fn( int $user_id, int $show_id, string $tier_slug ): void

Wire to your membership plugin’s recurring payment success event. Take care to fire the callback only for recurring charges, not for the initial sign-up transaction (which is already handled by on_subscription_activated).


on_payment_failed( callable $callback ): void

Registers a callback to fire when a payment fails after any upstream grace period elapses.

Callback signature: fn( int $user_id, int $show_id ): void

Fire when your membership plugin determines the subscription is definitively in a payment-failed state — after its own retry logic or grace period has run, not at the moment of the first failed charge. Benecaster marks the token inactive in response.


Minimal Class Skeleton

This skeleton shows the shape of a complete BridgeInterface implementation. It is not a working bridge — for a full implementation with tier map lookup and hook wiring, see Implement a Custom Bridge.

<?php

namespace MyPlugin;

use Benecaster\Bridge\BridgeInterface;
use Benecaster\Show\TierMapRepository;

class MyPluginBridge implements BridgeInterface {

    public function __construct(
        private readonly TierMapRepository $tier_map
    ) {}

    /**
     * A stable, unique slug for this bridge.
     */
    public function get_plugin_slug(): string {
        return 'my-plugin';
    }

    /**
     * Return the active internal tier slug for a user, or null if not subscribed.
     * Translate your plugin's level/product IDs through the tier map.
     */
    public function get_user_tier( int $user_id, int $show_id ): ?string {
        // Query your membership plugin for active level IDs,
        // then look them up in $this->tier_map.
        return null; // replace with real implementation
    }

    /**
     * Return all active tier slugs for a user as an array<string>.
     * Default implementation wraps get_user_tier() — override to return multiple tiers
     * when a subscriber holds concurrent active memberships mapped to different tiers.
     * Used by the feed delivery pipeline to assemble a merged feed for multi-tier subscribers.
     * Return [] if the user has no active mapped membership for this show.
     */
    public function get_all_user_tiers( int $user_id, int $show_id ): array {
        $tier = $this->get_user_tier( $user_id, $show_id );
        return $tier !== null ? [ $tier ] : [];
    }

    /**
     * Return whether the user has any active subscription.
     * Default: delegates to get_user_tier(). Override only if cheaper.
     */
    public function is_user_active( int $user_id, int $show_id ): bool {
        return $this->get_user_tier( $user_id, $show_id ) !== null;
    }

    /**
     * Return all membership levels as [{id, name, price}] for the tier mapping UI.
     * Include price (float) when the plugin exposes tier pricing; set null if unavailable.
     * price == 0 marks the tier as free (excluded from license limit counts).
     */
    public function get_all_tiers( int $show_id ): array {
        return []; // replace with real implementation
    }

    /**
     * Register a callback for tier create/update events.
     * Call $callback( ['id' => int, 'name' => string, 'price' => float|null] ) when any
     * tier is created or updated in your membership plugin (new level, name change, price change).
     * For most plugins the same save hook fires for both create and update — one registration covers both.
     * If your plugin has no reliable tier-save hook, leave this empty (no-op).
     * The daily sync serves as fallback and detects changes within 24 hours.
     */
    public function on_tier_saved( callable $callback ): void {
        // Hook into your plugin's tier create/update event, or leave empty if unavailable.
    }

    /**
     * Register a callback for subscription activation events.
     * Call $callback( $user_id, $show_id, $tier_slug, $source ) when a subscription activates.
     */
    public function on_subscription_activated( callable $callback ): void {
        // Hook into your plugin's activation event.
    }

    /**
     * Register a callback for subscription cancellation events.
     * Call $callback( $user_id, $show_id ) when a subscription is cancelled.
     */
    public function on_subscription_cancelled( callable $callback ): void {
        // Hook into your plugin's cancellation event.
    }

    /**
     * Register a callback for tier-change events.
     * If your plugin has no native tier-change event, leave this empty (no-op).
     */
    public function on_subscription_changed( callable $callback ): void {
        // Implement or leave empty if plugin has no tier-change event.
    }

    /**
     * Register a callback for successful recurring payment events.
     * Call $callback( $user_id, $show_id, $tier_slug ) on renewal.
     * Do not fire for initial sign-up transactions.
     */
    public function on_subscription_renewed( callable $callback ): void {
        // Hook into your plugin's renewal/recurring-payment event.
    }

    /**
     * Register a callback for payment failure after grace period.
     * Call $callback( $user_id, $show_id ) when payment definitively fails.
     */
    public function on_payment_failed( callable $callback ): void {
        // Hook into your plugin's payment failure event.
    }
}

For the complete implementation — tier map lookup, hook wiring, and registration via benecaster_boot — see Implement a Custom Bridge.