Skip to main content

LicenseController

\Benecaster\REST\LicenseController

Class Premium

REST controller for licence state, on-demand validation, one-click plan upgrade, and enforcement and telemetry preferences. Registers five routes in the benecaster/v1 namespace.

GET /license returns current licence state read straight from wp_options — no network call. POST /license/validate runs a full validation cycle through LicenseValidationCron::run() and returns the updated state. POST /license/preferences stores the subscriber-limit and token-cap enforcement choices (auto_upgrade or cap), both honoured on every plan including Launch. POST /license/upgrade confirms a one-click plan upgrade from Settings → Account → Plan changes. POST /license/telemetry-preferences stores the two opt-in flags, forcing revenue telemetry off whenever the base opt-in is off.

Connection is not handled here. Connecting and disconnecting a site is per-show OAuth/PKCE in LicenseOAuthControllerPOST /license/oauth/start and POST /license/oauth/deactivate. Those are the routes the Subscription tab and the setup wizard call.

Constructor Dependencies

Type Description
\Benecaster\License\LicenseManager Plan, add-on, referral and enforcement-preference reads.
\Benecaster\License\LicenseValidationCron Runs the validation cycle behind POST /license/validate.
\Benecaster\License\CapChecker Subscriber, show and limit counts for the licence payload.
\Benecaster\Token\TokenRepository Site-wide active and paying token counts.

Methods

Method Visibility Since Description
register_routes(): void Public Registers the five benecaster/v1 licence routes.
get_license(): WP_REST_Response Public Returns current licence state from wp_options. No network call.
validate(): WP_REST_Response Public Triggers an immediate validation cycle and returns the updated licence state.
subscriber_count(): WP_REST_Response Public Returns paying_subscriber_count and total_token_count. Guarded by permission_callback_license_key() — see the notes; unreachable on an OAuth-connected install.
set_preferences( WP_REST_Request $request ): WP_REST_Response Public Stores subscriber_limit_enforcement and token_cap_enforcement. Defaults are auto_upgrade for subscriber enforcement and cap for token enforcement. Returns the resolved values.
upgrade( WP_REST_Request $request ): WP_REST_Response|WP_Error Public Confirms a one-click plan upgrade (Settings → Account → Plan changes → Upgrade plan). Resolves the first connected show's bearer token, calls LicenseClient::upgrade() with target_interval 'monthly' first and retries once with 'annual' only on a 409 interval_mismatch refusal — the controller has no cached billing interval to consult. Persists benecaster_license_plan locally only once the server confirms the new plan, then fires benecaster_license_plan_upgraded when the plan actually changed. Any WP_Error from the client (network failure or a refusal reason/message from the server) is surfaced as a REST error with the server's own customer-facing message where one was supplied.
set_telemetry_preferences( WP_REST_Request $request ): WP_REST_Response Public Stores telemetry_opted_in and revenue_telemetry_opted_in. Revenue opt-in is forced off after the writes whenever the base opt-in is off, so a single request that clears the base flag also clears the revenue flag.
permission_callback_license_key( WP_REST_Request $request ): bool Public Compares the Authorization bearer value against the stored benecaster_license_key with hash_equals(). No nonce and no cookie auth — the intended caller is the license server, not a WordPress user. Returns false when no key is stored.

Hooks Fired

  • benecaster-license-plan-upgraded

Notes

POST /license/activate and POST /license/deactivate were REMOVED in feature/retire-legacy-license-key (2026-08-31) and must not be reintroduced. They were the pre-OAuth licence-key flow: activate() wrote benecaster_license_key and was the only writer of that option. Nothing reached either route — useActivateLicense() and useDeactivateLicense() existed in the admin bundle but were imported by no component, and the license server has no pull path that used them. The constructor's LicenseClient dependency went with them; the class now takes LicenseManager, LicenseValidationCron, CapChecker, TokenRepository.

Use POST /license/oauth/start and POST /license/oauth/deactivate on LicenseOAuthController instead.

deactivate() also cleared site-level licence state — plan, add-ons, next renewal date, checkout URL, referral options and push secret. Nothing does that now, because nothing calls it and per-show OAuth deactivation is scoped to one show's token by design.

benecaster_license_key is gone entirely as of bugfix/remove-license-key-surface (2026-08-31). The option is deregistered and the plugin has no reader and no writer for it anywhere. The three surfaces that read it went with it: permission_callback_license_key(), the license_key_masked field on GET /license, and DiagnosticLogSender's license_key_hash payload entry.

GET /license/subscriber-count was removed with them. It authenticated by comparing against that option, which no install has ever held, so the callback returned false unconditionally and the route could only ever answer 401. Subscriber counts are pushed on POST /validate by ValidationPayloadBuilder — there has never been a pull path, and the license server was verified never to call the route. Do not reintroduce it, and in particular do not "fix" the auth by widening it to the site token: that would turn a route nothing needs into a live inbound surface.

This class registers five routes: /license, /license/validate, /license/preferences, /license/upgrade, /license/telemetry-preferences. ⚠ **This is the first time that count has been accurate against the actual route table** — the description above claimed five routes while only four were ever registered (the removed /license/subscriber-count had already been subtracted, leaving the claim one high); /license/upgrade (feature/one-click-upgrade, 2026-09-11) is what makes five true.