Skip to content

License keys TTL doesn't play nice with subscriptions #11426

@pieterbeulque

Description

@pieterbeulque

User report

I have a customer who purchased a 1-year license on 4/27/26. That purchase gives a 30-day free trial.

If the free trial ends on 5/27/26, the 12-month license should expire on 5/27/27. However, the portal shows the expiration date as 4/27/27.

Analysis
The deeper issue is that the license-key benefit was designed around a one-shot TTL ("issue a key, expires N years from now") and that model doesn't compose with subscriptions at all:

  • Trial subscriptions — license starts ticking from purchase, not from when entitlement actually begins (Jennifer's case).
  • Renewalscycle() is a no-op (server/polar/benefit/strategies/license_keys/service.py:58-67), so an annual subscriber on a 1-year-TTL benefit gets a key that expires exactly when their subscription renews. After renewal the key is dead until something else re-grants it.
  • TTL ≠ billing period mismatch — a monthly subscriber on a 1-year-TTL benefit gets a 1-year key on day one (cancel-and-keep loophole). A yearly subscriber on a 1-month-TTL benefit gets a key that dies 11 months before their period ends.
  • Revocation already overrides the TTL — when a subscription is canceled the key is revoked regardless of expires_at, which proves the TTL was never the real source of truth for subscription benefits anyway.

The right model for a subscription benefit is: expires_at = subscription.current_period_end (or trial_end while trialing), extended on every cycle(). The standalone TTL config should really only apply to one-time-purchase grants.

Solution
The above feels like the correct way forward to marry license keys with subscriptions, but it's not backwards compatible and I'm not 100% sure how to go about it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions