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).
- Renewals —
cycle() 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.
User report
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:
cycle()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.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(ortrial_endwhile trialing), extended on everycycle(). 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.