Skip to content

Commit 10a0783

Browse files
james-allanmattallan
authored andcommitted
Add an admin notice for SEPA subscriptions migrations after disabling legacy checkout (#3422)
* Display an admin notice to inform users of the SEPA migration after disabling legacy checkout * Get any status * The next action should never be more than 3 hours so remove the else case * Add changelog entries * Display the notice for up to 3 days to limit the scope of the notice queries * Stop showing the notice after all subscriptions have been migrated * If admin re-enable legacy checkout and then later disable legacy checkout, we should make sure to reattempt the migrations * Update includes/migrations/class-wc-stripe-subscriptions-repairer-legacy-sepa-tokens.php Co-authored-by: Matt Allan <[email protected]> * Schedule jobs to run in 1 minute rather than 1 hour * Update notice to no longer include next update time case * Treat in-progress actions as $is_still_scheduling_jobs * Remove duplicated Paged param * Schedule individual SEPA updates to run in 2 minutes * Add a tool to enable users to restart the migration if needed * Replace "migrate" with "update" * Move variable around for slight performance improvement * Add changelog entry for new tool * Update includes/migrations/class-wc-stripe-subscriptions-repairer-legacy-sepa-tokens.php Co-authored-by: Matt Allan <[email protected]> --------- Co-authored-by: Matt Allan <[email protected]>
1 parent 1dfb9b5 commit 10a0783

File tree

4 files changed

+234
-1
lines changed

4 files changed

+234
-1
lines changed

changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
* Fix - Address Klarna availability based on correct presentment currency rules.
3030
* Fix - Use correct ISO country code of United Kingdom in supported country and currency list of AliPay and WeChat.
3131
* Fix - Prevent duplicate order notes and emails being sent when purchasing subscription products with no initial payment.
32+
* Add - Display an admin notice on the WooCommerce > Subscriptions screen for tracking the progress of SEPA subscriptions migrations after the legacy checkout is disabled.
33+
* Add - Introduce a new tool on the WooCommerce > Status > Tools screen to restart the legacy SEPA subscriptions update.
3234

3335
= 8.6.1 - 2024-08-09 =
3436
* Tweak - Improves the wording of the invalid Stripe keys errors, instructing merchants to click the "Configure connection" button instead of manually setting the keys.

includes/admin/class-wc-rest-stripe-settings-controller.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,12 @@ private function update_is_upe_enabled( WP_REST_Request $request ) {
455455
}
456456

457457
$settings = WC_Stripe_Helper::get_stripe_settings();
458+
459+
// If the new UPE is enabled, we need to remove the flag to ensure legacy SEPA tokens are updated flag.
460+
if ( $is_upe_enabled && ! WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
461+
delete_option( 'woocommerce_stripe_subscriptions_legacy_sepa_tokens_updated' );
462+
}
463+
458464
$settings[ WC_Stripe_Feature_Flags::UPE_CHECKOUT_FEATURE_ATTRIBUTE_NAME ] = $is_upe_enabled ? 'yes' : 'disabled';
459465
WC_Stripe_Helper::update_main_stripe_settings( $settings );
460466

includes/migrations/class-wc-stripe-subscriptions-repairer-legacy-sepa-tokens.php

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@
1212
*/
1313
class WC_Stripe_Subscriptions_Repairer_Legacy_SEPA_Tokens extends WCS_Background_Repairer {
1414

15+
/**
16+
* The transient key used to store the progress of the repair.
17+
*
18+
* @var string
19+
*/
20+
private $action_progress_transient = 'wc_stripe_legacy_sepa_tokens_repair_progress';
21+
22+
/**
23+
* The transient key used to store whether we should display the notice to the user.
24+
*
25+
* @var string
26+
*/
27+
private $display_notice_transient = 'wc_stripe_legacy_sepa_tokens_repair_notice';
28+
1529
/**
1630
* Constructor
1731
*
@@ -26,6 +40,10 @@ public function __construct( WC_Logger_Interface $logger ) {
2640

2741
// Repair subscriptions prior to renewal as a backstop. Hooked onto 0 to run before the actual renewal.
2842
add_action( 'woocommerce_scheduled_subscription_payment', [ $this, 'maybe_migrate_before_renewal' ], 0 );
43+
44+
add_action( 'admin_notices', [ $this, 'display_admin_notice' ] );
45+
46+
add_filter( 'woocommerce_debug_tools', [ $this, 'add_debug_tool' ] );
2947
}
3048

3149
/**
@@ -48,6 +66,9 @@ public function maybe_update() {
4866
// This will be handled in the scheduled action.
4967
$this->schedule_repair();
5068

69+
// Display the admin notice to inform the user that the repair is in progress. Limited to 3 days.
70+
set_transient( $this->display_notice_transient, 'yes', 3 * DAY_IN_SECONDS );
71+
5172
// Prevent the repair from being scheduled again.
5273
update_option( 'woocommerce_stripe_subscriptions_legacy_sepa_tokens_updated', 'yes' );
5374
}
@@ -67,11 +88,32 @@ public function repair_item( $subscription_id ) {
6788
$token_updater->maybe_update_subscription_legacy_payment_method( $subscription_id );
6889

6990
$this->log( sprintf( 'Successful migration of subscription #%1$d.', $subscription_id ) );
91+
92+
delete_transient( $this->action_progress_transient );
7093
} catch ( \Exception $e ) {
7194
$this->log( $e->getMessage() );
7295
}
7396
}
7497

98+
/**
99+
* Schedules an individual action to migrate a subscription.
100+
*
101+
* Overrides the parent class function to make two changes:
102+
* 1. Don't schedule an action if one already exists.
103+
* 2. Schedules the migration to happen in two minutes instead of in one hour.
104+
* 3. Delete the transient which stores the progress of the repair.
105+
*
106+
* @param int $item The ID of the subscription to migrate.
107+
*/
108+
protected function update_item( $item ) {
109+
if ( ! as_next_scheduled_action( $this->repair_hook, [ 'repair_object' => $item ] ) ) {
110+
as_schedule_single_action( gmdate( 'U' ) + ( 2 * MINUTE_IN_SECONDS ), $this->repair_hook, [ 'repair_object' => $item ] );
111+
}
112+
113+
unset( $this->items_to_repair[ $item ] );
114+
delete_transient( $this->action_progress_transient );
115+
}
116+
75117
/**
76118
* Gets the batch of subscriptions using the Legacy SEPA payment method to be updated.
77119
*
@@ -85,7 +127,6 @@ protected function get_items_to_repair( $page ) {
85127
'return' => 'ids',
86128
'type' => 'shop_subscription',
87129
'posts_per_page' => 20,
88-
'paged' => $page,
89130
'status' => 'any',
90131
'paged' => $page,
91132
'payment_method' => WC_Gateway_Stripe_Sepa::ID,
@@ -140,4 +181,186 @@ public function maybe_migrate_before_renewal( $subscription_id ) {
140181
$token_updater->maybe_update_subscription_source( $subscription );
141182
}
142183
}
184+
185+
/**
186+
* Displays an admin notice to inform the user that the repair is in progress.
187+
*
188+
* This notice is displayed on the Subscriptions list table page and includes information about the progress of the repair.
189+
* What % of the repair is complete, or when the next scheduled action is expected to run.
190+
*/
191+
public function display_admin_notice() {
192+
193+
if ( ! class_exists( 'WC_Subscriptions' ) || ! WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
194+
return;
195+
}
196+
197+
// Only display this on the subscriptions list table page.
198+
if ( ! $this->is_admin_subscriptions_list_table_screen() ) {
199+
return;
200+
}
201+
202+
// The notice is only displayed for up to 3 days after disabling the setting.
203+
$display_notice = get_transient( $this->display_notice_transient ) === 'yes';
204+
205+
if ( ! $display_notice ) {
206+
return;
207+
}
208+
209+
// If there are no subscriptions to be migrated, remove the transient so we don't show the notice.
210+
// Don't return early so we can show the notice at least once.
211+
if ( ! $this->has_legacy_sepa_subscriptions() ) {
212+
delete_transient( $this->display_notice_transient );
213+
}
214+
215+
$action_progress = $this->get_scheduled_action_counts();
216+
217+
if ( ! $action_progress ) {
218+
return;
219+
}
220+
221+
// If we're still in the process of scheduling jobs, show a note to the user.
222+
if ( (bool) as_next_scheduled_action( $this->scheduled_hook ) ) {
223+
// translators: %1$s: <strong> tag, %2$s: </strong> tag, %3$s: <i> tag. %4$s: </i> tag.
224+
$progress = sprintf( __( '%1$sProgress: %2$s %3$sWe are still identifying all subscriptions that require updating.%4$s', 'woocommerce-gateway-stripe' ), '<strong>', '</strong>', '<i>', '</i>' );
225+
} else {
226+
// All scheduled actions have run, so we're done.
227+
if ( 0 === absint( $action_progress['pending'] ) ) {
228+
// Remove the transient to prevent the notice from showing again.
229+
delete_transient( $this->display_notice_transient );
230+
}
231+
232+
// Calculate the percentage of completed actions.
233+
$total_action_count = $action_progress['pending'] + $action_progress['complete'];
234+
$compete_percentage = $total_action_count ? floor( ( $action_progress['complete'] / $total_action_count ) * 100 ) : 0;
235+
236+
// translators: %1$s: <strong> tag, %2$s: </strong> tag, %3$s: percentage complete.
237+
$progress = sprintf( __( '%1$sProgress: %2$s %3$s%% complete', 'woocommerce-gateway-stripe' ), '<strong>', '</strong>', $compete_percentage );
238+
}
239+
240+
// Note: We're using a Subscriptions class to generate the admin notice, however, it's safe to use given the context of this class.
241+
$notice = new WCS_Admin_Notice( 'notice notice-warning is-dismissible' );
242+
$notice->set_html_content(
243+
'<h4>' . esc_html__( 'SEPA subscription update in progress', 'woocommerce-gateway-stripe' ) . '</h4>' .
244+
'<p>' . __( "We are currently updating customer subscriptions that use the legacy Stripe SEPA Direct Debit payment method. During this update, you may notice that some subscriptions appear as manual renewals. Don't worry—renewals will continue to process as normal. Please be aware this process may take some time.", 'woocommerce-gateway-stripe' ) . '</p>' .
245+
'<p>' . $progress . '</p>'
246+
);
247+
248+
$notice->display();
249+
}
250+
251+
/**
252+
* Checks if the current screen is the subscriptions list table.
253+
*
254+
* @return bool True if the current screen is the subscriptions list table, false otherwise.
255+
*/
256+
private function is_admin_subscriptions_list_table_screen() {
257+
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
258+
return false;
259+
}
260+
261+
$screen = get_current_screen();
262+
263+
if ( ! is_object( $screen ) ) {
264+
return false;
265+
}
266+
267+
// Check if we are on the subscriptions list table page in a HPOS or WP_Post context.
268+
return in_array( $screen->id, [ 'woocommerce_page_wc-orders--shop_subscription', 'edit-shop_subscription' ], true );
269+
}
270+
271+
/**
272+
* Fetches the number of pending and completed migration scheduled actions.
273+
*
274+
* @return array|bool The counts of pending and completed actions. False if the Action Scheduler store is not available.
275+
*/
276+
private function get_scheduled_action_counts() {
277+
$action_counts = get_transient( $this->action_progress_transient );
278+
279+
// If the transient is not set, calculate the action counts.
280+
if ( false === $action_counts ) {
281+
$store = ActionScheduler::store();
282+
283+
if ( ! $store ) {
284+
return false;
285+
}
286+
287+
$action_counts = [
288+
'pending' => (int) $store->query_actions(
289+
[
290+
'hook' => $this->repair_hook,
291+
'status' => ActionScheduler_Store::STATUS_PENDING,
292+
],
293+
'count'
294+
),
295+
'complete' => (int) $store->query_actions(
296+
[
297+
'hook' => $this->repair_hook,
298+
'status' => ActionScheduler_Store::STATUS_COMPLETE,
299+
],
300+
'count'
301+
),
302+
];
303+
304+
set_transient( $this->action_progress_transient, $action_counts, 10 * MINUTE_IN_SECONDS );
305+
}
306+
307+
return $action_counts;
308+
}
309+
310+
/**
311+
* Registers the repair tool for the Legacy SEPA token migration.
312+
*
313+
* @param array $tools The existing repair tools.
314+
*
315+
* @return array The updated repair tools.
316+
*/
317+
public function add_debug_tool( $tools ) {
318+
// We don't need to show the tool if the WooCommerce Subscriptions extension isn't active or the UPE checkout isn't enabled
319+
if ( ! class_exists( 'WC_Subscriptions' ) || ! WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
320+
return $tools;
321+
}
322+
323+
// Don't show the tool if the repair is already in progress or there are no subscriptions to migrate.
324+
if ( (bool) as_next_scheduled_action( $this->scheduled_hook ) || (bool) as_next_scheduled_action( $this->repair_hook ) || ! $this->has_legacy_sepa_subscriptions() ) {
325+
return $tools;
326+
}
327+
328+
$tools['stripe_legacy_sepa_tokens'] = [
329+
'name' => __( 'Stripe Legacy SEPA Token Update', 'woocommerce-gateway-stripe' ),
330+
'desc' => __( 'This will restart the legacy Stripe SEPA update process.', 'woocommerce-gateway-stripe' ),
331+
'button' => __( 'Restart SEPA token update', 'woocommerce-gateway-stripe' ),
332+
'callback' => [ $this, 'restart_update' ],
333+
];
334+
335+
return $tools;
336+
}
337+
338+
/**
339+
* Checks if there are subscriptions using the Legacy SEPA payment method.
340+
*
341+
* @return bool True if there are subscriptions using the Legacy SEPA payment method, false otherwise.
342+
*/
343+
private function has_legacy_sepa_subscriptions() {
344+
$subscriptions = wc_get_orders(
345+
[
346+
'return' => 'ids',
347+
'type' => 'shop_subscription',
348+
'status' => 'any',
349+
'posts_per_page' => 1,
350+
'payment_method' => WC_Gateway_Stripe_Sepa::ID,
351+
]
352+
);
353+
354+
return ! empty( $subscriptions );
355+
}
356+
357+
/**
358+
* Restarts the legacy token update process.
359+
*/
360+
public function restart_update() {
361+
// Clear the option to allow the update to be scheduled again.
362+
delete_option( 'woocommerce_stripe_subscriptions_legacy_sepa_tokens_updated' );
363+
364+
$this->maybe_update();
365+
}
143366
}

readme.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,7 @@ If you get stuck, you can ask for help in the Plugin Forum.
157157
* Fix - Address Klarna availability based on correct presentment currency rules.
158158
* Fix - Use correct ISO country code of United Kingdom in supported country and currency list of AliPay and WeChat.
159159
* Fix - Prevent duplicate order notes and emails being sent when purchasing subscription products with no initial payment.
160+
* Add - Display an admin notice on the WooCommerce > Subscriptions screen for tracking the progress of SEPA subscriptions migrations after the legacy checkout is disabled.
161+
* Add - Introduce a new tool on the WooCommerce > Status > Tools screen to restart the legacy SEPA subscriptions update.
160162

161163
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt).

0 commit comments

Comments
 (0)