Skip to content

Commit

Permalink
Updates to 6.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
WooCommerce committed Aug 22, 2024
1 parent b2a4dd8 commit a8df935
Showing 25 changed files with 397 additions and 220 deletions.
10 changes: 10 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
*** WooCommerce Subscriptions Changelog ***

2024-08-22 - version 6.6.0
* Fix: Resolve duplicate subscription creation and PHP warning when switching subscriptions with Prepaid for WooCommerce Subscriptions plugin active.
* Fix: Switch calculations now exclude previous switch orders with a checkout-draft status.
* Dev: Introduce new parameter to WC_Subscription::get_last_order() to enable filtering out orders with specific statuses.
* Update: Schedule subscription-related events with a priority of 1 to allow for earlier execution within the Action Scheduler.
* Fix: Ensure admin notices are displayed after performing bulk actions on subscriptions when HPOS is enabled.
* Fix: Add a year to the next renewal date billing interval is 12 months or more for a synced subscription.
* Dev: Added filter to enable overriding the total paid for current switch period.
* Dev: Updated subscriptions-core to 7.4.1.

2024-07-16 - version 6.5.0
* Add: Include trial_period, suspension_count, and requires_manual_renewal in the REST API response for subscriptions.
* Update: When a renewal order's payment is skipped, include the order's current status in the note to help troubleshooting.
2 changes: 1 addition & 1 deletion includes/admin/class-wcs-admin-reports.php
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ public static function display_hpos_incompatibility_notice() {
// translators: placeholders $1 and $2 are opening <a> tags linking to the WooCommerce documentation on HPOS and data synchronization. Placeholder $3 is a closing link (<a>) tag.
__( 'Subscription reports are incompatible with the %1$sWooCommerce data storage features%3$s enabled on your store. Please enable %2$stable synchronization%3$s if you wish to use subscription reports.', 'woocommerce-subscriptions' ),
'<a href="https://woocommerce.com/document/high-performance-order-storage/">',
'<a href="https://woocommerce.com/document/high-performance-order-storage/#section-4">',
'<a href="https://woocommerce.com/document/high-performance-order-storage/#synchronization">',
'</a>'
)
)
4 changes: 2 additions & 2 deletions includes/class-wcs-manual-renewal-manager.php
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ public static function add_settings( $settings ) {
'default' => 'no',
'type' => 'checkbox',
// translators: placeholders are opening and closing link tags
'desc_tip' => sprintf( __( 'With manual renewals, a customer\'s subscription is put on-hold until they login and pay to renew it. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="http://docs.woocommerce.com/document/subscriptions/store-manager-guide/#accept-manual-renewals">', '</a>' ),
'desc_tip' => sprintf( __( 'With manual renewals, a customer\'s subscription is put on-hold until they login and pay to renew it. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="https://woocommerce.com/document/subscriptions/store-manager-guide/#accept-manual-renewals">', '</a>' ),
'checkboxgroup' => 'start',
'show_if_checked' => 'option',
),
@@ -51,7 +51,7 @@ public static function add_settings( $settings ) {
'default' => 'no',
'type' => 'checkbox',
// translators: placeholders are opening and closing link tags
'desc_tip' => sprintf( __( 'If you don\'t want new subscription purchases to automatically charge renewal payments, you can turn off automatic payments. Existing automatic subscriptions will continue to charge customers automatically. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="http://docs.woocommerce.com/document/subscriptions/store-manager-guide/#turn-off-automatic-payments">', '</a>' ),
'desc_tip' => sprintf( __( 'If you don\'t want new subscription purchases to automatically charge renewal payments, you can turn off automatic payments. Existing automatic subscriptions will continue to charge customers automatically. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="https://woocommerce.com/document/subscriptions/store-manager-guide/#turn-off-automatic-payments">', '</a>' ),
'checkboxgroup' => 'end',
'show_if_checked' => 'yes',
),
4 changes: 2 additions & 2 deletions includes/class-wcs-upgrade-notice-manager.php
Original file line number Diff line number Diff line change
@@ -86,7 +86,7 @@ public static function maybe_show_admin_notice() {
'description' => sprintf(
// translators: 1-3: opening/closing <a> tags - link to documentation.
__( 'Webhook and REST API users can now use v3 subscription endpoints. Click here to %1$slearn more%2$s about the REST API and check out the technical API docs %3$shere%2$s.', 'woocommerce-subscriptions' ),
'<a href="https://docs.woocommerce.com/document/woocommerce-rest-api/">',
'<a href="https://woocommerce.com/document/woocommerce-rest-api/">',
'</a>',
'<a href="https://woocommerce.github.io/subscriptions-rest-api-docs/">'
),
@@ -96,7 +96,7 @@ public static function maybe_show_admin_notice() {
'description' => sprintf(
// translators: 1-2: opening/closing <a> tags - link to documentation.
__( 'Subscriptions is now compatible with the WooCommerce cart and checkout blocks. You can learn more about the compatibility status of the cart & checkout blocks %1$shere%2$s.', 'woocommerce-subscriptions' ),
'<a href="https://docs.woocommerce.com/document/cart-checkout-blocks-support-status/">', '</a>'
'<a href="https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout/#compatible-extensions">', '</a>'
),
),
);
2 changes: 1 addition & 1 deletion includes/early-renewal/class-wcs-early-renewal-manager.php
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ public static function add_settings( $settings ) {
/* translators: 1-2: opening/closing <strong> tags , 2-3: opening/closing tags for a link to docs on early renewal. */
__( 'Allow customers to bypass the checkout and renew their subscription early from their %1$sMy Account > View Subscription%2$s page. %3$sLearn more.%4$s', 'woocommerce-subscriptions' ),
'<strong>', '</strong>',
'<a href="https://docs.woocommerce.com/document/subscriptions/early-renewal/">', '</a>'
'<a href="https://woocommerce.com/document/subscriptions/early-renewal/">', '</a>'
),
'default' => 'no',
'type' => 'checkbox',
Original file line number Diff line number Diff line change
@@ -149,7 +149,7 @@ public static function gateway_supports_subscriptions( $gateway ) {
public static function add_recurring_payment_gateway_information( $settings, $option_prefix ) {
$settings[] = array(
// translators: $1-$2: opening and closing tags. Link to documents->payment gateways, 3$-4$: opening and closing tags. Link to WooCommerce extensions shop page
'desc' => sprintf( __( 'Find new gateways that %1$ssupport automatic subscription payments%2$s in the official %3$sWooCommerce Marketplace%4$s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'http://docs.woocommerce.com/document/subscriptions/payment-gateways/' ) . '">', '</a>', '<a href="' . esc_url( 'http://www.woocommerce.com/product-category/woocommerce-extensions/' ) . '">', '</a>' ),
'desc' => sprintf( __( 'Find new gateways that %1$ssupport automatic subscription payments%2$s in the official %3$sWooCommerce Marketplace%4$s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'https://woocommerce.com/document/subscriptions/payment-gateways/' ) . '">', '</a>', '<a href="' . esc_url( 'http://www.woocommerce.com/product-category/woocommerce-extensions/' ) . '">', '</a>' ),
'id' => $option_prefix . '_payment_gateways_additional',
'type' => 'informational',
);
2 changes: 1 addition & 1 deletion includes/payment-retry/admin/class-wcs-retry-admin.php
Original file line number Diff line number Diff line change
@@ -165,7 +165,7 @@ public function add_settings( $settings ) {
'default' => 'no',
'type' => 'checkbox',
// translators: 1,2: opening/closing link tags (to documentation).
'desc_tip' => sprintf( __( 'Attempt to recover recurring revenue that would otherwise be lost due to payment methods being declined only temporarily. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="https://docs.woocommerce.com/document/subscriptions/failed-payment-retry/">', '</a>' ),
'desc_tip' => sprintf( __( 'Attempt to recover recurring revenue that would otherwise be lost due to payment methods being declined only temporarily. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="https://woocommerce.com/document/subscriptions/failed-payment-retry/">', '</a>' ),
),
) );

82 changes: 80 additions & 2 deletions includes/switching/class-wc-subscriptions-switcher.php
Original file line number Diff line number Diff line change
@@ -145,6 +145,8 @@ public static function init() {

// Override the add to cart text when switch args are present.
add_filter( 'woocommerce_product_single_add_to_cart_text', array( __CLASS__, 'display_switch_add_to_cart_text' ), 10, 1 );

add_filter( 'woocommerce_subscriptions_calculated_total', [ __CLASS__, 'remove_handled_switch_recurring_carts' ], 100, 1 );
}

/**
@@ -382,7 +384,7 @@ public static function add_settings( $settings ) {
'name' => __( 'Switching', 'woocommerce-subscriptions' ),
'type' => 'title',
// translators: placeholders are opening and closing link tags
'desc' => sprintf( __( 'Allow subscribers to switch (upgrade or downgrade) between different subscriptions. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'http://docs.woocommerce.com/document/subscriptions/switching-guide/' ) . '">', '</a>' ),
'desc' => sprintf( __( 'Allow subscribers to switch (upgrade or downgrade) between different subscriptions. %1$sLearn more%2$s.', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'https://woocommerce.com/document/subscriptions/switching-guide/' ) . '">', '</a>' ),
'id' => WC_Subscriptions_Admin::$option_prefix . '_switch_settings',
),
array(
@@ -1105,7 +1107,15 @@ public static function process_checkout( $order_id, $posted_data = array() ) {
}
}

$switch_order_data[ $subscription->get_id() ]['switches'][ $cart_item['subscription_switch']['order_line_item_id'] ] = $switched_item_data;
// Obtain the new order item id from the cart item switch data.
if ( isset( $cart_item['subscription_switch']['order_line_item_id'] ) ) {
$new_order_item_id = $cart_item['subscription_switch']['order_line_item_id'];
} else {
$new_order_item_id = wc_get_order_item_meta( $cart_item['subscription_switch']['item_id'], '_switched_subscription_new_item_id', true );
}

// Store the switching data for this item.
$switch_order_data[ $subscription->get_id() ]['switches'][ $new_order_item_id ] = $switched_item_data;

// If the old subscription has just one item, we can safely update its billing schedule
if ( $can_update_existing_subscription ) {
@@ -2982,4 +2992,72 @@ public static function switch_order_meta_box_rows( $post ) {
}

}

/**
* Removes subscription items from recurring carts which have been handled.
*
* It's possible that after we've processed the subscription switches and removed any recurring carts that shouldn't lead to new subscriptions,
* that someone could call WC()->cart->calculate_totals() and that would lead us to recreate all the recurring carts after we've already processed them.
*
* This method runs after subscription recurring carts have been created and removes any recurring carts which have been handled.
*
* @param float $total The total amount of the cart.
* @return float $total. The total amount of the cart. This is a pass-through method and doesn't modify the total.
*/
public static function remove_handled_switch_recurring_carts( $total ) {
if ( ! isset( WC()->cart->recurring_carts ) ) {
return $total;
}

// We only want to remove the recurring cart if the switch order has been processed.
if ( ! did_action( 'woocommerce_subscription_checkout_switch_order_processed' ) ) {
return $total;
}

foreach ( WC()->cart->recurring_carts as $recurring_cart_key => $recurring_cart ) {

// Remove any items from the recurring cart which have been handled.
foreach ( $recurring_cart->get_cart() as $cart_item_key => $cart_item ) {
if ( ! isset( $cart_item['subscription_switch'] ) ) {
continue;
}

$subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] );
$switch_order = $subscription->get_last_order( 'all', 'switch' );

if ( empty( $switch_order ) ) {
continue;
}

$switch_order_data = wcs_get_objects_property( $switch_order, 'subscription_switch_data' );

// Skip if the switch order data is not set.
if ( ! isset( $switch_order_data[ $subscription->get_id() ]['switches'] ) ) {
continue;
}

$subscription_switch_data = $switch_order_data[ $subscription->get_id() ]['switches'];
$switched_subscription_item_id = $cart_item['subscription_switch']['item_id'];

foreach ( $subscription_switch_data as $switch_data ) {

// We're only interested in cases where there's a straight swap of items. ie there's a remove and an add.
if ( ! isset( $switch_data['remove_line_item'], $switch_data['add_line_item'] ) ) {
continue;
}

if ( $switch_data['remove_line_item'] === $switched_subscription_item_id ) {
unset( WC()->cart->recurring_carts[ $recurring_cart_key ]->cart_contents[ $cart_item_key ] );
}
}
}

// If the recurring cart is now empty, remove it.
if ( empty( WC()->cart->recurring_carts[ $recurring_cart_key ]->cart_contents ) ) {
unset( WC()->cart->recurring_carts[ $recurring_cart_key ] );
}
}

return $total;
}
}
20 changes: 10 additions & 10 deletions includes/switching/class-wcs-switch-cart-item.php
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ class WCS_Switch_Cart_Item {

/**
* The existing subscription line item being switched.
* @var WC_Order_Item_Product
* @var WC_Order_Item
*/
public $existing_item;

@@ -117,9 +117,9 @@ class WCS_Switch_Cart_Item {
*
* @since 2.6.0
*
* @param array $cart_item The cart item.
* @param WC_Subscription $subscription The subscription being switched.
* @param WC_Order_Item_Product $existing_item The subscription line item being switched.
* @param array $cart_item The cart item.
* @param WC_Subscription $subscription The subscription being switched.
* @param WC_Order_Item $existing_item The subscription line item being switched.
*
* @throws Exception If WC_Subscriptions_Product::get_expiration_date() returns an invalid date.
*/
@@ -263,15 +263,15 @@ public function get_last_order_paid_time() {
public function get_total_paid_for_current_period() {

if ( ! isset( $this->total_paid_for_current_period ) ) {
// If the last order was a switch with a fully reduced pre-paid term, the amount the cutomer has paid is just the total in that order.
// If the last order was a switch with a fully reduced pre-paid term, the amount the customer has paid is just the total in that order.
if ( $this->is_switch_after_fully_reduced_prepaid_term() ) {
$this->total_paid_for_current_period = WC_Subscriptions_Switcher::calculate_total_paid_since_last_order( $this->subscription, $this->existing_item, 'exclude_sign_up_fees', array( $this->get_last_switch_order() ) );
} else {
$this->total_paid_for_current_period = WC_Subscriptions_Switcher::calculate_total_paid_since_last_order( $this->subscription, $this->existing_item, 'exclude_sign_up_fees' );
}
}

return $this->total_paid_for_current_period;
return apply_filters( 'wcs_switch_total_paid_for_current_period', $this->total_paid_for_current_period, $this->subscription, $this->existing_item );
}

/**
@@ -446,7 +446,7 @@ protected function get_last_switch_order() {
static $switch_order = null;

if ( ! $switch_order ) {
$switch_order = $this->subscription->get_last_order( 'all', 'switch' );
$switch_order = $this->subscription->get_last_order( 'all', 'switch', [ 'checkout-draft' ] );
}

return $switch_order;
@@ -459,12 +459,12 @@ protected function get_last_switch_order() {
*
* For example:
* - Original purchase of a $70 / week subscription.
* - 5 days into the subscription the customer switches to a $120 / 3 days. The lower freqency triggers the pre-paid term to be reduced.
* - 5 days into the subscription the customer switches to a $120 / 3 days. The lower frequency triggers the pre-paid term to be reduced.
* - The $70 paid at $40 a day only entitles the customer to 1.75 days.
* - Because they are already 5 days into the subscription, that $70 is fully absorbed at the new price and no time is 'owed'.
* - The subscription starts today and the customer pays full price.
*
* @see https://docs.woocommerce.com/document/subscriptions/switching-guide/#section-11
* @see https://woocommerce.com/document/subscriptions/switching-guide/switching-process-and-costs/#upgrades
* @see WCS_Switch_Totals_Calculator::reduce_prepaid_term()
*
* @since 3.0.7
@@ -482,7 +482,7 @@ protected function is_switch_after_fully_reduced_prepaid_term() {

$switch_paid_date = $last_switch_order->get_date_paid();

// If the last switch order occured after the last payment order (parent or renewal)
// If the last switch order occurred before the last payment order (parent or renewal), then the last order wasn't a switch.
if ( $switch_paid_date->getTimestamp() < $this->get_last_order_paid_time() ) {
$this->is_switch_after_fully_reduced_prepaid_term = false;
return false;
Loading

0 comments on commit a8df935

Please sign in to comment.