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: add cli command to fix stripe covered fees #2848

Merged
merged 4 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
225 changes: 225 additions & 0 deletions includes/reader-revenue/woocommerce/class-woocommerce-cli.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<?php
/**
* CLI tools for the woocommerce support
*
* @package Newspack
*/

namespace Newspack;

use WP_CLI;

defined( 'ABSPATH' ) || exit;

/**
* WooCommerce Order UTM class.
*/
class WooCommerce_Cli {

/**
* Lists the subscriptions that needs to be fixed by the fix_subscriptions_missing_fee command.
*
* ## OPTIONS
*
* [--format=<format>]
* : Accepted values: table, csv, json, count, yaml. Default: table
*
* @param array $args Args.
* @param array $assoc_args Assoc args.
*/
public function list_subscriptions_missing_fee( $args, $assoc_args ) {

$format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';

$subscriptions = $this->get_all_old_subscriptions();

$subscriptions = array_filter(
$subscriptions,
function( $subscription ) {
return empty( $subscription->get_fees() );
}
);

if ( empty( $subscriptions ) ) {
WP_CLI::success( 'No subscriptions missing fees found.' );
return;
}

WP_CLI::success( 'Subscriptions missing fees:' );
$this->output_subscriptions( $subscriptions, $format );

}

/**
* Lists the subscriptions that were already fixed by the fix_subscriptions_missing_fee command.
*
* ## OPTIONS
*
* [--format=<format>]
* : Accepted values: table, csv, json, count, yaml. Default: table
*
* @param array $args Args.
* @param array $assoc_args Assoc args.
*/
public function list_subscriptions_missing_fee_fixed( $args, $assoc_args ) {

$format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';

$subscriptions = $this->get_all_old_subscriptions();

$subscriptions = array_filter(
$subscriptions,
function( $subscription ) {
return ! empty( $subscription->get_fees() );
}
);

if ( empty( $subscriptions ) ) {
WP_CLI::success( 'No subscriptions missing fees found.' );
return;
}

WP_CLI::success( 'Fixed subscriptions that had missing fees:' );
$this->output_subscriptions( $subscriptions, $format );

}

/**
* Updates the subscriptions that had the Stripe cover fee option added in the old way.
*
* Will look for all subcriptions that had fees added in the old way and fix them.
*
* ## OPTIONS
*
* [--dry-run]
* : If set, no changes will be made.
*
* @param [type] $args Args.
* @param [type] $assoc_args Assoc args.
*/
public function fix_subscriptions_missing_fee( $args, $assoc_args ) {

$dry_run = isset( $assoc_args['dry-run'] ) && $assoc_args['dry-run'];

if ( ! $dry_run ) {
WP_CLI::line( 'This command will modify the database.' );
WP_CLI::line( 'Consider running it with --dry-run first to see what it will do.' );
WP_CLI::confirm( 'Are you sure you want to continue?', $assoc_args );
}

$subscriptions = $this->get_all_old_subscriptions();

$subscriptions = array_filter(
$subscriptions,
function( $subscription ) {
return empty( $subscription->get_fees() );
}
);

if ( empty( $subscriptions ) ) {
WP_CLI::success( 'No subscriptions missing fees found.' );
return;
}

foreach ( $subscriptions as $subscription ) {

WP_CLI::success( 'Fixing subscription #' . $subscription->get_id() );
WP_CLI::log( 'Subscription total is ' . $subscription->get_total() );

$fee_value = WooCommerce_Cover_Fees::get_fee_value( $subscription->get_total() );
WP_CLI::log( 'Fee value will be: ' . $fee_value );

$fee_display_value = WooCommerce_Cover_Fees::get_fee_display_value( $subscription->get_total() );
WP_CLI::log( 'Fee display value will be: ' . $fee_display_value );

$new_total = WooCommerce_Cover_Fees::get_total_with_fee( $subscription->get_total() );
WP_CLI::log( 'Subscription new total will be: ' . $new_total );

if ( $dry_run ) {
WP_CLI::warning( 'Dry run, not saving.' );
continue;
}

$fee_name = sprintf(
// Translators: %s is the fee percentage.
__( 'Transaction fee (%s)', 'newspack-plugin' ),
$fee_display_value
);

$fee = (object) [
'name' => $fee_name,
'amount' => $fee_value,
'tax' => '',
'taxable' => false,
'tax_data' => '',
];

$subscription->add_fee( $fee );
$subscription->legacy_set_total( $new_total );
$subscription->add_order_note( 'Subscription fee fixed and added via script' );
// No need to save, we don't want to trigger any hooks.

WP_CLI::success( 'Subscription #' . $subscription->get_id() . ' fixed.' );
WP_CLI::log( '' );
}

}

/**
* Outputs a list of subscription in CLI
*
* @param WC_Subscription[] $subscriptions The subscriptions.
* @param string $format The output format.
* @return void
*/
private function output_subscriptions( $subscriptions, $format = 'table' ) {
$subscriptions = array_map(
function( $subscription ) {
return [
'id' => $subscription->get_id(),
'date_created' => $subscription->get_date_created()->__toString(),
'amount' => $subscription->get_total(),
];
},
$subscriptions
);

WP_CLI\Utils\format_items( $format, $subscriptions, [ 'id', 'amount', 'date_created' ] );
WP_CLI::log( count( $subscriptions ) . ' subscriptions found.' );
}

/**
* Get all subscriptions that had the Stripe cover fee option added in the old way.
*
* We look at the order notes, and not the subscription meta, because there was a bug where the meta was not stored sometimes.
*
* @return ?WP_Subscription[] The subscriptions.
*/
private function get_all_old_subscriptions() {
global $wpdb;

// phpcs:ignore
$parent_order_ids = $wpdb->get_col(
"SELECT comment_post_ID FROM {$wpdb->comments} WHERE comment_content LIKE '%transaction fee. The total amount will be updated.'"
);

$subscriptions = [];
$ids = [];

foreach ( $parent_order_ids as $parent_order_id ) {
$subs = wcs_get_subscriptions_for_order( $parent_order_id );
if ( is_array( $subs ) && ! empty( $subs ) ) {
$sub = array_shift( $subs );
if ( ! in_array( $sub->get_id(), $ids, true ) ) {
$subscriptions[] = $sub;
$ids[] = $sub->get_id();
}
}
}

return $subscriptions;

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class WooCommerce_Connection {
public static function init() {
include_once __DIR__ . '/class-woocommerce-order-utm.php';
include_once __DIR__ . '/class-woocommerce-cover-fees.php';
include_once __DIR__ . '/class-woocommerce-cli.php';

\add_action( 'admin_init', [ __CLASS__, 'disable_woocommerce_setup' ] );
\add_filter( 'option_woocommerce_subscriptions_allow_switching', [ __CLASS__, 'force_allow_subscription_switching' ], 10, 2 );
Expand All @@ -50,6 +51,7 @@ public static function init() {
\add_filter( 'default_option_woocommerce_subscriptions_allow_switching_nyp_price', [ __CLASS__, 'force_allow_subscription_switching' ], 10, 2 );
\add_filter( 'default_option_woocommerce_subscriptions_enable_retry', [ __CLASS__, 'force_allow_failed_payment_retry' ] );
\add_filter( 'woocommerce_email_enabled_customer_completed_order', [ __CLASS__, 'send_customizable_receipt_email' ], 10, 3 );
\add_action( 'cli_init', [ __CLASS__, 'register_cli_commands' ] );

// WooCommerce Subscriptions.
\add_action( 'add_meta_boxes', [ __CLASS__, 'remove_subscriptions_schedule_meta_box' ], 45 );
Expand All @@ -72,6 +74,15 @@ public static function init() {
\add_action( 'wp_login', [ __CLASS__, 'sync_reader_on_customer_login' ], 10, 2 );
}

/**
* Register CLI command
*
* @return void
*/
public static function register_cli_commands() {
\WP_CLI::add_command( 'newspack-woocommerce', 'Newspack\\WooCommerce_Cli' );
}

/**
* Check whether everything is set up to enable customer syncing to ESP.
*
Expand Down Expand Up @@ -1172,9 +1183,9 @@ public static function force_allow_subscription_switching( $can_switch, $option_
/**
* Force option for allowing retries for failed payments to ON unless the
* NEWSPACK_PREVENT_WC_ALLOW_FAILED_PAYMENT_RETRIES_OVERRIDE constant is set.
*
*
* See: https://woo.com/document/subscriptions/failed-payment-retry/
*
*
* @param bool $should_retry Whether WooCommerce should automatically retry failed payments.
*
* @return string Option value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public static function add_transaction_fee( $cart ) {
sprintf(
// Translators: %s is the fee percentage.
__( 'Transaction fee (%s)', 'newspack-plugin' ),
self::get_fee_display_value()
self::get_cart_fee_display_value()
),
self::get_fee_value()
self::get_cart_fee_value()
);
}

Expand Down Expand Up @@ -175,7 +175,7 @@ public static function render_stripe_input() {
'I’d like to cover the %1$s transaction fee to ensure my full donation goes towards %2$s mission.',
'newspack-plugin'
),
esc_html( self::get_fee_display_value() ),
esc_html( self::get_cart_fee_display_value() ),
esc_html( self::get_possessive( get_option( 'blogname' ) ) )
);
}
Expand Down Expand Up @@ -236,33 +236,63 @@ private static function get_stripe_fee_static_value() {

/**
* Get the fee display value.
*
* @param float $subtotal The subtotal to calculate the fee for.
*/
public static function get_fee_display_value() {
$subtotal = WC()->cart->get_subtotal();
$total = self::get_total_with_fee();
public static function get_fee_display_value( $subtotal ) {
$total = self::get_total_with_fee( $subtotal );
// Just one decimal place, please.
$flat_percentage = (float) number_format( ( ( $total - $subtotal ) * 100 ) / $subtotal, 1 );
return $flat_percentage . '%';
}

/**
* Get the fee value.
*
* @param float $subtotal The subtotal to calculate the fee for.
*/
public static function get_fee_value() {
public static function get_fee_value( $subtotal ) {
$fee_multiplier = self::get_stripe_fee_multiplier_value();
$fee_static = self::get_stripe_fee_static_value();
$subtotal = WC()->cart->get_subtotal();
$fee = ( ( ( $subtotal + $fee_static ) / ( 100 - $fee_multiplier ) ) * 100 - $subtotal );
return $fee;
}

/**
* Calculate the adjusted total, taking the fee into account.
*
* @param float $subtotal The subtotal to calculate the total for.
* @return float
*/
public static function get_total_with_fee( $subtotal ) {
return $subtotal + self::get_fee_value( $subtotal );
}

/**
* Get the fee value for the current cart.
*
* @return float
*/
public static function get_cart_fee_value() {
return self::get_fee_value( WC()->cart->get_subtotal() );
}

/**
* Get the fee display value for the current cart.
*
* @return string
*/
public static function get_cart_fee_display_value() {
return self::get_fee_display_value( WC()->cart->get_subtotal() );
}

/**
* Get the total with fee for the current cart.
*
* @return float
*/
private static function get_total_with_fee() {
return WC()->cart->get_subtotal() + self::get_fee_value();
public static function get_cart_total_with_fee() {
return self::get_total_with_fee( WC()->cart->get_subtotal() );
}
}
WooCommerce_Cover_Fees::init();