Example #1
0
 /**
  * Move download permissions from original order to the new subscription created for the order.
  *
  * @param WC_Subscription $subscription A subscription object
  * @param int $subscription_item_id ID of the product line item on the subscription
  * @param WC_Order $original_order The original order that was created to purchase the subscription
  * @since 2.0
  */
 private static function migrate_download_permissions($subscription, $subscription_item_id, $order)
 {
     global $wpdb;
     $product_id = wcs_get_canonical_product_id(wcs_get_order_item($subscription_item_id, $subscription));
     $rows_affected = $wpdb->update($wpdb->prefix . 'woocommerce_downloadable_product_permissions', array('order_id' => $subscription->id, 'order_key' => $subscription->order_key), array('order_id' => $order->id, 'order_key' => $order->order_key, 'product_id' => $product_id, 'user_id' => absint($subscription->get_user_id())), array('%d', '%s'), array('%d', '%s', '%d', '%d'));
     WCS_Upgrade_Logger::add(sprintf('For subscription %d: migrated %d download permissions for product %d', $subscription->id, $rows_affected, $product_id));
 }
 /**
  * Check if a given line item on the subscription had a sign-up fee, and if so, return the value of the sign-up fee.
  *
  * The single quantity sign-up fee will be returned instead of the total sign-up fee paid. For example, if 3 x a product
  * with a 10 BTC sign-up fee was purchased, a total 30 BTC was paid as the sign-up fee but this function will return 10 BTC.
  *
  * @param array|int Either an order item (in the array format returned by self::get_items()) or the ID of an order item.
  * @return bool
  * @since 2.0
  */
 public function get_items_sign_up_fee($line_item)
 {
     if (!is_array($line_item)) {
         $line_item = wcs_get_order_item($line_item, $this);
     }
     // If there was no original order, nothing was paid up-front which means no sign-up fee
     if (empty($this->order)) {
         $sign_up_fee = 0;
     } else {
         $original_order_item = '';
         // Find the matching item on the order
         foreach ($this->order->get_items() as $order_item) {
             if (wcs_get_canonical_product_id($line_item) == wcs_get_canonical_product_id($order_item)) {
                 $original_order_item = $order_item;
                 break;
             }
         }
         // No matching order item, so this item wasn't purchased in the original order
         if (empty($original_order_item)) {
             $sign_up_fee = 0;
         } elseif (isset($line_item['item_meta']['_has_trial'])) {
             // Sign up was was total amount paid for this item on original order
             $sign_up_fee = $original_order_item['line_total'] / $original_order_item['qty'];
         } else {
             // Sign-up fee is any amount on top of recurring amount
             $sign_up_fee = max($original_order_item['line_total'] / $original_order_item['qty'] - $line_item['line_total'] / $line_item['qty'], 0);
         }
     }
     return apply_filters('woocommerce_subscription_items_sign_up_fee', $sign_up_fee, $line_item, $this);
 }
 /**
  * Set the subscription prices to be used in calculating totals by @see WC_Subscriptions_Cart::calculate_subscription_totals()
  *
  * @since 2.0
  */
 public static function calculate_prorated_totals($cart)
 {
     if (false === self::cart_contains_switches()) {
         return;
     }
     // 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 (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
         if (!isset($cart_item['subscription_switch']['subscription_id'])) {
             continue;
         }
         $subscription = wcs_get_subscription($cart_item['subscription_switch']['subscription_id']);
         $existing_item = wcs_get_order_item($cart_item['subscription_switch']['item_id'], $subscription);
         if (empty($existing_item)) {
             WC()->cart->remove_cart_item($cart_item_key);
             continue;
         }
         $item_data = $cart_item['data'];
         $product_id = wcs_get_canonical_product_id($cart_item);
         $product = wc_get_product($product_id);
         $is_virtual_product = $product->is_virtual();
         // Set when the first payment and end date for the new subscription should occur
         WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = $cart_item['subscription_switch']['next_payment_timestamp'];
         WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['end_timestamp'] = $end_timestamp = strtotime(WC_Subscriptions_Product::get_expiration_date($product_id, $subscription->get_date('last_payment')));
         // Add any extra sign up fees required to switch to the new subscription
         if ('yes' == $apportion_sign_up_fee) {
             // 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;
             $sign_up_fee_paid = $subscription->get_items_sign_up_fee($existing_item, 'inclusive_of_tax');
             // Make sure total prorated sign-up fee is prorated across total amount of sign-up fee so that customer doesn't get extra discounts
             if ($cart_item['quantity'] > $existing_item['qty']) {
                 $sign_up_fee_paid = $sign_up_fee_paid * $existing_item['qty'] / $cart_item['quantity'];
             }
             WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee = max($sign_up_fee_due - $sign_up_fee_paid, 0);
             WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee_prorated = WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee;
         } elseif ('no' == $apportion_sign_up_fee) {
             // $0 the initial sign-up fee
             WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee = 0;
         }
         // Get the current subscription's last payment date
         $last_payment_timestamp = $subscription->get_time('last_payment');
         $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));
         // If the subscription contains a synced product and the next payment is actually the first payment, determine the days in the "old" cycle from the subscription object
         if (WC_Subscriptions_Synchroniser::subscription_contains_synced_product($subscription->id) && WC_Subscriptions_Synchroniser::calculate_first_payment_date($product, 'timestamp', $subscription->get_date('start')) == $next_payment_timestamp) {
             $days_in_old_cycle = wcs_get_days_in_cycle($subscription->billing_period, $subscription->billing_interval);
         } else {
             // 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 = $existing_item['line_total'];
         if ('yes' == $subscription->prices_include_tax || true === $subscription->prices_include_tax) {
             // WC_Abstract_Order::$prices_include_tax can be set to true in __construct() or to 'yes' in populate()
             $old_recurring_total += $existing_item['line_tax'];
         }
         // 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 == $subscription->billing_period && $item_data->subscription_period_interval == $subscription->billing_interval) {
             $days_in_new_cycle = $days_in_old_cycle;
             // Use $days_in_old_cycle to make sure they're consistent
         } else {
             // We need to figure out the price per day for the new subscription based on its billing schedule
             $days_in_new_cycle = wcs_get_days_in_cycle($item_data->subscription_period, $item_data->subscription_period_interval);
         }
         // We need to use the cart items price to ensure we include extras added by extensions like Product Add-ons
         $new_price_per_day = $item_data->price * $cart_item['quantity'] / $days_in_new_cycle;
         if ($old_price_per_day < $new_price_per_day) {
             WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['upgraded_or_downgraded'] = 'upgraded';
         } elseif ($old_price_per_day > $new_price_per_day && $new_price_per_day >= 0) {
             WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['upgraded_or_downgraded'] = 'downgraded';
         }
         // 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) {
             // 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) {
                         WC()->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 {
                         WC()->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);
                     // when calculating a subscription with one length (no more next payment date and the end date may have been pushed back) we need to pay for those extra days at the new price per day between the old next payment date and new end date
                     if (1 == $item_data->subscription_length) {
                         $days_to_new_end = floor(($end_timestamp - $next_payment_timestamp) / (60 * 60 * 24));
                         if ($days_to_new_end > 0) {
                             $extra_to_pay += $days_to_new_end * $new_price_per_day;
                         }
                     }
                     // We need to find the per item extra to pay so we can set it as the sign-up fee (WC will then multiply it by the quantity)
                     $extra_to_pay = $extra_to_pay / $cart_item['quantity'];
                     // Keep a record of the two separate amounts so we store these and calculate future switch amounts correctly
                     WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee_prorated = WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee;
                     WC()->cart->cart_contents[$cart_item_key]['data']->subscription_price_prorated = round($extra_to_pay, 2);
                     WC()->cart->cart_contents[$cart_item_key]['data']->subscription_sign_up_fee += round($extra_to_pay, 2);
                 }
                 // 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;
                 }
                 WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] = $next_payment_timestamp + $days_to_add * 60 * 60 * 24;
             }
             // The old price per day == the new price per day, no need to change anything
             if (WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp'] != $cart_item['subscription_switch']['next_payment_timestamp']) {
                 WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['recurring_payment_prorated'] = true;
             }
         }
         // 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 != WC()->cart->cart_contents[$cart_item_key]['subscription_switch']['first_payment_timestamp']) {
             WC()->cart->cart_contents[$cart_item_key]['data']->subscription_trial_length = 1;
         }
         if ('yes' == $apportion_length || 'virtual' == $apportion_length && $is_virtual_product) {
             $base_length = WC_Subscriptions_Product::get_length($product_id);
             $completed_payments = $subscription->get_completed_payment_count();
             $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;
             }
             WC()->cart->cart_contents[$cart_item_key]['data']->subscription_length = $length_remaining;
         }
     }
 }
 /**
  * Check if a given line item on the subscription had a sign-up fee, and if so, return the value of the sign-up fee.
  *
  * The single quantity sign-up fee will be returned instead of the total sign-up fee paid. For example, if 3 x a product
  * with a 10 BTC sign-up fee was purchased, a total 30 BTC was paid as the sign-up fee but this function will return 10 BTC.
  *
  * @param array|int Either an order item (in the array format returned by self::get_items()) or the ID of an order item.
  * @param  string $tax_inclusive_or_exclusive Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc
  * @return bool
  * @since 2.0
  */
 public function get_items_sign_up_fee($line_item, $tax_inclusive_or_exclusive = 'exclusive_of_tax')
 {
     if (!is_array($line_item)) {
         $line_item = wcs_get_order_item($line_item, $this);
     }
     // If there was no original order, nothing was paid up-front which means no sign-up fee
     if (empty($this->order)) {
         $sign_up_fee = 0;
     } else {
         $original_order_item = '';
         // Find the matching item on the order
         foreach ($this->order->get_items() as $order_item) {
             if (wcs_get_canonical_product_id($line_item) == wcs_get_canonical_product_id($order_item)) {
                 $original_order_item = $order_item;
                 break;
             }
         }
         // No matching order item, so this item wasn't purchased in the original order
         if (empty($original_order_item)) {
             $sign_up_fee = 0;
         } elseif (isset($line_item['item_meta']['_has_trial'])) {
             // Sign up was was total amount paid for this item on original order
             $sign_up_fee = $original_order_item['line_total'] / $original_order_item['qty'];
         } else {
             // Sign-up fee is any amount on top of recurring amount
             $sign_up_fee = max($original_order_item['line_total'] / $original_order_item['qty'] - $line_item['line_total'] / $line_item['qty'], 0);
         }
         // If prices inc tax, ensure that the sign up fee amount includes the tax
         if ('inclusive_of_tax' === $tax_inclusive_or_exclusive && !empty($original_order_item) && 'yes' == $this->prices_include_tax) {
             $proportion = $sign_up_fee / ($original_order_item['line_total'] / $original_order_item['qty']);
             $sign_up_fee += round($original_order_item['line_tax'] * $proportion, 2);
         }
     }
     return apply_filters('woocommerce_subscription_items_sign_up_fee', $sign_up_fee, $line_item, $this, $tax_inclusive_or_exclusive);
 }