mirror of
https://github.com/pronamic/woocommerce-subscriptions.git
synced 2025-10-07 18:14:03 +00:00
372 lines
15 KiB
PHP
372 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* Implement resubscribing to a subscription via the cart.
|
|
*
|
|
* Resubscribing is a similar process to renewal via checkout (which is why this class extends WCS_Cart_Renewal), only it:
|
|
* - creates a new subscription with similar terms to the existing subscription, where as a renewal resumes the existing subscription
|
|
* - is for an expired or cancelled subscription only.
|
|
*
|
|
* @package WooCommerce Subscriptions
|
|
* @subpackage WCS_Cart_Resubscribe
|
|
* @category Class
|
|
* @author Prospress
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
|
|
class WCS_Cart_Resubscribe extends WCS_Cart_Renewal {
|
|
|
|
/* The flag used to indicate if a cart item is a renewal */
|
|
public $cart_item_key = 'subscription_resubscribe';
|
|
|
|
/**
|
|
* Bootstraps the class and hooks required actions & filters.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
public function __construct() {
|
|
|
|
$this->setup_hooks();
|
|
|
|
// When a resubscribe order is created on checkout, record the resubscribe, attached after WC_Subscriptions_Checkout::process_checkout()
|
|
add_action( 'woocommerce_checkout_subscription_created', array( &$this, 'maybe_record_resubscribe' ), 10, 3 );
|
|
|
|
add_filter( 'woocommerce_subscriptions_recurring_cart_key', array( &$this, 'get_recurring_cart_key' ), 10, 2 );
|
|
|
|
add_filter( 'wcs_recurring_cart_next_payment_date', array( &$this, 'recurring_cart_next_payment_date' ), 100, 2 );
|
|
|
|
// Mock a free trial on the cart item to make sure the resubscribe total doesn't include any recurring amount when honoring prepaid term
|
|
add_filter( 'woocommerce_before_calculate_totals', array( &$this, 'maybe_set_free_trial' ), 100, 1 );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_subscription_cart_before_grouping', array( &$this, 'maybe_unset_free_trial' ) );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_subscription_cart_after_grouping', array( &$this, 'maybe_set_free_trial' ) );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'wcs_recurring_cart_start_date', array( &$this, 'maybe_unset_free_trial' ), 0, 1 );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'wcs_recurring_cart_end_date', array( &$this, 'maybe_set_free_trial' ), 100, 1 );
|
|
add_filter( 'woocommerce_subscriptions_calculated_total', array( &$this, 'maybe_unset_free_trial' ), 10000, 1 );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_cart_totals_before_shipping', array( &$this, 'maybe_set_free_trial' ) );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_cart_totals_after_shipping', array( &$this, 'maybe_unset_free_trial' ) );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_review_order_before_shipping', array( &$this, 'maybe_set_free_trial' ) );
|
|
// @phpstan-ignore return.void
|
|
add_action( 'woocommerce_review_order_after_shipping', array( &$this, 'maybe_unset_free_trial' ) );
|
|
|
|
add_action( 'woocommerce_order_status_changed', array( &$this, 'maybe_cancel_existing_subscription' ), 10, 3 );
|
|
|
|
add_filter( 'wc_dynamic_pricing_apply_cart_item_adjustment', array( &$this, 'prevent_compounding_dynamic_discounts' ), 10, 2 );
|
|
}
|
|
|
|
/**
|
|
* Checks if the current request is by a user to resubcribe to a subscription, and if it is setup a
|
|
* subscription resubcribe process via the cart for the product/variation/s that are being renewed.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
public function maybe_setup_cart() {
|
|
global $wp;
|
|
|
|
if ( isset( $_GET['resubscribe'] ) && isset( $_GET['_wpnonce'] ) ) {
|
|
|
|
$subscription = wcs_get_subscription( wc_clean( wp_unslash( $_GET['resubscribe'] ) ) );
|
|
$redirect_to = get_permalink( wc_get_page_id( 'myaccount' ) );
|
|
|
|
if ( wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), $subscription->get_id() ) === false ) {
|
|
wc_add_notice( __( 'There was an error with your request to resubscribe. Please try again.', 'woocommerce-subscriptions' ), 'error' );
|
|
} elseif ( empty( $subscription ) ) {
|
|
wc_add_notice( __( 'That subscription does not exist. Has it been deleted?', 'woocommerce-subscriptions' ), 'error' );
|
|
|
|
} elseif ( ! current_user_can( 'subscribe_again', $subscription->get_id() ) ) {
|
|
|
|
wc_add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' );
|
|
|
|
} elseif ( ! wcs_can_user_resubscribe_to( $subscription ) ) {
|
|
|
|
wc_add_notice( __( 'You can not resubscribe to that subscription. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' );
|
|
} else {
|
|
|
|
$this->setup_cart( $subscription, array(
|
|
'subscription_id' => $subscription->get_id(),
|
|
), 'all_items_required' );
|
|
|
|
if ( WC()->cart->get_cart_contents_count() != 0 ) {
|
|
wc_add_notice( __( 'Complete checkout to resubscribe.', 'woocommerce-subscriptions' ), 'success' );
|
|
}
|
|
|
|
$redirect_to = wc_get_checkout_url();
|
|
}
|
|
|
|
wp_safe_redirect( $redirect_to );
|
|
exit;
|
|
|
|
} elseif ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) && isset( $wp->query_vars['order-pay'] ) ) {
|
|
|
|
$order_id = ( isset( $wp->query_vars['order-pay'] ) ) ? $wp->query_vars['order-pay'] : absint( $_GET['order_id'] );
|
|
$order = wc_get_order( $wp->query_vars['order-pay'] );
|
|
$order_key = wc_clean( wp_unslash( $_GET['key'] ) );
|
|
|
|
if ( wcs_get_objects_property( $order, 'order_key' ) == $order_key && $order->has_status( array( 'pending', 'failed' ) ) && wcs_order_contains_resubscribe( $order ) ) {
|
|
|
|
if ( ! is_user_logged_in() ) {
|
|
|
|
$redirect = add_query_arg( array(
|
|
'wcs_redirect' => 'pay_for_order',
|
|
'wcs_redirect_id' => $order_id,
|
|
), get_permalink( wc_get_page_id( 'myaccount' ) ) );
|
|
|
|
wp_safe_redirect( $redirect );
|
|
exit;
|
|
}
|
|
|
|
wc_add_notice( __( 'Complete checkout to resubscribe.', 'woocommerce-subscriptions' ), 'success' );
|
|
|
|
$subscriptions = wcs_get_subscriptions_for_resubscribe_order( $order );
|
|
|
|
foreach ( $subscriptions as $subscription ) {
|
|
if ( current_user_can( 'subscribe_again', $subscription->get_id() ) ) {
|
|
$this->setup_cart( $subscription, array(
|
|
'subscription_id' => $subscription->get_id(),
|
|
), 'all_items_required' );
|
|
} else {
|
|
wc_add_notice( __( 'That doesn\'t appear to be one of your subscriptions.', 'woocommerce-subscriptions' ), 'error' );
|
|
wp_safe_redirect( get_permalink( wc_get_page_id( 'myaccount' ) ) );
|
|
exit;
|
|
}
|
|
}
|
|
|
|
wp_safe_redirect( wc_get_checkout_url() );
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When creating an order at checkout, if the checkout is to resubscribe to an expired or cancelled
|
|
* subscription, make sure we record that on the order and new subscription.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
public function maybe_record_resubscribe( $new_subscription, $order, $recurring_cart ) {
|
|
|
|
$cart_item = $this->cart_contains( $recurring_cart );
|
|
|
|
if ( false !== $cart_item ) {
|
|
$old_subscription = wcs_get_subscription( $cart_item[ $this->cart_item_key ]['subscription_id'] );
|
|
WCS_Related_Order_Store::instance()->add_relation( $order, $old_subscription, 'resubscribe' );
|
|
WCS_Related_Order_Store::instance()->add_relation( $new_subscription, $old_subscription, 'resubscribe' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore renewal flag when cart is reset and modify Product object with renewal order related info
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
*/
|
|
public function get_cart_item_from_session( $cart_item_session_data, $cart_item, $key ) {
|
|
if ( isset( $cart_item[ $this->cart_item_key ]['subscription_id'] ) ) {
|
|
|
|
// Setup the cart as if it's a renewal (as the setup process is almost the same)
|
|
$cart_item_session_data = parent::get_cart_item_from_session( $cart_item_session_data, $cart_item, $key );
|
|
|
|
// Need to get the original subscription price, not the current price
|
|
$subscription = wcs_get_subscription( $cart_item[ $this->cart_item_key ]['subscription_id'] );
|
|
if ( $subscription ) {
|
|
// Make sure the original subscription terms persist
|
|
$_product = $cart_item_session_data['data'];
|
|
wcs_set_objects_property( $_product, 'subscription_period', $subscription->get_billing_period(), 'set_prop_only' );
|
|
wcs_set_objects_property( $_product, 'subscription_period_interval', $subscription->get_billing_interval(), 'set_prop_only' );
|
|
|
|
// And don't give another free trial period
|
|
wcs_set_objects_property( $_product, 'subscription_trial_length', 0, 'set_prop_only' );
|
|
}
|
|
}
|
|
|
|
return $cart_item_session_data;
|
|
}
|
|
|
|
/**
|
|
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
|
|
* but the current request is to resubscribe to the subscription, then mark it as purchasable.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
|
* @return bool
|
|
*/
|
|
public function is_purchasable( $is_purchasable, $product ) {
|
|
_deprecated_function( __METHOD__, '2.1', 'WCS_Limiter::is_purchasable_renewal' );
|
|
return WCS_Limiter::is_purchasable_renewal( $is_purchasable, $product );
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks the cart to see if it contains a subscription resubscribe item.
|
|
*
|
|
* @see wcs_cart_contains_resubscribe()
|
|
* @param WC_Cart $cart The cart object to search in.
|
|
* @return bool|array The cart item containing the renewal, else false.
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.10
|
|
*/
|
|
protected function cart_contains( $cart = null ) {
|
|
return wcs_cart_contains_resubscribe( $cart );
|
|
}
|
|
|
|
/**
|
|
* Get the subscription object used to construct the resubscribe cart.
|
|
*
|
|
* @param array $cart_item The resubscribe cart item.
|
|
* @return WC_Subscription The subscription object.
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13
|
|
*/
|
|
protected function get_order( $cart_item = null ) {
|
|
$subscription = false;
|
|
|
|
if ( empty( $cart_item ) ) {
|
|
$cart_item = $this->cart_contains();
|
|
}
|
|
|
|
if ( false !== $cart_item && isset( $cart_item[ $this->cart_item_key ] ) ) {
|
|
$subscription = wcs_get_subscription( $cart_item[ $this->cart_item_key ]['subscription_id'] );
|
|
}
|
|
|
|
return $subscription;
|
|
}
|
|
|
|
/**
|
|
* Make sure that a resubscribe item's cart key is based on the end of the pre-paid term if the user already has a subscription that is pending-cancel, not the date calculated for the product.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
|
*/
|
|
public function get_recurring_cart_key( $cart_key, $cart_item ) {
|
|
$subscription = $this->get_order( $cart_item );
|
|
if ( false !== $subscription && $subscription->has_status( 'pending-cancel' ) ) {
|
|
remove_filter( 'woocommerce_subscriptions_recurring_cart_key', array( &$this, 'get_recurring_cart_key' ), 10 );
|
|
$cart_key = WC_Subscriptions_Cart::get_recurring_cart_key( $cart_item, $subscription->get_time( 'end' ) );
|
|
add_filter( 'woocommerce_subscriptions_recurring_cart_key', array( &$this, 'get_recurring_cart_key' ), 10, 2 );
|
|
}
|
|
|
|
return $cart_key;
|
|
}
|
|
|
|
/**
|
|
* Make sure when displaying the next payment date for a subscription, the date takes into
|
|
* account the end of the pre-paid term if the user is resubscribing to a subscription that is pending-cancel.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
|
*/
|
|
public function recurring_cart_next_payment_date( $first_renewal_date, $cart ) {
|
|
foreach ( $cart->get_cart() as $cart_item ) {
|
|
$subscription = $this->get_order( $cart_item );
|
|
|
|
if ( $this->is_pre_cancelled_resubscribe( $subscription ) ) {
|
|
$first_renewal_date = ( '1' != WC_Subscriptions_Product::get_length( $cart_item['data'] ) ) ? $subscription->get_date( 'end' ) : 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $first_renewal_date;
|
|
}
|
|
|
|
/**
|
|
* Make sure resubscribe cart item price doesn't include any recurring amount by setting a free trial.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
|
* @param mixed $total This parameter is unused. Its sole purpose is for returning an unchanged variable while setting the mock trial when hooked onto filters. Optional.
|
|
* @return mixed $total The unchanged $total parameter.
|
|
*/
|
|
public function maybe_set_free_trial( $total = '' ) {
|
|
$subscription = $this->get_order();
|
|
|
|
if ( $this->is_pre_cancelled_resubscribe( $subscription ) ) {
|
|
foreach ( WC()->cart->cart_contents as &$cart_item ) {
|
|
if ( isset( $cart_item[ $this->cart_item_key ] ) ) {
|
|
wcs_set_objects_property( $cart_item['data'], 'subscription_trial_length', 1, 'set_prop_only' );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
|
|
/**
|
|
* Remove mock free trials from resubscribe cart items.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
|
* @param mixed $total This parameter is unused. Its sole purpose is for returning an unchanged variable while unsetting the mock trial when hooked onto filters. Optional.
|
|
* @return mixed $total The unchanged $total parameter.
|
|
*/
|
|
public function maybe_unset_free_trial( $total = '' ) {
|
|
$subscription = $this->get_order();
|
|
|
|
if ( $this->is_pre_cancelled_resubscribe( $subscription ) ) {
|
|
foreach ( WC()->cart->cart_contents as &$cart_item ) {
|
|
if ( isset( $cart_item[ $this->cart_item_key ] ) ) {
|
|
wcs_set_objects_property( $cart_item['data'], 'subscription_trial_length', 0, 'set_prop_only' );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
|
|
/**
|
|
* When the user resubscribes to a subscription that is pending-cancel, cancel the existing subscription.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
|
*/
|
|
public function maybe_cancel_existing_subscription( $order_id, $old_order_status, $new_order_status ) {
|
|
if ( wcs_order_contains_subscription( $order_id ) && wcs_order_contains_resubscribe( $order_id ) ) {
|
|
$order = wc_get_order( $order_id );
|
|
|
|
foreach ( wcs_get_subscriptions_for_resubscribe_order( $order_id ) as $subscription ) {
|
|
if ( $subscription->has_status( 'pending-cancel' ) ) {
|
|
// translators: %s: order number.
|
|
$cancel_note = sprintf( __( 'Customer resubscribed in order #%s', 'woocommerce-subscriptions' ), $order->get_order_number() );
|
|
$subscription->update_status( 'cancelled', $cancel_note );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overrides the place order button text on the checkout when the cart contains only resubscribe requests.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0
|
|
*
|
|
* @param string $place_order_text The place order button text.
|
|
* @return string The place order button text. 'Resubscribe' if the cart contains only resubscribe requests, otherwise the default.
|
|
*/
|
|
public function order_button_text( $place_order_text ) {
|
|
|
|
if ( isset( WC()->cart ) && count( wcs_get_order_type_cart_items( 'resubscribe' ) ) === count( WC()->cart->get_cart() ) ) {
|
|
$place_order_text = _x( 'Resubscribe', 'The place order button text while resubscribing to a subscription', 'woocommerce-subscriptions' );
|
|
}
|
|
|
|
return $place_order_text;
|
|
}
|
|
|
|
/**
|
|
* Determines if the customer is resubscribe prior to the subscription being cancelled.
|
|
*
|
|
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0
|
|
*
|
|
* @param WC_Subscription $subscription
|
|
* @return bool
|
|
*/
|
|
private function is_pre_cancelled_resubscribe( $subscription ) {
|
|
return is_a( $subscription, 'WC_Subscription' ) && $subscription->has_status( 'pending-cancel' ) && $subscription->get_time( 'end' ) > gmdate( 'U' );
|
|
}
|
|
|
|
/**
|
|
* Checks if the current user can resubscribe to the subscription.
|
|
*
|
|
* @since 1.6.3
|
|
*
|
|
* @param WC_Subscription $subscription The WC subscription to validate the current user against.
|
|
* @return bool Whether the current user can resubscribe to the subscription.
|
|
*/
|
|
public function validate_current_user( $subscription ) {
|
|
return current_user_can( 'subscribe_again', $subscription->get_id() );
|
|
}
|
|
}
|