From 18444621be09237a5ade4fa79e54c8d31eb4e473 Mon Sep 17 00:00:00 2001 From: shazzad Date: Wed, 25 Feb 2026 22:49:25 +0600 Subject: [PATCH] Clean up stale license data on invalid_license error When a license is deleted upstream, the API returns an invalid_license WP_Error but locally stored license code and data remained in the database. This adds cleanup logic to Admin (save/sync) and Tracker (hourly cron) to delete stored license code and data on that error. - Add delete_license_code() and delete_license_data() helpers to Integration - Handle invalid_license error in Admin::handle_save() and handle_sync() - Handle invalid_license error in Tracker::sync_license_data() - Refactor bare delete_option() in handle_save() to use delete_license_code() - Add unit tests for the new delete helpers Co-Authored-By: Claude Opus 4.6 --- src/Admin.php | 12 +++++++++++- src/Integration.php | 22 ++++++++++++++++++++++ src/Tracker.php | 5 +++++ tests/IntegrationLicenseTest.php | 24 ++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Admin.php b/src/Admin.php index aff5f84..1276986 100644 --- a/src/Admin.php +++ b/src/Admin.php @@ -116,6 +116,11 @@ private function handle_sync() { $response = $this->integration->client->check_license( $key ); if ( is_wp_error( $response ) ) { + if ( 'invalid_license' === $response->get_error_code() ) { + $this->integration->delete_license_code(); + $this->integration->delete_license_data(); + } + wp_redirect( add_query_arg( 'error', @@ -164,7 +169,7 @@ private function handle_save() { $base_url = remove_query_arg( [ 'm' ] ); if ( empty( $_POST['wprepo_license'] ) ) { - delete_option( $this->integration->get_license_code_key() ); + $this->integration->delete_license_code(); $this->integration->clear_updates_transient(); wp_redirect( add_query_arg( @@ -180,6 +185,11 @@ private function handle_save() { $response = $this->integration->client->check_license( $key ); if ( is_wp_error( $response ) ) { + if ( 'invalid_license' === $response->get_error_code() ) { + $this->integration->delete_license_code(); + $this->integration->delete_license_data(); + } + wp_redirect( add_query_arg( 'error', diff --git a/src/Integration.php b/src/Integration.php index da6a344..d05f462 100644 --- a/src/Integration.php +++ b/src/Integration.php @@ -309,6 +309,28 @@ public function update_license_data( $data ) { return update_option( $this->get_license_data_key(), $data ); } + /** + * Deletes the license code from the database. + * + * @since 1.2 + * + * @return bool True if the option was deleted, false otherwise. + */ + public function delete_license_code() { + return delete_option( $this->get_license_code_key() ); + } + + /** + * Deletes the license data from the database. + * + * @since 1.2 + * + * @return bool True if the option was deleted, false otherwise. + */ + public function delete_license_data() { + return delete_option( $this->get_license_data_key() ); + } + /** * Checks if the license is currently active. * diff --git a/src/Tracker.php b/src/Tracker.php index a108bc6..ae9262a 100644 --- a/src/Tracker.php +++ b/src/Tracker.php @@ -66,6 +66,11 @@ public function sync_license_data() { $response = $this->integration->client->check_license(); if ( is_wp_error( $response ) ) { + if ( 'invalid_license' === $response->get_error_code() ) { + $this->integration->delete_license_code(); + $this->integration->delete_license_data(); + } + return; } diff --git a/tests/IntegrationLicenseTest.php b/tests/IntegrationLicenseTest.php index 67532eb..62aae23 100644 --- a/tests/IntegrationLicenseTest.php +++ b/tests/IntegrationLicenseTest.php @@ -210,6 +210,30 @@ public function get_license_renewal_url_handles_missing_license_code() { ); } + /** @test */ + public function delete_license_code_calls_delete_option() { + $integration = $this->create_integration(); + + Functions\expect( 'delete_option' ) + ->once() + ->with( 'my-plugin42_code' ) + ->andReturn( true ); + + $this->assertTrue( $integration->delete_license_code() ); + } + + /** @test */ + public function delete_license_data_calls_delete_option() { + $integration = $this->create_integration(); + + Functions\expect( 'delete_option' ) + ->once() + ->with( 'my-plugin42_data' ) + ->andReturn( true ); + + $this->assertTrue( $integration->delete_license_data() ); + } + /** @test */ public function get_license_renewal_url_returns_url_without_placeholders() { $integration = $this->create_integration();