PriceChangeRepository
\Benecaster\Membership\PriceChange\PriceChangeRepository
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.