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() ); } }