Cancel a pending subscription renewal payment retry

Use this guide when you need to stop one pending automatic retry for a failed subscription renewal. The command checks the latest renewal order and its related subscriptions before cancelling the retry. It leaves the renewal order unpaid and does not change the subscription status or set a new payment date.

Note:

This is a Developer level doc. If you are unfamiliar working with code and resolving potential conflicts, we recommend you work with a Woo Agency Partner for larger projects, or find a WooCommerce developer on Codeable for smaller customizations. We are unable to provide support for customizations under our Support Policy.

Before you begin

↑ Back to top
  1. Find the failed renewal order in WooCommerce > Orders. Check the Automatic Failed Payment Retries box. The latest retry must be Pending.
  2. Check each subscription linked to that order. The order must be its latest renewal. Review the order notes and payment gateway for a payment that may already be processing.
  3. Back up the site and test this procedure on a staging copy. On the live site, use a controlled maintenance window with background retry processing paused. Do not proceed if a payment is in progress or its gateway result is uncertain.

This procedure cancels the current pending attempt. It does not disable future automatic retries, collect payment, or reactivate a subscription.

Inspect and cancel the retry

↑ Back to top

Save the following PHP as cancel-retry.php where you run WP-CLI. It runs in preview mode unless you explicitly add apply. Replace 123 in the commands with the renewal order ID.

<?php
/**
 * Inspect or cancel the latest pending retry for one renewal order.
 * Run with WP-CLI: wp eval-file cancel-retry.php ORDER_ID [apply]
 */

if ( empty( $args[0] ) || ! ctype_digit( (string) $args[0] ) ) {
	throw new RuntimeException( 'Pass a numeric renewal order ID.' );
}

$order_id = (int) $args[0];
$apply   = isset( $args[1] ) && 'apply' === $args[1];
$order   = wc_get_order( $order_id );

if ( ! $order || ! wcs_order_contains_renewal( $order ) ) {
	throw new RuntimeException( 'The ID must belong to a renewal order.' );
}

if ( ! WCS_Retry_Manager::is_retry_enabled() || false === has_action( 'woocommerce_subscriptions_retry_status_updated', 'WCS_Retry_Manager::maybe_delete_payment_retry_date' ) ) {
	throw new RuntimeException( 'The retry cleanup hooks are not active.' );
}

$subscriptions = wcs_get_subscriptions_for_renewal_order( $order );

if ( empty( $subscriptions ) ) {
	throw new RuntimeException( 'No subscriptions are linked to this renewal order.' );
}

foreach ( $subscriptions as $subscription ) {
	if ( $order_id !== (int) $subscription->get_last_order( 'ids', 'renewal' ) ) {
		throw new RuntimeException( 'This is not the latest renewal for subscription ' . $subscription->get_id() . '.' );
	}
}

$retry = WCS_Retry_Manager::store()->get_last_retry_for_order( $order_id );

if ( ! $retry || 'pending' !== $retry->get_status() ) {
	throw new RuntimeException( 'The latest retry is not pending; no change was made.' );
}

$hook         = 'woocommerce_scheduled_subscription_payment_retry';
$action_args  = array( 'order_id' => $order_id );
$running_ids  = as_get_scheduled_actions(
	array(
		'hook'     => $hook,
		'args'     => $action_args,
		'status'   => ActionScheduler_Store::STATUS_RUNNING,
		'per_page' => 1,
	),
	'ids'
);

if ( ! empty( $running_ids ) ) {
	throw new RuntimeException( 'A retry action is already running; no change was made.' );
}

echo 'Renewal order ' . $order_id . ': latest retry ' . $retry->get_id() . ' is pending; related subscriptions: ' . implode( ', ', array_keys( $subscriptions ) ) . ".\n";

if ( ! $apply ) {
	echo "Preview only. Review the order, gateway, and execution state before rerunning with apply.\n";
	return;
}

$retry->update_status( 'cancelled' );

if ( 'cancelled' !== WCS_Retry_Manager::store()->get_retry( $retry->get_id() )->get_status() || as_has_scheduled_action( $hook, $action_args ) ) {
	throw new RuntimeException( 'The retry did not reach a consistent cancelled state. Investigate before taking another action.' );
}

foreach ( $subscriptions as $subscription ) {
	if ( 0 !== wcs_get_subscription( $subscription->get_id() )->get_time( 'payment_retry' ) ) {
		throw new RuntimeException( 'The retry date remains on subscription ' . $subscription->get_id() . '. Investigate before taking another action.' );
	}
}

echo "Retry cancelled and the linked subscription retry dates and queued action cleared.\n";

First inspect the target:

wp eval-file cancel-retry.php 123

Check the printed order and subscription IDs against the site. When the payment and scheduled action are safe to stop, run:

wp eval-file cancel-retry.php 123 apply

Check the result

↑ Back to top
  1. Confirm the command reports that the retry was cancelled and its dates and action cleared.
  2. Reload the renewal order and check that its latest retry is Cancelled. Check WooCommerce > Status > Scheduled Actions for any pending or running payment retry action for that order.
  3. Reload each linked subscription. Its Renewal Payment Retry date should be gone, and the Next Payment field should be available again. Review the actual next payment date before making any billing decision.

If the command refuses the order, do not force a database edit. An older renewal, a running retry, or inactive cleanup hooks needs separate investigation. A leftover date without a pending retry is also a different case. See the developer guide to the retry system for its components and hooks.

Related Products

Power your membership association, online magazine, elearning sites, and more with access control to...

Offer add-ons like gift wrapping, special messages or other special options for your products.

Use of your personal data
We and our partners process your personal data (such as browsing data, IP Addresses, cookie information, and other unique identifiers) based on your consent and/or our legitimate interest to optimize our website, marketing activities, and your user experience.