Skip to main content

benecaster_membership_price_created

Action Premium

Fires after a billing cadence is added to a built-in membership tier. Two paths reach it: creating a tier with a prices[] array via POST /membership-tiers, and reconciliation on PUT /membership-tiers/{id} when the payload contains a cadence that is not yet stored.

Core hooks this action itself to mint the Stripe Price for the cadence. The row therefore exists with null stripe_price_id_test and stripe_price_id_live at the moment this hook fires — do not assume a Stripe ID is present in a callback on this hook.

Note that changing the amount on an existing cadence does not fire this hook. That path nulls the Stripe IDs and re-mints against the same row.

Add a quarterly cadence to an existing tier

Premium Intermediate

A membership tier is one Stripe Product with N attached Prices, and those Prices are a collection hanging off the tier rather than a fixed monthly/annual pair on the tier itself. Adding a cadence therefore means inserting a price record through MembershipPriceRepository, not editing the tier.

Operators normally do this in Memberships → Tiers → Prices. This recipe is the programmatic equivalent, for setup scripts, WP-CLI commands, and bulk provisioning across a fleet of sites — anywhere you need a tier to come out of the box selling quarterly alongside its monthly and annual options.

The pattern generalises to any interval Stripe supports. Change interval_unit and interval_count for six-monthly (month × 6), weekly (week × 1), or biennial (year × 2).

<?php
add_action( 'benecaster_boot', function ( \Benecaster\Container $container ): void {
    // Resolve the tier by its stable slug — do not hardcode primary keys.
    $tiers = $container->make( \Benecaster\Membership\MembershipTierRepository::class );
    $tier  = $tiers->find_by_slug( 'gold' );
    if ( null === $tier ) {
        return;
    }

    $prices = $container->make( \Benecaster\Membership\MembershipPriceRepository::class );

    // Idempotency: bail if a 3-month cadence already exists for this tier
    // so re-running the setup script does not create duplicate rows.
    foreach ( $prices->find_by_tier( (int) $tier['id'] ) as $existing ) {
        if ( 'month' === $existing['interval_unit'] && 3 === (int) $existing['interval_count'] ) {
            return;
        }
    }

    // Insert the new Price row. StripeTierProvisioner picks this up on the
    // next reconciliation and mints the corresponding Stripe Price against
    // the tier's existing Stripe Product — no admin visit required.
    $prices->insert( [
        'tier_id'        => (int) $tier['id'],
        'cadence_label'  => 'Quarterly',
        'interval_unit'  => 'month',
        'interval_count' => 3,
        'amount_cents'   => 1500,
        'currency'       => 'USD',
        'is_default'     => false,
        'sort_order'     => 15,
    ] );
} );

View on GitHub →

Parameters

Name Type Default Description
$price_id int ID of the newly created price record.
$price array The full cadence: `cadence_label`, `interval_unit`, `interval_count`, `amount_cents`, `currency`, `is_default`, `sort_order`, and the two Stripe ID columns (null at this point).
$tier_id int ID of the tier the cadence belongs to.

Example

// Mirror new cadences into an external catalogue.
add_action( 'benecaster_membership_price_created', function ( int $price_id, array $price, int $tier_id ) {
    my_catalogue_add_plan( [
        'tier_id'  => $tier_id,
        'price_id' => $price_id,
        'label'    => $price['cadence_label'],
        'amount'   => $price['amount_cents'] / 100,
        'currency' => $price['currency'],
        'every'    => $price['interval_count'] . ' ' . $price['interval_unit'],
    ] );
}, 10, 3 );

Notes

Runs on every cadence during a bulk tier create, once per cadence. If your callback is expensive, batch on shutdown rather than doing the work inline.