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

Stripe-to-Stripe Migrator #2032

Merged
merged 3 commits into from
Sep 26, 2022
Merged
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
162 changes: 162 additions & 0 deletions includes/reader-revenue/class-stripe-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,142 @@ private static function process_all_stripe_customers( $args, $last_id = false )
}
}

/**
* CLI command for migrating Stripe Subscriptions from Stripe Connect to a regular Stripe account.
*
* @param array $args Positional args.
* @param array $assoc_args Associative args.
*/
public static function sync_stripe_connect_to_stripe( $args, $assoc_args ) {
$is_dry_run = ! empty( $assoc_args['dry-run'] );
$force_override = ! empty( $assoc_args['force'] );
$batch_size = ! empty( $assoc_args['batch-size'] ) ? intval( $assoc_args['batch-size'] ) : 10;

$customers = self::get_batch_of_customers_for_stripe_connect_to_stripe( $batch_size );
while ( $customers ) {
$customer = array_shift( $customers );

self::process_customer_for_stripe_connect_to_stripe(
$customer,
[
'dry_run' => $is_dry_run,
'force_subscription_override' => $force_override,
]
);

// Get the next batch.
if ( empty( $customers ) ) {
$customers = self::get_batch_of_customers_for_stripe_connect_to_stripe( $batch_size, $customer->id );
}
}

\WP_CLI::success( 'Finished processing.' );
}

/**
* Get a batch of customers for the Stripe-Connect-to-Stripe CLI tool.
*
* @param int $limit Number of customers to fetch.
* @param string $last_customer_id Stripe ID of customer to get results after, essentially the offset.
* @return array Array of Stripe customers.
*/
protected static function get_batch_of_customers_for_stripe_connect_to_stripe( $limit, $last_customer_id = false ) {
$stripe = Stripe_Connection::get_stripe_client();
try {
$params = [
'limit' => $limit,
'expand' => [
'data.subscriptions',
],
];

if ( $last_customer_id ) {
$params['starting_after'] = $last_customer_id;
}

return $stripe->customers->all( $params )['data'];
} catch ( \Throwable $e ) {
\WP_CLI::error( sprintf( 'Could not process all customers: %s', $e->getMessage() ) );
}
}

/**
* Process one customer's Stripe subscriptions and migrate them to Newspack Stripe subscriptions.
*
* @param Stripe_Customer $customer Stripe customer object.
* @param array $args Params to control migration behavior.
*/
protected static function process_customer_for_stripe_connect_to_stripe( $customer, $args ) {
$dry_run = ! empty( $args['dry_run'] );
$force_subscription_override = ! empty( $args['force_subscription_override'] );

$stripe = Stripe_Connection::get_stripe_client();
$stripe_prices = Stripe_Connection::get_donation_prices();

\WP_CLI::log( sprintf( 'Processing customer: %s', $customer->email ) );
if ( empty( $customer->subscriptions ) || empty( $customer->subscriptions->data ) ) {
\WP_CLI::log( ' - No subscriptions found for customer. Skipping. A future version of this tool will handle the case where we want to turn one-time Stripe payments into Stripe subscriptions.' );
return;
}

foreach ( $customer->subscriptions['data'] as $existing_subscription ) {
\WP_CLI::log( sprintf( ' - Processing subscription: %s', $existing_subscription->id ) );

// Skip subscription if it's already migrated.
if ( ! empty( $existing_subscription->metadata['subscription_migrated_to_newspack'] && ! $force_subscription_override ) ) {
\WP_CLI::log( sprintf( ' - Subscription already migrated to Newspack on %s. Skipping.', $existing_subscription->metadata['subscription_migrated_to_newspack'] ) );
continue;
}

$new_subscription_items = [];

foreach ( $existing_subscription->items->data as $existing_subscription_item ) {
// Quantity is used here as a fallback because Simplified Donate Block uses quantity * 1 cent to do a variable price subscription.
$existing_subscription_item_price = ! empty( $existing_subscription_item->price->unit_amount ) ? $existing_subscription_item->price->unit_amount : $existing_subscription_item->quantity;

$frequency = $existing_subscription_item->price->recurring->interval;
$new_subscription_items[] = [
'price' => $stripe_prices[ $frequency ]['id'],
'quantity' => $existing_subscription_item_price,
];

\WP_CLI::log( sprintf( ' * Found subscription item: $%s/%s', $existing_subscription_item_price / 100, $frequency ) );
}

if ( $dry_run ) {
\WP_CLI::log( sprintf( ' * Would have created subscription with next renewal at %s', gmdate( 'Y-m-d', $existing_subscription->current_period_end ) ) );
return;

}

// Create new subscriptions.
try {
$subscription = $stripe->subscriptions->create(
[
'customer' => $customer->id,
'items' => $new_subscription_items,
'payment_behavior' => 'allow_incomplete',
'billing_cycle_anchor' => $existing_subscription->current_period_end,
'trial_end' => $existing_subscription->current_period_end,
'metadata' => [
'subscription_migrated_to_newspack' => gmdate( 'c' ),
],
]
);
\WP_CLI::log( sprintf( ' * Created subscription: %s with next renewal at %s', $subscription->id, gmdate( 'Y-m-d', $existing_subscription->current_period_end ) ) );
} catch ( \Throwable $e ) {
\WP_CLI::error( sprintf( 'Failed to create subscription: %s', $e->getMessage() ) );
}

try {
$stripe->subscriptions->cancel( $existing_subscription->id );
\WP_CLI::log( sprintf( ' * Cancelled old subscription: %s', $existing_subscription->id ) );
} catch ( \Throwable $e ) {
\WP_CLI::error( sprintf( 'Failed to cancel subscription: %s', $e->getMessage() ) );
}
}
}

/**
* Add CLI commands.
*/
Expand Down Expand Up @@ -210,6 +346,32 @@ public static function wp_cli() {
'shortdesc' => __( 'Backfill WC Customers from Stripe database.', 'newspack' ),
]
);

\WP_CLI::add_command(
'newspack stripe sync-stripe-connect-to-stripe',
[ __CLASS__, 'sync_stripe_connect_to_stripe' ],
[
'shortdesc' => __( 'Migrate customers from Stripe Connect to Stripe', 'newspack' ),
'synopsis' => [
[
'type' => 'flag',
'name' => 'dry-run',
'optional' => true,
],
[
'type' => 'flag',
'name' => 'force',
'optional' => true,
],
[
'type' => 'flag',
'name' => 'batch-size',
'default' => 10,
'optional' => true,
],
],
]
);
}
}

Expand Down