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

Add support for signup fee coupons during subscription switches #751

Draft
wants to merge 10 commits into
base: trunk
Choose a base branch
from
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** WooCommerce Subscriptions Core Changelog ***

= 7.9.0 - xxxx-xx-xx =
* Add - Support use of sign up fee coupons during subscription switches.
* Fix - Prevent payment gateways that complete Change Payment requests asynchronously from blocking future attempts to update payment methods for all subscriptions.

= 7.8.0 - 2024-12-16 =
Expand Down
26 changes: 21 additions & 5 deletions includes/class-wc-subscriptions-coupon.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,24 @@ public static function get_discount_amount_for_cart_item( $cart_item, $discount,
}
}

// Apply sign-up discounts. Exclude switch cart items because their initial amount is entirely sign-up fees but should be treated as initial amounts
if ( ! $is_switch && WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ) > 0 ) {
// Compute the original sign-up fee. If it's a switch, we need to get the signup fee less
// upgrade costs.
if ( $is_switch ) {
$sign_up_fee_prorated = (int) $cart_item['data']->get_meta( '_subscription_sign_up_fee_prorated' );
$price_prorated = (int) $cart_item['data']->get_meta( '_subscription_price_prorated' );

Mayisha marked this conversation as resolved.
Show resolved Hide resolved
if ( 0 === $sign_up_fee_prorated && 0 === $price_prorated ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Quick question for my understanding - why the price_prorated is also being checked here? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! In 3ee2abc, I simplified the logic using meta_exists. Hopefully it's more readable.

// No prorated recurring fees, i.e. no extra upgrade costs, so we can use the original sign-up fee.
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] );
} else {
$sign_up_fee = $sign_up_fee_prorated;
}
} else {
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] );
}

// Apply sign-up discounts
if ( $sign_up_fee > 0 ) {

if ( 'sign_up_fee' == $coupon_type ) {
$apply_initial_coupon = true;
Expand All @@ -236,15 +252,15 @@ public static function get_discount_amount_for_cart_item( $cart_item, $discount,
$cart_item['data'],
array(
'qty' => 1,
'price' => WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ),
'price' => $sign_up_fee,
)
);
} else {
$signup_fee = wc_get_price_excluding_tax(
$cart_item['data'],
array(
'qty' => 1,
'price' => WC_Subscriptions_Product::get_sign_up_fee( $cart_item['data'] ),
'price' => $sign_up_fee,
)
);
}
Expand All @@ -253,7 +269,7 @@ public static function get_discount_amount_for_cart_item( $cart_item, $discount,
if ( in_array( $coupon_type, array( 'sign_up_fee', 'sign_up_fee_percent' ) ) ) {
$discounting_amount = $signup_fee;
} else {
$discounting_amount -= $signup_fee;
$discounting_amount = max( 0, $discounting_amount - $signup_fee );
}
}

Expand Down
316 changes: 316 additions & 0 deletions tests/unit/test-class-wc-subscriptions-coupon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
<?php
/**
* Class: WC_Subscription_Payment_Count_Test
*/
class WC_Subscriptions_Coupon_Test extends WP_UnitTestCase {

private $cart;

private $simple_subscription_product;
private $variable_subscription_product;

public function set_up() {
parent::set_up();

$this->cart = WC()->cart;

// Create a simple subscription product.
$simple = WCS_Helper_Product::create_simple_subscription_product(
[
'price' => 25,
'subscription_period' => 'month',
]
);
$simple->update_meta_data( '_subscription_sign_up_fee', 10 );
$simple->update_meta_data( '_subscription_trial_length', 0 );
$this->simple_subscription_product = $simple;

// Create a variable subscription product.
$variable = WCS_Helper_Product::create_variable_subscription_product(
[
'subscription_period' => 'month',
]
);
$variable->update_meta_data( '_subscription_sign_up_fee', 10 );
$variable->update_meta_data( '_subscription_trial_length', 0 );
$this->variable_subscription_product = $variable;
}

public function tear_down() {
$this->cart->empty_cart();

parent::tear_down();
}

/**
* Tests for the WC_Subscriptions_Coupon::get_discount_amount_for_cart_item method,
* specifically for sign-up fee and sign-up fee percent coupons.
*/
public function test_get_discount_amount_for_cart_item_sign_up_fee_coupons() {
$coupon_sign_up_fee_percent = new WC_Coupon();
$coupon_sign_up_fee_percent->set_amount( 10 );
$coupon_sign_up_fee_percent->set_discount_type( 'sign_up_fee_percent' );

$coupon_sign_up_fee = new WC_Coupon();
$coupon_sign_up_fee->set_amount( 2 );
$coupon_sign_up_fee->set_discount_type( 'sign_up_fee' );

$coupon_sign_up_fee_large = new WC_Coupon();
$coupon_sign_up_fee_large->set_amount( 100 );
$coupon_sign_up_fee_large->set_discount_type( 'sign_up_fee' );

$discount = 0;
$discounting_amount = 30;
$single = true;

// Not a subscription switch
$cart_item = array(
'data' => $this->simple_subscription_product,
'quantity' => 1,
'subscription_switch' => false,
);
$this->cart->empty_cart();
$this->cart->add_to_cart( $cart_item['data']->get_id() );

$this->assertEquals(
1,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_percent
)
);

$this->assertEquals(
2,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee
)
);

// Subscription switch, with extra upgrade costs. Discount should be
// applied to the sign-up fee before other costs.
$cart_item = array(
'data' => $this->variable_subscription_product,
'quantity' => 1,
'subscription_switch' => [
'subscription_id' => 123,
],
);
$this->cart->empty_cart();
$this->cart->add_to_cart( $cart_item['data']->get_id() );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee', 30 );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee_prorated', 10 );
$cart_item['data']->update_meta_data( '_subscription_price_prorated', 20 );
$this->assertEquals(
1,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_percent
)
);

$this->assertEquals(
2,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee
)
);

$this->assertEquals(
10, // Discount should never be more than the sign-up fee
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_large
)
);

// Subscription switch -- no sign up fee, no discount
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee', 20 );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee_prorated', 0 );
$cart_item['data']->update_meta_data( '_subscription_price_prorated', 20 );
$this->assertEquals(
0,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_percent
)
);

$this->assertEquals(
0,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee
)
);

$this->assertEquals(
0,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_large
)
);

// Subscription switch -- no prorated fees, e.g. downgrade
$discounting_amount = 10;
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee', 10 );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee_prorated', 0 );
$cart_item['data']->update_meta_data( '_subscription_price_prorated', 0 );
$this->assertEquals(
1,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_sign_up_fee_percent
)
);
}


/**
* Tests for the WC_Subscriptions_Coupon::get_discount_amount_for_cart_item method,
* specifically for recurring fee and recurring fee percent coupons.
*/
public function test_get_discount_amount_for_cart_item_recurring_fee_coupons() {
$coupon_recurring_percent = new WC_Coupon();
$coupon_recurring_percent->set_amount( 10 );
$coupon_recurring_percent->set_discount_type( 'recurring_percent' );

$coupon_recurring_fee = new WC_Coupon();
$coupon_recurring_fee->set_amount( 5 );
$coupon_recurring_fee->set_discount_type( 'recurring_fee' );

$coupon_recurring_fee_large = new WC_Coupon();
$coupon_recurring_fee_large->set_amount( 100 );
$coupon_recurring_fee_large->set_discount_type( 'recurring_fee' );

$discount = 0;
$discounting_amount = 30;
$single = true;

// Not a subscription switch
$cart_item = array(
'data' => $this->simple_subscription_product,
'quantity' => 1,
'subscription_switch' => false,
);
$this->cart->empty_cart();
$this->cart->add_to_cart( $cart_item['data']->get_id() );

// 10% off recurring fee (20.00)
$this->assertEquals(
2,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_percent
)
);

// 5.00 off recurring fee (20.00)
$this->assertEquals(
5,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_fee
)
);

// 100.00 off recurring fee (20.00)
$this->assertEquals(
20, // Discount should never be more than the recurring fee
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_fee_large
)
);

// Subscription switch
$cart_item = array(
'data' => $this->variable_subscription_product,
'quantity' => 1,
'subscription_switch' => [
'subscription_id' => 123,
],
);
$this->cart->empty_cart();
$this->cart->add_to_cart( $cart_item['data']->get_id() );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee', 30 );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee_prorated', 10 );
$cart_item['data']->update_meta_data( '_subscription_price_prorated', 20 );
$this->assertEquals(
2,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_percent
)
);

$this->assertEquals(
5,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_fee
)
);

// Subscription switch -- no prorated fees, e.g. downgrade
$discounting_amount = 10;
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee', 10 );
$cart_item['data']->update_meta_data( '_subscription_sign_up_fee_prorated', 0 );
$cart_item['data']->update_meta_data( '_subscription_price_prorated', 0 );
$this->assertEquals(
0,
WC_Subscriptions_Coupon::get_discount_amount_for_cart_item(
$cart_item,
$discount,
$discounting_amount,
$single,
$coupon_recurring_percent
)
);
}
}
Loading