/** * Set the subscription prices to be used in calculating totals by @see WC_Subscriptions_Cart::calculate_subscription_totals() * * @since 1.4 */ public static function maybe_set_apporitioned_totals($total) { global $woocommerce; if (false === self::cart_contains_subscription_switch()) { return $total; } // Maybe charge an initial amount to account for upgrading from a cheaper subscription $apportion_recurring_price = get_option(WC_Subscriptions_Admin::$option_prefix . '_apportion_recurring_price', 'no'); $apportion_sign_up_fee = get_option(WC_Subscriptions_Admin::$option_prefix . '_apportion_sign_up_fee', 'no'); $apportion_length = get_option(WC_Subscriptions_Admin::$option_prefix . '_apportion_length', 'no'); foreach ($woocommerce->cart->get_cart() as $cart_item_key => $cart_item) { if (!isset($cart_item['subscription_switch']['subscription_key'])) { continue; } $old_subscription_key = $cart_item['subscription_switch']['subscription_key']; $item_data = $cart_item['data']; $product_id = empty($cart_item['variation_id']) ? $cart_item['product_id'] : $cart_item['variation_id']; $product = get_product($product_id); // Set the date on which the first payment for the new subscription should be charged $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = $cart_item['subscription_switch']['next_payment_timestamp']; $is_virtual_product = 'no' != $item_data->virtual ? true : false; // Add any extra sign up fees required to switch to the new subscription if ('yes' == $apportion_sign_up_fee) { $old_subscription = WC_Subscriptions_Manager::get_subscription($old_subscription_key); $old_order = new WC_Order($old_subscription['order_id']); // Because product add-ons etc. don't apply to sign-up fees, it's safe to use the product's sign-up fee value rather than the cart item's $sign_up_fee_due = $product->subscription_sign_up_fee; if (self::order_contains_subscription_switch($old_subscription['order_id'])) { // Deduct any prorated amounts already paid $sign_up_fee_paid = $old_order->get_total(); } else { // We need the product's raw sign-up fee value $sign_up_fee_paid = WC_Subscriptions_Order::get_item_meta($old_order, '_subscription_sign_up_fee', $old_subscription['product_id'], 0); } $woocommerce->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee = max($sign_up_fee_due - $sign_up_fee_paid, 0); } elseif ('no' == $apportion_sign_up_fee) { // $0 the initial sign-up fee $woocommerce->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee = 0; } // Now lets see if we should add a prorated amount to the sign-up fee (for upgrades) or extend the next payment date (for downgrades) if (in_array($apportion_recurring_price, array('yes', 'yes-upgrade')) || in_array($apportion_recurring_price, array('virtual', 'virtual-upgrade')) && $is_virtual_product) { // Get the current subscription $old_subscription = isset($old_subscription) ? $old_subscription : WC_Subscriptions_Manager::get_subscription($old_subscription_key); // Get the current subscription's original order $old_order = isset($old_order) ? $old_order : new WC_Order($old_subscription['order_id']); // Get the current subscription's last payment date $last_payment_timestamp = WC_Subscriptions_Manager::get_last_payment_date($old_subscription_key, get_current_user_id(), 'timestamp'); $days_since_last_payment = floor((gmdate('U') - $last_payment_timestamp) / (60 * 60 * 24)); // Get the current subscription's next payment date $next_payment_timestamp = $cart_item['subscription_switch']['next_payment_timestamp']; $days_until_next_payment = ceil(($next_payment_timestamp - gmdate('U')) / (60 * 60 * 24)); // Find the number of days between the two $days_in_old_cycle = $days_until_next_payment + $days_since_last_payment; // Find the actual recurring amount charged for the old subscription (we need to use the '_recurring_line_total' meta here rather than '_subscription_recurring_amount' because we want the recurring amount to include extra from extensions, like Product Add-ons etc.) $old_recurring_total = WC_Subscriptions_Order::get_item_meta($old_order, '_recurring_line_total', $old_subscription['product_id'], 0) / WC_Subscriptions_Order::get_item_meta($old_order, '_qty', $old_subscription['product_id'], 0); // Find the $price per day for the old subscription's recurring total $old_price_per_day = $old_recurring_total / $days_in_old_cycle; // Find the price per day for the new subscription's recurring total // If the subscription uses the same billing interval & cycle as the old subscription, if ($item_data->subscription_period == $old_subscription['period'] && $item_data->subscription_period_interval == $old_subscription['interval']) { $days_in_new_cycle = $days_in_old_cycle; // Use $days_in_old_cycle to make sure they're consistent $new_price_per_day = $item_data->price / $days_in_old_cycle; // We need to use the cart items price to ensure we include extras added by extensions like Product Add-ons } else { // We need to figure out the price per day for the new subscription based on its billing schedule switch ($item_data->subscription_period) { case 'day': $days_in_new_cycle = $item_data->subscription_period_interval; break; case 'week': $days_in_new_cycle = $item_data->subscription_period_interval * 7; break; case 'month': $days_in_new_cycle = $item_data->subscription_period_interval * 30.4375; // Average days per month over 4 year period break; case 'year': $days_in_new_cycle = $item_data->subscription_period_interval * 365.25; // Average days per year over 4 year period break; } $new_price_per_day = $item_data->price / $days_in_new_cycle; } // If the customer is upgrading, we may need to add a gap payment to the sign-up fee or to reduce the pre-paid period (or both) if ($old_price_per_day < $new_price_per_day) { // The new subscription may be more expensive, but it's also on a shorter billing cycle, so reduce the next pre-paid term if ($days_in_old_cycle > $days_in_new_cycle) { // Find out how many days at the new price per day the customer would receive for the total amount already paid // (e.g. if the customer paid $10 / month previously, and was switching to a $5 / week subscription, she has pre-paid 14 days at the new price) $pre_paid_days = 0; do { $pre_paid_days++; $new_total_paid = $pre_paid_days * $new_price_per_day; } while ($new_total_paid < $old_recurring_total); // If the total amount the customer has paid entitles her to more days at the new price than she has received, there is no gap payment, just shorten the pre-paid term the appropriate number of days if ($days_since_last_payment < $pre_paid_days) { $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = $last_payment_timestamp + $pre_paid_days * 60 * 60 * 24; // If the total amount the customer has paid entitles her to the same or less days at the new price then start the new subscription from today } else { $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = 0; } } else { $extra_to_pay = $days_until_next_payment * ($new_price_per_day - $old_price_per_day); $woocommerce->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee += round($extra_to_pay, 2); } $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['upgraded_or_downgraded'] = 'upgraded'; // If the customer is downgrading, set the next payment date and maybe extend it if downgrades are prorated } elseif ($old_price_per_day > $new_price_per_day && $new_price_per_day > 0) { $old_total_paid = $old_price_per_day * $days_until_next_payment; $new_total_paid = $new_price_per_day; // if downgrades are apportioned, extend the next payment date for n more days if (in_array($apportion_recurring_price, array('virtual', 'yes'))) { // Find how many more days at the new lower price it takes to exceed the amount already paid for ($days_to_add = 0; $new_total_paid <= $old_total_paid; $days_to_add++) { $new_total_paid = $days_to_add * $new_price_per_day; } $days_to_add -= $days_until_next_payment; } else { $days_to_add = 0; } $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = $next_payment_timestamp + $days_to_add * 60 * 60 * 24; $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['upgraded_or_downgraded'] = 'downgraded'; } // The old price per day == the new price per day, no need to change anything } // Finally, if we need to make sure the initial total doesn't include any recurring amount, we can by spoofing a free trial if (0 != $woocommerce->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp']) { $woocommerce->cart->cart_contents[$cart_item_key]['data']->subscription_trial_length = 1; } if ('yes' == $apportion_length || 'virtual' == $apportion_length && $is_virtual_product) { // Maybe charge an initial amount to account for upgrading from a cheaper subscription $base_length = WC_Subscriptions_Product::get_length($product_id); $completed_payments = WC_Subscriptions_Manager::get_subscriptions_completed_payment_count($old_subscription_key); $length_remaining = $base_length - $completed_payments; // Default to the base length if more payments have already been made than this subscription requires if ($length_remaining < 0) { $length_remaining = $base_length; } $woocommerce->cart->cart_contents[$cart_item_key]['data']->subscription_length = $length_remaining; } } return $total; }