Skip to main content

SubscriptionRepository — Billing Methods

Benecaster\Membership\SubscriptionRepository

Two methods added to SubscriptionRepository in feature/built-in-membership-portal-emails to support the billing email subsystem.

mark_cancelled()

SubscriptionRepository::mark_cancelled( int $id ): bool

Sets status = 'cancelled' and cancelled_at to the current UTC timestamp on the benecaster_subscriptions row identified by $id. Returns true on success, false on DB error.

Called by SubscriptionDeletedListener when customer.subscription.deleted arrives from Stripe. The row is not deleted — it is kept in place for historical reference and to distinguish subscribers who have a cancellation history from subscribers who never had a subscription.

find_renewal_candidates()

SubscriptionRepository::find_renewal_candidates(
    int    $show_id,
    string $window_start,
    string $window_end
): array

Returns benecaster_subscriptions rows for a show where current_period_end falls in the UTC half-open window [$window_start, $window_end). Used by RenewalReminderCron to find subscribers who should receive a renewal reminder on the current day.

Parameters:

Name Type Description
$show_id int Show post ID
$window_start string UTC datetime string (inclusive). Typically midnight + N days.
$window_end string UTC datetime string (exclusive). Typically midnight + (N+1) days.

Returns: Array of subscription row objects (same shape as find_active_by_user_show()).

Explicit exclusions — rows that are NOT returned:

Condition Reason
cancel_at_period_end = 1 Subscriber already cancelled — a renewal reminder is misleading
Status past_due Active payment failure — renewal reminder would conflict with the payment_failed flow
Status not in active, trialing Only active and trialing rows receive reminders

The past_due exclusion is load-bearing: RenewalReminderCron must not overlap with InvoicePaymentFailedListener’s messaging. A subscriber whose payment just failed should receive the payment_failed email, not a renewal reminder.