Skip to content

Add refactored subscription migration system - #2134

Open
amans2k wants to merge 7 commits into
releasefrom
fix/2133
Open

Add refactored subscription migration system#2134
amans2k wants to merge 7 commits into
releasefrom
fix/2133

Conversation

@amans2k

@amans2k amans2k commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Close #2133

Summary

  • Adds a namespaced, class-based subscription migration system (src/Migration/) for migrating WooCommerce subscriptions from legacy PayPal Express to PPCP
  • Wires the migration autoloader into the PPCP core trait bootstrap
  • Includes admin UI page with progress tracking, failed subscription management, retry/reset actions, and CSV export
  • Updates minimum PHP requirement to 8.1 (enums, readonly properties, union types)
  • Adds PHPUnit 10 with 48 unit tests (143 assertions) covering DTOs, enums, token validation, and migration service orchestration

Key fixes applied during integration

  • Removed duplicate get_failed_subscriptions() method that caused parameter type mismatch breaking retry_failed()
  • Fixed double mark_started() call in retry() that was incrementing attempt counters twice
  • Made AJAX handlers accept payment_method from POST data instead of hardcoding paypal_express

Test plan

  • All 48 unit tests pass (composer test)
  • Verify admin page loads under WooCommerce → PayPal Migration
  • Start a test migration with PayPal Express subscriptions
  • Verify retry and reset actions work from admin UI
  • Confirm batch processing via Action Scheduler completes correctly

🤖 Generated with Claude Code

amans2k and others added 6 commits March 8, 2026 18:01
Introduces a namespaced, class-based migration system under src/Migration/
for migrating WooCommerce subscriptions from legacy PayPal Express to PPCP.

Key components:
- Migration_Controller: facade/entry point with singleton pattern
- Subscription_Migration_Service: core migration logic
- Action_Scheduler_Batch_Processor: async batch processing
- HPOS_Migration_State_Storage: HPOS-compatible state tracking
- Migration_Admin_Page: WP admin UI with AJAX handlers
- DTOs, Enums, Contracts for type safety

Fixes applied during integration:
- Removed duplicate get_failed_subscriptions() method that caused
  parameter type mismatch (string vs array) breaking retry_failed()
- Updated retry_failed() to work with detailed result objects
- Removed double mark_started() call in retry() preventing
  duplicate attempt counter increments
- Made AJAX handlers accept payment_method from POST data
  instead of hardcoding 'paypal_express'

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Load the refactored migration autoloader and initialize it
during PPCP core initialization, alongside the existing
migration class.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The new migration code uses PHP 8.1 features (enums, readonly
properties, union types). Update composer.json to reflect the
actual minimum supported version.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Includes tests for:
- Migration_Status enum (terminal states, failure checks, labels, CSS classes)
- Migration_Result DTO (factory methods, validation, serialization)
- Batch_Result DTO (aggregation, success rate, filtering)
- Payment_Token_Validator (format validation, masking, multi-key lookup)
- Subscription_Migration_Service (process_single, process_batch, retry,
  error handling, edge cases)

Tests use WP/WC function stubs so they run without a full WordPress
environment. 48 tests, 143 assertions.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copilot AI review requested due to automatic review settings March 8, 2026 15:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a refactored, namespaced subscription migration subsystem under src/Migration/ to migrate WooCommerce Subscriptions from legacy PayPal Express to PPCP, including state tracking, Action Scheduler batch processing, an admin management UI, and a PHPUnit 10 unit test suite (and raising the project’s Composer PHP requirement to 8.1).

Changes:

  • Added a new AngellEYE\PayPal\Migration module (controller, services, DTOs, enums, HPOS-aware state storage, Action Scheduler batch processor, and an admin UI page).
  • Wired the migration module into the PPCP bootstrap and provided a standalone autoloader + init hooks.
  • Added PHPUnit-based unit tests and a composer test script; updated composer.json minimum PHP to >=8.1.0.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
tests/Migration/stubs.php WP/WC stubs to run unit tests without WordPress.
tests/Migration/phpunit.xml PHPUnit 10 configuration for Migration unit tests.
tests/Migration/bootstrap.php Loads stubs + Migration autoloader for tests.
tests/Migration/Unit/Test_Subscription_Migration_Service.php Unit tests for batch/single/retry orchestration.
tests/Migration/Unit/Test_Payment_Token_Validator.php Unit tests for token validation + masking.
tests/Migration/Unit/Test_Migration_Status_Enum.php Unit tests for enum helpers.
tests/Migration/Unit/Test_Migration_Result_DTO.php Unit tests for Migration_Result DTO behavior.
tests/Migration/Unit/Test_Batch_Result_DTO.php Unit tests for Batch_Result DTO aggregation.
tests/Migration/.phpunit.cache/test-results PHPUnit cache artifact (should not be committed).
src/Migration/autoload.php Custom autoloader + init functions + AS hook registration.
src/Migration/State/HPOS_Migration_State_Storage.php HPOS-aware state meta read/write + stats + pending queries.
src/Migration/Services/Subscription_Migration_Service.php Core migration logic: validate token, update method, track state.
src/Migration/Services/Payment_Token_Validator.php Token meta scanning, format checks, masking, diagnostics.
src/Migration/Services/Payment_Method_Updater.php Applies payment method switch + notes + hooks.
src/Migration/Queue/Action_Scheduler_Batch_Processor.php AS-based scheduling, execution, logging, lifecycle controls.
src/Migration/Migration_Controller.php Facade API: start/stop/stats/retry/reset and failed listing.
src/Migration/Enums/Migration_Status.php Enum for lifecycle states + labels + CSS classes.
src/Migration/DTOs/Migration_Stats.php Migration stats DTO (completion %, success rate, estimates).
src/Migration/DTOs/Migration_Result.php Migration result DTO (success/failed/skipped) + serialization.
src/Migration/DTOs/Batch_Result.php Batch result DTO with aggregation helpers + serialization.
src/Migration/Contracts/Migration_Step_Interface.php Interface placeholder for step-based migrations.
src/Migration/Contracts/Migration_State_Storage_Interface.php Contract + meta key constants for state storage.
src/Migration/Contracts/Batch_Processor_Interface.php Contract for batch processing implementations.
src/Migration/Admin/Migration_Admin_Page.php Admin UI page + AJAX handlers + export/retry/reset actions.
ppcp-gateway/includes/trait-angelleye-ppcp-core.php Boots the new migration module from PPCP core bootstrap.
composer.json Raises PHP min to 8.1, adds PHPUnit 10 + test script.
CLAUDE.md Repository contributor guidance (now needs updates for PHP/tests).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +104 to +109
as_unschedule_action(
$this->hook_name,
[
'from' => $from_payment_method,
'to' => $to_payment_method,
],

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pause() unschedules using only from/to args, but scheduled actions also include batch_size. If Action Scheduler requires exact arg matches, this call may fail to pause migrations. Consider including batch_size in the unschedule args or changing the scheduled args shape so pause/cancel can match.

Suggested change
as_unschedule_action(
$this->hook_name,
[
'from' => $from_payment_method,
'to' => $to_payment_method,
],
// Unschedule all pending migration actions for this hook and group.
// Using null for $args ensures we don't rely on a specific argument shape,
// which might include additional parameters like batch_size.
as_unschedule_all_actions(
$this->hook_name,
null,

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +100
$controller = \AngellEYE\PayPal\Migration\Migration_Controller::instance();
$controller->process_batch($from_payment_method, $to_payment_method, $batch_size);
}

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global Action Scheduler callback processes a batch but doesn’t reschedule the next batch when there’s more work. If this is the only callback available in a cron request, the migration will stop after a single batch. Route scheduled actions through the batch processor’s handler (which schedules the next batch).

Copilot uses AI. Check for mistakes.
Comment thread CLAUDE.md
vendor/bin/phpcbf --standard=phpcs.xml.dist path/to/file.php
```

No build step — JS/CSS are maintained directly (no Webpack/Gulp). No automated test suite exists.

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line says no automated test suite exists, but this PR adds PHPUnit tests and a composer test script. Update the guidance so contributors discover and run the new test suite.

Suggested change
No build step — JS/CSS are maintained directly (no Webpack/Gulp). No automated test suite exists.
No build step — JS/CSS are maintained directly (no Webpack/Gulp). PHPUnit test suite available — run via `composer test`.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +66
$asset_url = plugins_url('assets/', PAYPAL_FOR_WOOCOMMERCE_PLUGIN_FILE);

// CSS
wp_enqueue_style(
'angelleye-ppcp-migration-admin',
$asset_url . 'css/migration-admin.css',
[],
'1.0.0'
);

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enqueues assets/css/migration-admin.css, but that file doesn’t appear to exist in the repository’s assets/css/ directory. This will result in a 404 and unstyled admin UI. Either add the CSS file at that path or update the enqueue to point at the actual stylesheet location.

Suggested change
$asset_url = plugins_url('assets/', PAYPAL_FOR_WOOCOMMERCE_PLUGIN_FILE);
// CSS
wp_enqueue_style(
'angelleye-ppcp-migration-admin',
$asset_url . 'css/migration-admin.css',
[],
'1.0.0'
);
$asset_url = plugins_url('assets/', PAYPAL_FOR_WOOCOMMERCE_PLUGIN_FILE);
// CSS
$css_file_path = $asset_path . 'css/migration-admin.css';
if ( file_exists( $css_file_path ) ) {
wp_enqueue_style(
'angelleye-ppcp-migration-admin',
$asset_url . 'css/migration-admin.css',
[],
'1.0.0'
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +191
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'limit' => -1,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'value' => $status->value,
'compare' => '=',
],
],
];

$status_counts[$status->value] = count(wc_get_orders($args));
}

// Count not started
$args = [
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'limit' => -1,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'compare' => 'NOT EXISTS',
],
],
];
$status_counts['not_started'] = count(wc_get_orders($args));

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_stats() counts orders by fetching all matching IDs (limit => -1, return => ids) and then count()ing them. On large stores and frequent AJAX refreshes this can be very expensive. Prefer an approach that returns totals without loading all IDs (e.g., a paginated query with total, or a dedicated COUNT query) and/or cache short-term.

Suggested change
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'limit' => -1,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'value' => $status->value,
'compare' => '=',
],
],
];
$status_counts[$status->value] = count(wc_get_orders($args));
}
// Count not started
$args = [
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'limit' => -1,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'compare' => 'NOT EXISTS',
],
],
];
$status_counts['not_started'] = count(wc_get_orders($args));
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'paginate' => true,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'value' => $status->value,
'compare' => '=',
],
],
];
$result = wc_get_orders( $args );
$status_counts[ $status->value ] = isset( $result['total'] ) ? (int) $result['total'] : 0;
}
// Count not started
$args = [
'type' => 'shop_subscription',
'status' => ['wc-active', 'wc-on-hold', 'wc-pending-cancel'],
'payment_method' => $payment_method,
'paginate' => true,
'return' => 'ids',
'meta_query' => [
[
'key' => self::META_STATUS,
'compare' => 'NOT EXISTS',
],
],
];
$result = wc_get_orders( $args );
$status_counts['not_started'] = isset( $result['total'] ) ? (int) $result['total'] : 0;

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +49
$migration_autoload = PAYPAL_FOR_WOOCOMMERCE_PLUGIN_DIR . '/src/Migration/autoload.php';
if (file_exists($migration_autoload) && !function_exists('angelleye_ppcp_migration_init')) {
include_once $migration_autoload;
angelleye_ppcp_migration_init();

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored migration code uses PHP 8.1+ features (enums, etc.). This include happens without a PHP-version guard, so on older PHP it can fatal as soon as it’s loaded. If older PHP may still run this plugin, add a PHP_VERSION_ID/version_compare() check before including src/Migration/autoload.php / calling angelleye_ppcp_migration_init().

Suggested change
$migration_autoload = PAYPAL_FOR_WOOCOMMERCE_PLUGIN_DIR . '/src/Migration/autoload.php';
if (file_exists($migration_autoload) && !function_exists('angelleye_ppcp_migration_init')) {
include_once $migration_autoload;
angelleye_ppcp_migration_init();
if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 80100) {
$migration_autoload = PAYPAL_FOR_WOOCOMMERCE_PLUGIN_DIR . '/src/Migration/autoload.php';
if (file_exists($migration_autoload) && !function_exists('angelleye_ppcp_migration_init')) {
include_once $migration_autoload;
angelleye_ppcp_migration_init();
}

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +147
$pending_actions = as_get_scheduled_actions([
'hook' => $this->hook_name,
'args' => [
'from' => $from_payment_method,
'to' => $to_payment_method,
],
'status' => \ActionScheduler_Store::STATUS_PENDING,
'group' => $this->group,
]);

return !empty($pending_actions);

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_running() queries scheduled actions by from/to only, but scheduled actions also include batch_size. If args must match exactly, is_running() may return false even when a migration is queued, allowing duplicate schedules.

Suggested change
$pending_actions = as_get_scheduled_actions([
'hook' => $this->hook_name,
'args' => [
'from' => $from_payment_method,
'to' => $to_payment_method,
],
'status' => \ActionScheduler_Store::STATUS_PENDING,
'group' => $this->group,
]);
return !empty($pending_actions);
// Query all pending actions for this hook/group, then match by args in PHP.
$pending_action_ids = as_get_scheduled_actions([
'hook' => $this->hook_name,
'status' => \ActionScheduler_Store::STATUS_PENDING,
'group' => $this->group,
'return' => 'ids',
]);
foreach ($pending_action_ids as $action_id) {
$action = as_get_scheduled_action($action_id);
if (! $action) {
continue;
}
$args = $action->get_args();
if (
isset($args['from'], $args['to']) &&
$args['from'] === $from_payment_method &&
$args['to'] === $to_payment_method
) {
return true;
}
}
return false;

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
{"version":2,"defects":[],"times":{"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_calculates_totals_correctly":0.003,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_success_rate_calculation":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_success_rate_returns_zero_for_empty_batch":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_failures_returns_only_failed_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_successes_returns_only_successful_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_skipped_returns_only_skipped_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_failures_returns_true_when_failures_exist":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_failures_returns_false_when_no_failures":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_more_flag_is_stored":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_to_array_contains_all_fields":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_success_creates_completed_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_failed_creates_failure_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_failed_throws_exception_for_non_failure_status":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_skipped_creates_skipped_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_to_array_contains_all_fields":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_processed_at_is_set_automatically":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_is_skipped_returns_true_for_skipped_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_is_skipped_returns_false_for_non_skipped":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_true_for_completed":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_true_for_failed_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_false_for_in_progress":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_false_for_not_started":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_failure_returns_true_for_failed_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_failure_returns_false_for_non_failed_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_label_returns_expected_strings":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_css_class_returns_expected_values":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_all_statuses_can_be_instantiated_from_string":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_tryFrom_returns_null_for_invalid_value":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_payment_tokens_id":0.005,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_ppec_billing_agreement":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_paypal_subscription_id":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_for_invalid_subscription_id":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_when_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_for_short_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_token_details_returns_correct_info":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_token_details_masks_token_value":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_all_token_attempts_returns_all_checks":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_skips_already_processed":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_fails_for_missing_subscription":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_fails_when_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_succeeds_with_valid_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_catches_update_exception":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_returns_correct_counts":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_empty_returns_zero_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_sets_has_more_when_remaining":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_calls_process_single_and_returns_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_throws_for_nonexistent_subscription":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_uses_old_payment_method_meta_when_available":0}} No newline at end of file

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPUnit cache artifacts are committed (tests/Migration/.phpunit.cache/test-results). These files are machine-specific and should typically be gitignored rather than versioned. Consider removing this file from the repo and adding tests/Migration/.phpunit.cache/ (or .phpunit.cache/) to .gitignore.

Suggested change
{"version":2,"defects":[],"times":{"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_calculates_totals_correctly":0.003,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_success_rate_calculation":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_success_rate_returns_zero_for_empty_batch":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_failures_returns_only_failed_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_successes_returns_only_successful_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_get_skipped_returns_only_skipped_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_failures_returns_true_when_failures_exist":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_failures_returns_false_when_no_failures":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_has_more_flag_is_stored":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Batch_Result_DTO::test_to_array_contains_all_fields":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_success_creates_completed_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_failed_creates_failure_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_failed_throws_exception_for_non_failure_status":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_skipped_creates_skipped_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_to_array_contains_all_fields":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_processed_at_is_set_automatically":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_is_skipped_returns_true_for_skipped_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Result_DTO::test_is_skipped_returns_false_for_non_skipped":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_true_for_completed":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_true_for_failed_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_false_for_in_progress":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_terminal_returns_false_for_not_started":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_failure_returns_true_for_failed_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_is_failure_returns_false_for_non_failed_statuses":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_label_returns_expected_strings":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_css_class_returns_expected_values":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_all_statuses_can_be_instantiated_from_string":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Migration_Status_Enum::test_tryFrom_returns_null_for_invalid_value":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_payment_tokens_id":0.005,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_ppec_billing_agreement":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_true_for_paypal_subscription_id":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_for_invalid_subscription_id":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_when_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_has_valid_token_returns_false_for_short_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_token_details_returns_correct_info":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_token_details_masks_token_value":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Payment_Token_Validator::test_get_all_token_attempts_returns_all_checks":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_skips_already_processed":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_fails_for_missing_subscription":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_fails_when_no_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_succeeds_with_valid_token":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_single_catches_update_exception":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_returns_correct_counts":0.001,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_empty_returns_zero_results":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_process_batch_sets_has_more_when_remaining":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_calls_process_single_and_returns_result":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_throws_for_nonexistent_subscription":0,"AngellEYE\\PayPal\\Migration\\Tests\\Unit\\Test_Subscription_Migration_Service::test_retry_uses_old_payment_method_meta_when_available":0}}
# Placeholder: PHPUnit .phpunit.cache/test-results file should not contain committed cache data.

Copilot uses AI. Check for mistakes.
return;
}

$asset_path = plugin_dir_path(PAYPAL_FOR_WOOCOMMERCE_PLUGIN_FILE) . 'assets/';

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enqueue_assets() assigns $asset_path but never uses it. This is dead code and can be removed to avoid confusion (or use it for file existence/versioning if that was the intent).

Suggested change
$asset_path = plugin_dir_path(PAYPAL_FOR_WOOCOMMERCE_PLUGIN_FILE) . 'assets/';

Copilot uses AI. Check for mistakes.
Comment on lines +130 to +133
$total = $stats['total'] ?? 0;
$completed = $stats['completed'] ?? 0;
$failed = $stats['failed'] ?? 0;
$pending = $total - $completed - $failed;

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$pending is computed as total - completed - failed, which ignores skipped statuses. If any subscriptions are marked skipped_*, the banner will overstate “remaining” subscriptions. Consider computing pending as not_started + in_progress (or subtract skipped as well).

Suggested change
$total = $stats['total'] ?? 0;
$completed = $stats['completed'] ?? 0;
$failed = $stats['failed'] ?? 0;
$pending = $total - $completed - $failed;
$total = $stats['total'] ?? 0;
$completed = $stats['completed'] ?? 0;
$failed = $stats['failed'] ?? 0;
$not_started = $stats['not_started'] ?? null;
$in_progress = $stats['in_progress'] ?? null;
if ($not_started !== null || $in_progress !== null) {
$pending = (int) ($not_started ?? 0) + (int) ($in_progress ?? 0);
} else {
// Fallback to legacy calculation when detailed status counts are unavailable.
$pending = $total - $completed - $failed;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor subscription migration system to fix infinite loop, add state tracking and HPOS support

2 participants