Skip to main content

PriceChangeRepository

\Benecaster\Membership\PriceChange\PriceChangeRepository

Class Premium

Database access for price changes. It reads and writes two tables: benecaster_price_changes, one row per change (its target_type is tier_price or buyup_price), and benecaster_price_change_subscribers, one row per subscriber told about it, holding that subscriber’s status, skip reason, first charge date at the new price, reminder-sent time and any Stripe error message. Every datetime it stores is UTC, and it compares against a “now” value passed in rather than the database clock.

A change is pending (notice sent, not applied), applied (the apply pass has run over every subscriber), cancelled (the podcaster cancelled it) or superseded (replaced by a later change to the same price). A subscriber row is pending, applied, skipped (with a skip_reason), failed (with a message) or cancelled. The status values are exposed as constants.

This is internal storage, used by the scheduler, the applier, the price-change admin endpoints, the account page and the personal-data export and erasure. It is not a public API and it applies no permission checks. To follow a change from an add-on, use the benecaster_price_change_* hooks rather than querying these tables.

Methods

Method Visibility Since Description
insert_change( array $data ): int Public — Inserts a change with status pending. Requires show_id, target_type, target_id, old_amount_cents, new_amount_cents, currency, notice_days, created_at and effective_at; also stores tier_id, item_name, interval_unit, interval_count, the old Stripe Price IDs for test and live mode, reminder_days and created_by. Returns the new ID, or 0 when the insert fails.
find_change( int $id ): ?array Public — Returns one change as an array (numbers cast to int, empty strings as null for the nullable columns, completed_at null until finished), or null when it does not exist.
find_pending_for_target( string $target_type, int $target_id ): ?array Public — Returns the pending change on a tier price or buy-up, or null. There is at most one, because a new change supersedes the old one before it is inserted.
find_due( string $now_utc ): array Public — Returns pending changes whose effective_at is at or before $now_utc (a Y-m-d H:i:s UTC string), oldest effective date first.
find_with_reminders(): array Public — Returns pending and applied changes that have a reminder configured. Applied changes are included because a reminder can fall after the change was applied, before the first charge at the new price.
find_by_show( int $show_id, ?int $tier_id = null, ?string $status = null ): array Public — Returns a show's changes, newest first, optionally narrowed to one tier or one status, up to 200 rows. This is the admin list's source.
mark_change( int $id, string $status, string $now_utc ): bool Public — Sets a change's status and sets its completed_at to $now_utc, whatever the status. Returns false only when the database update fails.
insert_subscriber( array $data ): int Public — Inserts a subscriber row with status pending. Requires change_id, subscription_id, user_id and notified_at; first_new_charge_at is optional. Returns the new ID, or 0 on failure.
find_subscribers( int $change_id, ?string $status = null ): array Public — Returns a change's subscriber rows in insert order, optionally only those with one status. user_id is null on a row anonymised by erasure.
count_subscribers_by_status( int $change_id ): array Public — Returns counts keyed by pending, applied, skipped, failed and cancelled, each present even when zero.
find_upcoming_for_subscription( int $subscription_id, string $now_utc ): array Public — Returns the upcoming price changes for one subscription, as a list of { change, subscriber } pairs: subscriber rows that are pending on a still-pending change, or applied with a first new charge still in the future. Anonymised rows are never returned. The account page uses it.
find_for_user( int $user_id ): array Public — Returns every price change a person was told about, in every status, as { change, subscriber } pairs where change holds only id, show_id, item_name, old_amount_cents, new_amount_cents, currency and status. Returns an empty array for a user ID below 1. The personal-data export uses it.
anonymize_for_user( int $user_id ): int Public — Personal-data erasure. Sets user_id and message to null on every subscriber row for that user and returns the number of rows changed; 0 for a user ID below 1. Statuses, skip reasons, timestamps and the subscription link are kept, and no benecaster_price_changes row is touched.
update_subscriber( int $id, array $changes ): bool Public — Updates a subscriber row. Only status, skip_reason, first_new_charge_at, reminder_sent_at, applied_at and message are written; any other key is ignored. Returns true when there was nothing to write, and false only when the database update fails.

Constants

Name Value Description
TARGET_TIER_PRICE 'tier_price' target_type for a change to one billing cadence of a membership tier.
TARGET_BUYUP_PRICE 'buyup_price' target_type for a change to one of a buy-up's price rows; target_id is a benecaster_buyup_prices row. Replaced TARGET_BUYUP ('buyup'), which pointed at the buy-up itself. Pending 'buyup' changes were cancelled by the 1.72.0 upgrade, since nothing can apply them.
STATUS_PENDING 'pending' Change status: notice sent, not yet applied.
STATUS_APPLIED 'applied' Change status: the apply pass has run over every subscriber row.
STATUS_CANCELLED 'cancelled' Change status: the podcaster cancelled it, or its price or buy-up was deleted.
STATUS_SUPERSEDED 'superseded' Change status: replaced by a later change to the same price. No email is sent for it.
SUB_PENDING 'pending' Subscriber status: told about the change, not yet moved.
SUB_APPLIED 'applied' Subscriber status: moved onto the new price.
SUB_SKIPPED 'skipped' Subscriber status: deliberately left on the current price. skip_reason says why.
SUB_FAILED 'failed' Subscriber status: a Stripe error stopped the move. message holds the error text.
SUB_CANCELLED 'cancelled' Subscriber status: the change was withdrawn or superseded before this subscriber was moved. It does not mean the membership ended.

Notes

A subscriber row's user_id can be null: a personal-data erasure anonymises the row, clearing user_id and message but keeping every status and timestamp, so counts per change do not change. Code that reads these rows must handle a null user_id. The erasure also means the plugin can no longer show that a particular person was sent the notice, because the email log is erased in the same request; whether a podcaster must keep that proof is a legal question for the podcaster.