#14358·polar

Benefits never revoked when grace period expires while subscription stays past_due

Author: stilla[bot]Created Sep 10, 2026Updated Sep 10, 2026

Bug

When a subscription's org has benefit_revocation_grace_period > 0, benefit grants are never revoked once the grace period expires, as long as the subscription remains in past_due (still going through dunning retries). No benefit_grant.revoked / customer.state_changed webhook fires either. The customer-facing "Benefits were revoked on " message keeps showing the correct grace-period deadline, but nothing actually enforces it.

Root cause

In Subscription.enqueue_benefits_grants (server/polar/subscription/service.py), when a subscription becomes inactive, _is_within_revocation_grace_period is checked. If still within the grace window, the method just returns early without enqueuing the revoke job and without scheduling any follow-up check for when the grace period ends:

python
if task == "revoke":
    ...
    if await self._is_within_revocation_grace_period(
        session, subscription, organization
    ):
        # Don't enqueue revocation yet, still within grace period
        return

enqueue_benefits_grants is only invoked in response to subscription/order lifecycle events (status transitions, cancellations, dunning attempts, etc.), and there is no cron/scheduled task that re-checks past_due subscriptions once their grace period elapses. So if a subscription stays past_due throughout its dunning retry window (which routinely lasts longer than a short grace period), the grace-period expiry is never re-evaluated and the benefit grants stay is_granted: true indefinitely, until some unrelated event happens to call enqueue_benefits_grants again (e.g. the subscription is manually revoked, or dunning eventually ends and the subscription transitions to canceled/unpaid).

Repro reported by a customer

  • Grace period set to "After 2 Days"
  • Subscription went past_due on day 0
  • Order page correctly displayed "Benefits were revoked on <day 2>"
  • GET /v1/benefit-grants?customer_id=… kept returning the grants as is_granted: true well past day 2, with no customer.state_changed webhook, until the grants were manually revoked from the backoffice

Expected behavior

At grace period expiry, Polar should revoke the benefit grants, update customer state, and emit benefit_grant.revoked / customer.state_changed, matching the displayed revocation date, even if the subscription is still past_due mid-dunning.

Suggested fix

Schedule a delayed re-check (e.g. via the existing delay param on enqueue_benefits_grants/benefit.enqueue_benefits_grants) for grace_period_ends_at when a past_due subscription is found to still be within its grace period, or add a periodic cron job that scans past_due/unpaid subscriptions whose grace period has elapsed and enqueues the revoke.

Sent by @allison-polar from Benefit revocation grace period bug investigation.