Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wcs): add expired subscription cli tool #3593

Merged
merged 6 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions includes/cli/class-initializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static function init() {
include_once NEWSPACK_ABSPATH . 'includes/cli/class-ras.php';
include_once NEWSPACK_ABSPATH . 'includes/cli/class-ras-esp-sync.php';
include_once NEWSPACK_ABSPATH . 'includes/cli/class-co-authors-plus.php';
include_once NEWSPACK_ABSPATH . 'includes/cli/class-woocommerce-subscriptions.php';
}

/**
Expand Down Expand Up @@ -66,5 +67,6 @@ public static function register_comands() {
'schedule_author_term_backfill',
]
);
WP_CLI::add_command( 'newspack migrate-expired-subscriptions', [ 'Newspack\CLI\WooCommerce_Subscriptions', 'migrate_expired_subscriptions' ] );
}
}
184 changes: 184 additions & 0 deletions includes/cli/class-woocommerce-subscriptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
/**
* WooCommerce Subscriptions Integration CLI commands.
*
* @package Newspack
*/

namespace Newspack\CLI;

use WP_CLI;
use Newspack\Woocommerce_Subscriptions as WooCommerce_Subscriptions_Integration;

defined( 'ABSPATH' ) || exit;

/**
* WooCommerce Subscriptions Integration CLI commands.
*/
class WooCommerce_Subscriptions {
/**
* Flag for live mode.
*
* @var bool
*/
private static $live = false;

/**
* Flag for verbose output.
*
* @var bool
*/
private static $verbose = false;

/**
* Subscription ids to process.
*
* @var bool|array
*/
private static $ids = false;

/**
* Migrate status of on-hold WooCommerce subscriptions that have failed all payment retries to expired.
*
* ## OPTIONS
*
* [--live]
* : Run the command in live mode, updating the subscriptions.
*
* [--verbose]
* : Produce more output.
*
* [--ids]
* : Comma-separated list of subscription IDs. If provided, only ubscriptions with these IDs will be processed.
*
* @param array $args Positional arguments.
* @param array $assoc_args Assoc arguments.
*
* @return void
*/
public function migrate_expired_subscriptions( $args, $assoc_args ) {
WP_CLI::line( '' );
if ( ! WooCommerce_Subscriptions_Integration::is_enabled() ) {
WP_CLI::error( 'WooCommerce Subscriptions Integration is not enabled.' );
WP_CLI::line( '' );
return;
}
self::$ids = isset( $assoc_args['ids'] ) ? explode( ',', $assoc_args['ids'] ) : false;
self::$live = isset( $assoc_args['live'] ) ? true : false;
self::$verbose = isset( $assoc_args['verbose'] ) ? true : false;
$updated = 0;
$page = 1;
$per_page = 25;
$subscriptions = self::get_subscriptions( $page );
if ( empty( $subscriptions ) ) {
WP_CLI::success( 'No on-hold subscriptions to process.' );
WP_CLI::line( '' );
return;
}
WP_CLI::line( 'Processing subscriptions in ' . ( self::$live ? 'live' : 'dry run' ) . ' mode...' );
WP_CLI::line( '' );
while ( ! empty( $subscriptions ) ) {
foreach ( $subscriptions as $subscription ) {
$id = $subscription->get_id();
if ( self::$verbose ) {
WP_CLI::line( 'Processing subscription ' . $id . '...' );
}
// A pending retry indicates the subscription is awaiting payment retry.
if ( $subscription->get_date( 'payment_retry' ) > 0 ) {
if ( self::$verbose ) {
WP_CLI::line( 'Subscription is awaiting payment retry. Moving to next subscription...' );
WP_CLI::line( '' );
}
continue;
}
$renewal_order = $subscription->get_last_order(
'all',
[ 'renewal' ],
[
'completed',
'processing',
'refunded',
]
);
// No failed or pending renewal orders indicates the subscription was likely manually placed on hold.
if ( empty( $renewal_order ) ) {
if ( self::$verbose ) {
WP_CLI::line( 'Subscription has no pending renewal orders. Moving to next subscription...' );
WP_CLI::line( '' );
}
continue;
}
$last_retry = \WCS_Retry_Manager::store()->get_last_retry_for_order( wcs_get_objects_property( $renewal_order, 'id' ) );
// No retries indicates the subscription was likely manually placed on hold.
if ( empty( $last_retry ) ) {
if ( self::$verbose ) {
WP_CLI::line( 'No retries scheduled. Moving to next subscription...' );
WP_CLI::line( '' );
}
continue;
}
// A non failed status indicates the retry was either manually cancelled
// or was successful at one point but likely placed on hold for some other reason.
if ( 'failed' !== $last_retry->get_status() ) {
if ( self::$verbose ) {
WP_CLI::line( 'Last retry does not have a failed status. Moving to next subscription...' );
WP_CLI::line( '' );
}
continue;
} else {
if ( self::$verbose ) {
WP_CLI::line( 'Updating subscription status to expired...' );
}
if ( self::$live ) {
$subscription->update_status( 'expired', __( 'Subscription status updated by Newspack CLI command.', 'newspack-plugin' ) );
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's also add one of those Subscriptions Notes that show up in the subscription page?

Copy link
Contributor

Choose a reason for hiding this comment

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

In discussion with Tracy and Katie, it would be best if the subscription had the expiration date of the last failed payment rather than the expiration date of when the script was run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's also add one of those Subscriptions Notes that show up in the subscription page?

The second argument to $subscription->update_status is added as a subscription note. Is this what you were referring to?

Screenshot 2024-12-06 at 11 44 11

In discussion with Tracy and Katie, it would be best if the subscription had the expiration date of the last failed payment rather than the expiration date of when the script was run.

Sure thing @claudiulodro! I updated to script to use the date of the last retry object. This should be the same as the last failed payment: b29bdd1

When testing though, since we are manually triggering the scheduled actions, this end date will be set as though the scheduled action was not triggered manually, using the expected end date for the retry. I think this should be fine since our publishers shouldn't be doing this, however let me know if we need to be more specific here.

$subscription->set_end_date( $last_retry->get_date() );
$subscription->save();
}
++$updated;
}
if ( self::$verbose ) {
WP_CLI::line( 'Finished processing subscription ' . $id );
WP_CLI::line( '' );
}
}
$subscriptions = self::get_subscriptions( ++$page );
}
WP_CLI::success( 'Finished processing subscriptions. ' . $updated . ' subscriptions updated.' );
if ( ! self::$live ) {
WP_CLI::warning( 'Dry run. Use --live flag to process live subscriptions.' );
}
WP_CLI::line( '' );
}

/**
* Get subscriptions to process.
*
* @param int $page Page number.
*
* @return array
*/
private static function get_subscriptions( $page = 1 ) {
$subscriptions = [];
if ( false !== self::$ids ) {
while ( ! empty( self::$ids ) ) {
$id = array_shift( self::$ids );
if ( ! is_numeric( $id ) ) {
continue;
}
$subscription = wcs_get_subscription( $id );
if ( $subscription ) {
$subscriptions[] = $subscription;
}
}
} else {
$subscriptions = wcs_get_subscriptions(
[
'paged' => $page,
'subscriptions_per_page' => 25,
'subscription_status' => 'on-hold',
]
);
}
return $subscriptions;
}
}
Loading