Skip to main content

PriceChangeApplier

\Benecaster\Membership\PriceChange\PriceChangeApplier

Class Premium

The hourly job that carries out scheduled price changes. Each run first sends any reminder emails that have come due, then processes every pending change whose notice period has ended: for each notified subscriber it moves the subscriber’s Stripe subscription item from the old Price to the new one, records the result on the subscriber’s row, and, once no subscriber is still waiting, marks the change applied and posts a notice to the podcaster.

A subscriber is moved only once Stripe’s next renewal is the first renewal at the new price. A renewal that falls inside the notice window is billed at the old price, so the item is left alone until that renewal has happened, and a change can take several hourly passes to finish. If the wait overshoots and a renewal is billed at the old price after the email named the date, the subscriber is still moved, their stored first-charge date is corrected, and the only effect is one more renewal at the old price.

A subscriber is skipped rather than moved when their membership has ended or is set to cancel at period end, they have switched tier (for a tier price), they have dropped the buy-up (for a buy-up), their billing is past due or paused, or their Stripe subscription no longer carries the old Price. A person whose data has been erased is treated as an ended membership. A Stripe error marks the row failed with the error message, and it can be retried. Skips and failures are named in the podcaster’s after-action notice.

This is internal scheduled work, registered at boot; add-ons never call it. To follow a change, use benecaster_price_change_apply_started, benecaster_price_change_subscriber_applied and benecaster_price_change_applied.

Constructor Dependencies

Type Description
\Benecaster\Membership\PriceChange\PriceChangeRepository Finds due changes and their subscriber rows, and records each outcome.
\Benecaster\Membership\SubscriptionRepository Reads each subscriber's subscription to check it is still live, on the right tier and not in a billing exception.
\Benecaster\Membership\MembershipTierRepository Checks a tier-price subscriber is still on the tier the change belongs to.
\Benecaster\Membership\MembershipPriceRepository Resolves the new Stripe Price for a tier-price change, and confirms its amount still matches what subscribers were told.
\Benecaster\Payment\StripeSubscriptionManager Retrieves the Stripe subscription and swaps the subscription item's Price.
\Benecaster\Payment\StripeClient Supplies the show's Stripe mode for subscriptions that did not record one at creation.
\Benecaster\Notices\NoticeManager Publishes the after-action notice to the podcaster.
\Benecaster\Membership\PriceChange\PriceChangeMailer Sends due reminder emails.
?\Benecaster\Membership\BuyupRepository Optional. Resolves the new Stripe Price for a buy-up change. A default instance is created when null.
?\Benecaster\Membership\SubscriberBuyupRepository Optional. Checks the holder still has the buy-up. A default instance is created when null.
?\Benecaster\Membership\BuyupPriceRepository Optional. Resolves the new Stripe Price from the targeted buy-up price row. A default instance is created when null.

Methods

Method Visibility Since Description
register(): void Public — Attaches run() to the benecaster_price_change_cron action and schedules that event hourly if it is not already scheduled.
run(): void Public — One hourly pass: calls send_due_reminders() and then apply_due(), both with the current time.
send_due_reminders( int $now ): int Public — Sends the optional reminder email to each subscriber whose reminder time has arrived and whose first new charge is still ahead. Considers pending and applied changes that have a reminder set, and subscriber rows that are pending or applied, have a known first-charge date, have not already been reminded, and belong to a live subscription. Rows anonymised by data erasure are skipped. A row is marked reminded before the send is attempted, so a disabled per-show reminder switch or a failed send is not retried every hour. Returns the number of reminders actually queued.
apply_due( int $now ): void Public — Runs apply_change() for every pending change whose effective date is at or before $now (a UTC timestamp), oldest first.
apply_change( array $change, int $now ): void Public — Processes one change: fires benecaster_price_change_apply_started with the count of pending subscribers, moves or skips each pending subscriber as described above, and returns without finishing when any row is still pending. When none are left it marks the change applied, publishes the podcaster notice (informational when everyone moved or had cancelled, critical and naming people when any were left on the old price or failed), and fires benecaster_price_change_applied. Fires benecaster_price_change_subscriber_applied for each subscriber moved. The Stripe update uses an idempotency key derived from the change and the item, so Stripe deduplicates a retried request.

Constants

Name Value Description
HOOK 'benecaster_price_change_cron' The name of the hourly cron event that runs the job.

Hooks Fired

Notes

The job depends on WordPress cron; on a site where cron does not run on time, the subscriber's next renewal can pass before the move, which only ever delays the new price. A change is not applied until every subscriber row has been moved, skipped or has failed.