/**
  * Uses the details of an order to create a pending subscription on the customers account
  * for a subscription product, as specified with $product_id.
  *
  * @param $order mixed int | WC_Order The order ID or WC_Order object to create the subscription from.
  * @param $product_id int The ID of the subscription product on the order.
  * @param $args array An array of name => value pairs to customise the details of the subscription, including:
  * 			'start_date' A MySQL formatted date/time string on which the subscription should start, in UTC timezone
  * 			'expiry_date' A MySQL formatted date/time string on which the subscription should expire, in UTC timezone
  * @since 1.1
  */
 public static function create_pending_subscription_for_order($order, $product_id, $args = array())
 {
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     if (!WC_Subscriptions_Product::is_subscription($product_id)) {
         return;
     }
     $args = wp_parse_args($args, array('start_date' => '', 'expiry_date' => ''));
     $subscription_key = self::get_subscription_key($order->id, $product_id);
     // In case the subscription exists already
     $subscription = self::get_users_subscription($order->customer_user, $subscription_key);
     // Adding a new subscription so set the start date/time to now
     if (!empty($args['start_date'])) {
         if (is_numeric($args['start_date'])) {
             $args['start_date'] = date('Y-m-d H:i:s', $args['start_date']);
         }
         $start_date = $args['start_date'];
     } else {
         $start_date = isset($subscription['start_date']) ? $subscription['start_date'] : gmdate('Y-m-d H:i:s');
     }
     // Adding a new subscription so set the expiry date/time from the order date
     if (!empty($args['expiry_date'])) {
         if (is_numeric($args['expiry_date'])) {
             $args['expiry_date'] = date('Y-m-d H:i:s', $args['expiry_date']);
         }
         $expiration = $args['expiry_date'];
     } else {
         $expiration = isset($subscription['expiry_date']) ? $subscription['expiry_date'] : WC_Subscriptions_Product::get_expiration_date($product_id, $start_date);
     }
     // Adding a new subscription so set the expiry date/time from the order date
     $trial_expiration = isset($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, $start_date);
     $failed_payments = isset($subscription['failed_payments']) ? $subscription['failed_payments'] : 0;
     $completed_payments = isset($subscription['completed_payments']) ? $subscription['completed_payments'] : array();
     $subscriptions[$subscription_key] = array('product_id' => $product_id, 'order_key' => $order->order_key, 'order_id' => $order->id, 'start_date' => $start_date, 'expiry_date' => $expiration, 'end_date' => 0, 'status' => 'pending', 'trial_expiry_date' => $trial_expiration, 'failed_payments' => $failed_payments, 'completed_payments' => $completed_payments);
     self::update_users_subscriptions($order->customer_user, $subscriptions);
     $product = WC_Subscriptions::get_product($product_id);
     // Set subscription status to active and log activation
     $order->add_order_note(sprintf(__('Pending subscription created for "%s".', WC_Subscriptions::$text_domain), $product->get_title()));
     do_action('pending_subscription_created_for_order', $order, $product_id);
 }
 /**
  * Calculate the first payment date for a synced subscription.
  *
  * The date is calculated in UTC timezone.
  *
  * @param WC_Product $product A subscription product.
  * @param string $type (optional) The format to return the first payment date in, either 'mysql' or 'timestamp'. Default 'mysql'.
  * @param string $from_date (optional) The date to calculate the first payment from in GMT/UTC timzeone. If not set, it will use the current date. This should not include any trial period on the product.
  * @since 1.5
  */
 public static function calculate_first_payment_date($product, $type = 'mysql', $from_date = '')
 {
     if (!is_object($product)) {
         $product = WC_Subscriptions::get_product($product);
     }
     if (!self::is_product_synced($product)) {
         return 0;
     }
     $period = WC_Subscriptions_Product::get_period($product);
     $trial_period = WC_Subscriptions_Product::get_trial_period($product);
     $trial_length = WC_Subscriptions_Product::get_trial_length($product);
     $from_date_param = $from_date;
     if (empty($from_date)) {
         $from_date = gmdate('Y-m-d H:i:s');
     }
     // If the subscription has a free trial period, the first payment should be synced to a day after the free trial
     if ($trial_length > 0) {
         $from_date = WC_Subscriptions_Product::get_trial_expiration_date($product, $from_date);
     }
     $from_timestamp = strtotime($from_date) + get_option('gmt_offset') * 3600;
     // Site time
     $payment_day = self::get_products_payment_day($product);
     if ('week' == $period) {
         // strtotime() only handles English, so can't use $wp_locale->weekday here
         $weekdays = array(1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday');
         // strtotime() will figure out if the day is in the future or today (see: https://gist.github.com/thenbrent/9698083)
         $first_payment_timestamp = strtotime($weekdays[$payment_day], $from_timestamp);
     } elseif ('month' == $period) {
         // strtotime() needs to know the month, so we need to determine if the specified day has occured this month yet or if we want the last day of the month (see: https://gist.github.com/thenbrent/9698083)
         if ($payment_day > 27) {
             // we actually want the last day of the month
             $payment_day = gmdate('t', $from_timestamp);
             $month = gmdate('F', $from_timestamp);
         } elseif (gmdate('j', $from_timestamp) > $payment_day) {
             // today is later than specified day in the from date, we need the next month
             $month = date('F', WC_Subscriptions::add_months($from_timestamp, 1));
         } else {
             // specified day is either today or still to come in the month of the from date
             $month = gmdate('F', $from_timestamp);
         }
         $first_payment_timestamp = strtotime("{$payment_day} {$month}", $from_timestamp);
     } elseif ('year' == $period) {
         // We can't use $wp_locale here because it is translated
         switch ($payment_day['month']) {
             case 1:
                 $month = 'January';
                 break;
             case 2:
                 $month = 'February';
                 break;
             case 3:
                 $month = 'March';
                 break;
             case 4:
                 $month = 'April';
                 break;
             case 5:
                 $month = 'May';
                 break;
             case 6:
                 $month = 'June';
                 break;
             case 7:
                 $month = 'July';
                 break;
             case 8:
                 $month = 'August';
                 break;
             case 9:
                 $month = 'September';
                 break;
             case 10:
                 $month = 'October';
                 break;
             case 11:
                 $month = 'November';
                 break;
             case 12:
                 $month = 'December';
                 break;
         }
         $first_payment_timestamp = strtotime("{$payment_day['day']} {$month}", $from_timestamp);
     }
     // Make sure the next payment is in the future and after the $from_date, as strtotime() will return the date this year for any day in the past when adding months or years (see: https://gist.github.com/thenbrent/9698083)
     if ('year' == $period || 'month' == $period) {
         // First make sure the day is in the past so that we don't end up jumping a month or year because of a few hours difference between now and the billing date
         if (gmdate('j', $first_payment_timestamp) < gmdate('j') && gmdate('n', $first_payment_timestamp) <= gmdate('n') && gmdate('Y', $first_payment_timestamp) <= gmdate('Y')) {
             $i = 1;
             // Then make sure the date and time of the payment is in the future
             while (($first_payment_timestamp < gmdate('U') || $first_payment_timestamp < $from_timestamp) && $i < 30) {
                 $first_payment_timestamp = strtotime("+ 1 {$period}", $first_payment_timestamp);
                 $i = $i + 1;
             }
         }
     }
     // We calculated a timestamp for midnight on the specific day in the site's timezone, let's push it to 3am to account for any daylight savings changes
     $first_payment_timestamp += 3 * HOUR_IN_SECONDS;
     // And convert it to the UTC equivalent of 3am on that day
     $first_payment_timestamp -= get_option('gmt_offset') * HOUR_IN_SECONDS;
     $first_payment = 'mysql' == $type && 0 != $first_payment_timestamp ? date('Y-m-d H:i:s', $first_payment_timestamp) : $first_payment_timestamp;
     return apply_filters('woocommerce_subscriptions_synced_first_payment_date', $first_payment, $product, $type, $from_date, $from_date_param);
 }
Exemplo n.º 3
0
 private function processSubscriptions()
 {
     global $wpdb;
     // check wether subscriptions addon is activated
     if (class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($this->order)) {
         $products = $this->order->get_items();
         foreach ($products as $product) {
             if (is_array($product) && isset($product['product_id']) && intval($product['product_id']) > 0 && isset($product['subscription_period']) && $product['subscription_period'] != '') {
                 // product is a subscription?
                 $woo_sub_key = WC_Subscriptions_Manager::get_subscription_key($this->order_id, $product['product_id']);
                 // required vars
                 $amount = floatval(WC_Subscriptions_Order::get_recurring_total($this->order)) * 100;
                 $currency = get_woocommerce_currency();
                 $interval = intval($product['subscription_interval']);
                 $period = strtoupper($product['subscription_period']);
                 $length = strtoupper($product['subscription_length']);
                 if ($length > 0) {
                     $periodOfValidity = $length . ' ' . $period;
                 } else {
                     $periodOfValidity = false;
                 }
                 $trial_end = strtotime(WC_Subscriptions_Product::get_trial_expiration_date($product['product_id'], get_gmt_from_date($this->order->order_date)));
                 if ($trial_end === false) {
                     $trial_time = 0;
                 } else {
                     $datediff = $trial_end - time();
                     $trial_time = ceil($datediff / (60 * 60 * 24));
                 }
                 // md5 name
                 $woo_sub_md5 = md5($amount . $currency . $interval . $trial_time);
                 // get offer
                 $name = 'woo_' . $product['product_id'] . '_' . $woo_sub_md5;
                 $offer = $this->subscriptions->offerGetDetailByName($name);
                 // check wether offer exists in paymill
                 if ($offer === false) {
                     // offer does not exist in paymill yet, create it
                     $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time));
                     $offer = $this->subscriptions->offerCreate($params);
                     if ($GLOBALS['paymill_loader']->paymill_errors->status()) {
                         $GLOBALS['paymill_loader']->paymill_errors->getErrors();
                         return false;
                     }
                 }
                 // create user subscription
                 $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity);
                 if ($GLOBALS['paymill_loader']->paymill_errors->status()) {
                     //maybe offer cache is outdated, recache and try again
                     $GLOBALS['paymill_loader']->paymill_errors->reset();
                     // reset error status
                     $this->subscriptions->offerGetList(true);
                     $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time));
                     $offer = $this->subscriptions->offerCreate($params);
                     if ($GLOBALS['paymill_loader']->paymill_errors->status()) {
                         $GLOBALS['paymill_loader']->paymill_errors->getErrors();
                         return false;
                     }
                     $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity);
                     if ($GLOBALS['paymill_loader']->paymill_errors->status()) {
                         $GLOBALS['paymill_loader']->paymill_errors->getErrors();
                         return false;
                     }
                 }
                 $wpdb->query($wpdb->prepare('INSERT INTO ' . $wpdb->prefix . 'paymill_subscriptions (paymill_sub_id, woo_user_id, woo_offer_id) VALUES (%s, %s, %s)', array($user_sub, get_current_user_id(), $woo_sub_key)));
                 // subscription successful
                 do_action('paymill_woocommerce_subscription_created', array('product_id' => $product['product_id'], 'offer_id' => $offer));
                 return true;
             }
         }
     } else {
         return true;
     }
 }
 /**
  * Uses the details of an order to create a pending subscription on the customers account
  * for a subscription product, as specified with $product_id.
  *
  * @param int|WC_Order $order The order ID or WC_Order object to create the subscription from.
  * @param int $product_id The ID of the subscription product on the order.
  * @param array $args An array of name => value pairs to customise the details of the subscription, including:
  * 			'start_date' A MySQL formatted date/time string on which the subscription should start, in UTC timezone
  * 			'expiry_date' A MySQL formatted date/time string on which the subscription should expire, in UTC timezone
  * @since 1.1
  */
 public static function create_pending_subscription_for_order($order, $product_id, $args = array())
 {
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     if (!WC_Subscriptions_Product::is_subscription($product_id)) {
         return;
     }
     $args = wp_parse_args($args, array('start_date' => '', 'expiry_date' => ''));
     $subscription_key = self::get_subscription_key($order->id, $product_id);
     // In case the subscription exists already
     $subscription = self::get_subscription($subscription_key);
     if (!empty($subscription['variation_id'])) {
         $product_id = $subscription['variation_id'];
     } elseif (!empty($subscription['product_id'])) {
         $product_id = $subscription['product_id'];
     }
     // Adding a new subscription so set the start date/time to now
     if (!empty($args['start_date'])) {
         if (is_numeric($args['start_date'])) {
             $args['start_date'] = date('Y-m-d H:i:s', $args['start_date']);
         }
         $start_date = $args['start_date'];
     } else {
         $start_date = !empty($subscription['start_date']) ? $subscription['start_date'] : gmdate('Y-m-d H:i:s');
     }
     // Adding a new subscription so set the expiry date/time from the order date
     if (!empty($args['expiry_date'])) {
         if (is_numeric($args['expiry_date'])) {
             $args['expiry_date'] = date('Y-m-d H:i:s', $args['expiry_date']);
         }
         $expiration = $args['expiry_date'];
     } else {
         $expiration = !empty($subscription['expiry_date']) ? $subscription['expiry_date'] : WC_Subscriptions_Product::get_expiration_date($product_id, $start_date);
     }
     // Adding a new subscription so set the expiry date/time from the order date
     $trial_expiration = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, $start_date);
     $failed_payments = !empty($subscription['failed_payments']) ? $subscription['failed_payments'] : 0;
     $completed_payments = !empty($subscription['completed_payments']) ? $subscription['completed_payments'] : array();
     $order_item_id = WC_Subscriptions_Order::get_item_id_by_subscription_key($subscription_key);
     // Store the subscription details in item meta
     woocommerce_add_order_item_meta($order_item_id, '_subscription_start_date', $start_date, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_expiry_date', $expiration, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_trial_expiry_date', $trial_expiration, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_failed_payments', $failed_payments, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_completed_payments', $completed_payments, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_status', 'pending', true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_end_date', 0, true);
     woocommerce_add_order_item_meta($order_item_id, '_subscription_suspension_count', 0, true);
     $product = WC_Subscriptions::get_product($product_id);
     // Set subscription status to active and log activation
     $order->add_order_note(sprintf(__('Pending subscription created for "%s".', 'woocommerce-subscriptions'), $product->get_title()));
     do_action('pending_subscription_created_for_order', $order, $product_id);
 }
 /**
  * Uses the details of an order to create a pending subscription on the customers account
  * for a subscription product, as specified with $product_id.
  *
  * @param int|WC_Order $order The order ID or WC_Order object to create the subscription from.
  * @param int $product_id The ID of the subscription product on the order, if a variation, it must be the variation's ID.
  * @param array $args An array of name => value pairs to customise the details of the subscription, including:
  * 			'start_date' A MySQL formatted date/time string on which the subscription should start, in UTC timezone
  * 			'expiry_date' A MySQL formatted date/time string on which the subscription should expire, in UTC timezone
  * @since 1.1
  */
 public static function create_pending_subscription_for_order($order, $product_id, $args = array())
 {
     _deprecated_function(__METHOD__, '2.0', 'wcs_create_subscription()');
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     if (!WC_Subscriptions_Product::is_subscription($product_id)) {
         return;
     }
     $args = wp_parse_args($args, array('start_date' => get_gmt_from_date($order->order_date), 'expiry_date' => ''));
     $billing_period = WC_Subscriptions_Product::get_period($product_id);
     $billing_interval = WC_Subscriptions_Product::get_interval($product_id);
     // Support passing timestamps
     $args['start_date'] = is_numeric($args['start_date']) ? date('Y-m-d H:i:s', $args['start_date']) : $args['start_date'];
     $product = wc_get_product($product_id);
     // Check if there is already a subscription for this product and order
     $subscriptions = wcs_get_subscriptions(array('order_id' => $order->id, 'product_id' => $product_id));
     if (!empty($subscriptions)) {
         $subscription = array_pop($subscriptions);
         // Make sure the subscription is pending and start date is set correctly
         wp_update_post(array('ID' => $subscription->id, 'post_status' => 'wc-' . apply_filters('woocommerce_default_subscription_status', 'pending'), 'post_date' => get_date_from_gmt($args['start_date'])));
     } else {
         $subscription = wcs_create_subscription(array('start_date' => get_date_from_gmt($args['start_date']), 'order_id' => $order->id, 'customer_id' => $order->get_user_id(), 'billing_period' => $billing_period, 'billing_interval' => $billing_interval, 'customer_note' => $order->customer_note));
         if (is_wp_error($subscription)) {
             throw new Exception(__('Error: Unable to create subscription. Please try again.', 'woocommerce-subscriptions'));
         }
         $item_id = $subscription->add_product($product, 1, array('variation' => method_exists($product, 'get_variation_attributes') ? $product->get_variation_attributes() : array(), 'totals' => array('subtotal' => $product->get_price(), 'subtotal_tax' => 0, 'total' => $product->get_price(), 'tax' => 0, 'tax_data' => array('subtotal' => array(), 'total' => array()))));
         if (!$item_id) {
             throw new Exception(__('Error: Unable to add product to created subscription. Please try again.', 'woocommerce-subscriptions'));
         }
     }
     // Make sure some of the meta is copied form the order rather than the store's defaults
     update_post_meta($subscription->id, '_order_currency', $order->order_currency);
     update_post_meta($subscription->id, '_prices_include_tax', $order->prices_include_tax);
     // Adding a new subscription so set the expiry date/time from the order date
     if (!empty($args['expiry_date'])) {
         if (is_numeric($args['expiry_date'])) {
             $args['expiry_date'] = date('Y-m-d H:i:s', $args['expiry_date']);
         }
         $expiration = $args['expiry_date'];
     } else {
         $expiration = WC_Subscriptions_Product::get_expiration_date($product_id, $args['start_date']);
     }
     // Adding a new subscription so set the expiry date/time from the order date
     $trial_expiration = WC_Subscriptions_Product::get_trial_expiration_date($product_id, $args['start_date']);
     $dates_to_update = array();
     if ($trial_expiration > 0) {
         $dates_to_update['trial_end'] = $trial_expiration;
     }
     if ($expiration > 0) {
         $dates_to_update['end'] = $expiration;
     }
     if (!empty($dates_to_update)) {
         $subscription->update_dates($dates_to_update);
     }
     // Set the recurring totals on the subscription
     $subscription->set_total(0, 'tax');
     $subscription->set_total($product->get_price(), 'total');
     $subscription->add_order_note(__('Pending subscription created.', 'woocommerce-subscriptions'));
     do_action('pending_subscription_created_for_order', $order, $product_id);
 }
 /**
  * Takes a subscription product's ID and calculates the date on which the next payment is due.
  *
  * Calculation is based on $from_date if specified, otherwise it will fall back to the last
  * completed payment, the subscription's start time, or the current date/time, in that order.
  *
  * The next payment date will occur after any free trial period and up to any expiration date.
  *
  * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in.
  * @param int $product_id The product/post ID of the subscription
  * @param string $type (optional) The format for the Either 'mysql' or 'timestamp'.
  * @param mixed $from_date A MySQL formatted date/time string from which to calculate the next payment date, or empty (default), which will use the last payment on the subscription, or today's date/time if no previous payments have been made.
  * @return mixed If there is no future payment set, returns 0, otherwise it will return a date of the next payment in the form specified by $type
  * @since 1.0
  */
 public static function calculate_next_payment_date($order, $product_id, $type = 'mysql', $from_date = '')
 {
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     $from_date_arg = $from_date;
     $subscription = WC_Subscriptions_Manager::get_subscription(WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id));
     $subscription_period = self::get_subscription_period($order, $product_id);
     $subscription_interval = self::get_subscription_interval($order, $product_id);
     $subscription_trial_length = self::get_subscription_trial_length($order, $product_id);
     $subscription_trial_period = self::get_subscription_trial_period($order, $product_id);
     $subscription_length = self::get_subscription_length($order, $product_id);
     $trial_end_time = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, get_gmt_from_date($order->order_date));
     $trial_end_time = strtotime($trial_end_time);
     // If the subscription is not active, there is no next payment date
     if ($subscription['status'] != 'active' || $subscription_interval == $subscription_length) {
         $next_payment_timestamp = 0;
         // If the subscription has a free trial period, and we're still in the free trial period, the next payment is due at the end of the free trial
     } elseif ($subscription_trial_length > 0 && $trial_end_time > gmdate('U') + 60 * 60 * 23 + 120) {
         // Make sure trial expiry is more than 23+ hours in the future to account for trial expiration dates incorrectly stored in non-UTC/GMT timezone and also for any potential changes to the site's timezone
         $next_payment_timestamp = $trial_end_time;
         // The next payment date is {interval} billing periods from the from date
     } else {
         // We have a timestamp
         if (!empty($from_date) && is_numeric($from_date)) {
             $from_date = date('Y-m-d H:i:s', $from_date);
         }
         if (empty($from_date)) {
             if (!empty($subscription['completed_payments'])) {
                 $from_date = array_pop($subscription['completed_payments']);
                 $add_failed_payments = true;
             } else {
                 if (!empty($subscription['start_date'])) {
                     $from_date = $subscription['start_date'];
                     $add_failed_payments = true;
                 } else {
                     $from_date = gmdate('Y-m-d H:i:s');
                     $add_failed_payments = false;
                 }
             }
             $failed_payment_count = self::get_failed_payment_count($order, $product_id);
             // Maybe take into account any failed payments
             if (true === $add_failed_payments && $failed_payment_count > 0) {
                 $failed_payment_periods = $failed_payment_count * $subscription_interval;
                 $from_timestamp = strtotime($from_date);
                 if ('month' == $subscription_period) {
                     $from_date = date('Y-m-d H:i:s', WC_Subscriptions::add_months($from_timestamp, $failed_payment_periods));
                 } else {
                     // Safe to just add the billing periods
                     $from_date = date('Y-m-d H:i:s', strtotime("+ {$failed_payment_periods} {$subscription_period}", $from_timestamp));
                 }
             }
         }
         $from_timestamp = strtotime($from_date);
         if ('month' == $subscription_period) {
             // Workaround potential PHP issue
             $next_payment_timestamp = WC_Subscriptions::add_months($from_timestamp, $subscription_interval);
         } else {
             $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $from_timestamp);
         }
         // Make sure the next payment is in the future
         $i = 1;
         while ($next_payment_timestamp < gmdate('U') && $i < 30) {
             if ('month' == $subscription_period) {
                 $next_payment_timestamp = WC_Subscriptions::add_months($next_payment_timestamp, $subscription_interval);
             } else {
                 // Safe to just add the billing periods
                 $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $next_payment_timestamp);
             }
             $i = $i + 1;
         }
     }
     // If the subscription has an expiry date and the next billing period comes after the expiration, return 0
     if (isset($subscription['expiry_date']) && 0 != $subscription['expiry_date'] && $next_payment_timestamp + 120 > strtotime($subscription['expiry_date'])) {
         $next_payment_timestamp = 0;
     }
     $next_payment = 'mysql' == $type && 0 != $next_payment_timestamp ? date('Y-m-d H:i:s', $next_payment_timestamp) : $next_payment_timestamp;
     return apply_filters('woocommerce_subscriptions_calculated_next_payment_date', $next_payment, $order, $product_id, $type, $from_date, $from_date_arg);
 }
 /**
  * Maybe recalculate the trial end date for synced subscription products that contain the unnecessary
  * "one day trial" period.
  *
  * @since 2.0
  * @deprecated 2.0.14
  */
 public static function recalculate_trial_end_date($trial_end_date, $recurring_cart, $product)
 {
     _deprecated_function(__METHOD__, '2.0.14');
     if (self::is_product_synced($product)) {
         $product_id = isset($product->variation_id) ? $product->variation_id : $product->id;
         $trial_end_date = WC_Subscriptions_Product::get_trial_expiration_date($product_id);
     }
     return $trial_end_date;
 }
Exemplo n.º 8
0
 /**
  * Calculate the initial and recurring totals for all subscription products in the cart.
  *
  * We need to group subscriptions by billing schedule to make the display and creation of recurring totals sane,
  * when there are multiple subscriptions in the cart. To do that, we use an array with keys of the form:
  * '{billing_interval}_{billing_period}_{trial_interval}_{trial_period}_{length}_{billing_period}'. This key
  * is used to reference WC_Cart objects for each recurring billing schedule and these are stored in the master
  * cart with the billing schedule key.
  *
  * After we have calculated and grouped all recurring totals, we need to checks the structure of the subscription
  * product prices to see whether they include sign-up fees and/or free trial periods and then recalculates the
  * appropriate totals by using the @see self::$calculation_type flag and cloning the cart to run @see WC_Cart::calculate_totals()
  *
  * @since 1.3.5
  * @version 2.0
  */
 public static function calculate_subscription_totals($total, $cart)
 {
     if (!self::cart_contains_subscription() && !wcs_cart_contains_resubscribe()) {
         // cart doesn't contain subscription
         return $total;
     } elseif ('none' != self::$calculation_type) {
         // We're in the middle of a recalculation, let it run
         return $total;
     }
     // Save the original cart values/totals, as we'll use this when there is no sign-up fee
     WC()->cart->total = $total < 0 ? 0 : $total;
     do_action('woocommerce_subscription_cart_before_grouping');
     $subscription_groups = array();
     // Group the subscription items by their cart item key based on billing schedule
     foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
         if (WC_Subscriptions_Product::is_subscription($cart_item['data'])) {
             $subscription_groups[self::get_recurring_cart_key($cart_item)][] = $cart_item_key;
         }
     }
     do_action('woocommerce_subscription_cart_after_grouping');
     $recurring_carts = array();
     // Now let's calculate the totals for each group of subscriptions
     self::$calculation_type = 'recurring_total';
     foreach ($subscription_groups as $recurring_cart_key => $subscription_group) {
         // Create a clone cart to calculate and store totals for this group of subscriptions
         $recurring_cart = clone WC()->cart;
         $product = null;
         // Remove any items not in this subscription group
         foreach ($recurring_cart->get_cart() as $cart_item_key => $cart_item) {
             if (!in_array($cart_item_key, $subscription_group)) {
                 unset($recurring_cart->cart_contents[$cart_item_key]);
                 continue;
             }
             if (null === $product) {
                 $product = $cart_item['data'];
             }
         }
         $recurring_cart->start_date = apply_filters('wcs_recurring_cart_start_date', gmdate('Y-m-d H:i:s'), $recurring_cart);
         $recurring_cart->trial_end_date = apply_filters('wcs_recurring_cart_trial_end_date', WC_Subscriptions_Product::get_trial_expiration_date($product, $recurring_cart->start_date), $recurring_cart, $product);
         $recurring_cart->next_payment_date = apply_filters('wcs_recurring_cart_next_payment_date', WC_Subscriptions_Product::get_first_renewal_payment_date($product, $recurring_cart->start_date), $recurring_cart, $product);
         $recurring_cart->end_date = apply_filters('wcs_recurring_cart_end_date', WC_Subscriptions_Product::get_expiration_date($product, $recurring_cart->start_date), $recurring_cart, $product);
         // No fees recur (yet)
         $recurring_cart->fees = array();
         $recurring_cart->fee_total = 0;
         WC()->shipping->reset_shipping();
         self::maybe_recalculate_shipping();
         $recurring_cart->calculate_totals();
         // Store this groups cart details
         $recurring_carts[$recurring_cart_key] = clone $recurring_cart;
         // And remove some other floatsam
         $recurring_carts[$recurring_cart_key]->removed_cart_contents = array();
         $recurring_carts[$recurring_cart_key]->cart_session_data = array();
     }
     self::$calculation_type = 'none';
     // We need to reset the packages and totals stored in WC()->shipping too
     self::maybe_recalculate_shipping();
     WC()->cart->calculate_shipping();
     // If there is no sign-up fee and a free trial, and no products being purchased with the subscription, we need to zero the fees for the first billing period
     if (0 == self::get_cart_subscription_sign_up_fee() && self::all_cart_items_have_free_trial()) {
         foreach (WC()->cart->get_fees() as $fee_index => $fee) {
             WC()->cart->fees[$fee_index]->amount = 0;
             WC()->cart->fees[$fee_index]->tax = 0;
         }
         WC()->cart->fee_total = 0;
     }
     WC()->cart->recurring_carts = $recurring_carts;
     $total = max(0, round(WC()->cart->cart_contents_total + WC()->cart->tax_total + WC()->cart->shipping_tax_total + WC()->cart->shipping_total + WC()->cart->fee_total, WC()->cart->dp));
     if (isset(WC()->cart->discount_total) && 0 !== WC()->cart->discount_total) {
         // WC < 2.3, deduct deprecated after tax discount total
         $total = max(0, round($total - WC()->cart->discount_total, WC()->cart->dp));
     }
     if (!self::charge_shipping_up_front()) {
         $total = max(0, $total - WC()->cart->shipping_tax_total - WC()->cart->shipping_total);
         WC()->cart->shipping_taxes = array();
         WC()->cart->shipping_tax_total = 0;
         WC()->cart->shipping_total = 0;
     }
     return apply_filters('woocommerce_subscriptions_calculated_total', $total);
 }
 /**
  * Records the initial payment against a subscription.
  *
  * This function is called when an orders status is changed to completed or processing
  * for those gateways which never call @see WC_Order::payment_complete(), like the core
  * WooCommerce Cheque and Bank Transfer gateways.
  *
  * It will also set the start date on the subscription to the time the payment is completed.
  *
  * @param $order_id int|WC_Order
  * @param $old_order_status
  * @param $new_order_status
  * @since 2.0
  */
 public static function maybe_record_subscription_payment($order_id, $old_order_status, $new_order_status)
 {
     if (wcs_order_contains_subscription($order_id)) {
         $subscriptions = wcs_get_subscriptions_for_order($order_id);
         $was_activated = false;
         $order_completed = in_array($new_order_status, array(apply_filters('woocommerce_payment_complete_order_status', 'processing', $order_id), 'processing', 'completed')) && in_array($old_order_status, apply_filters('woocommerce_valid_order_statuses_for_payment', array('pending', 'on-hold', 'failed')));
         foreach ($subscriptions as $subscription) {
             // Do we need to activate a subscription?
             if ($order_completed && !$subscription->has_status(wcs_get_subscription_ended_statuses()) && !$subscription->has_status('active')) {
                 $new_start_date_offset = current_time('timestamp', true) - $subscription->get_time('start');
                 // if the payment has been processed more than an hour after the order was first created, let's update the dates on the subscription to account for that, because it may have even been processed days after it was first placed
                 if ($new_start_date_offset > HOUR_IN_SECONDS) {
                     $dates = array('start' => current_time('mysql', true));
                     if (WC_Subscriptions_Synchroniser::subscription_contains_synced_product($subscription)) {
                         $trial_end = $subscription->get_time('trial_end');
                         $next_payment = $subscription->get_time('next_payment');
                         // if either there is a free trial date or a next payment date that falls before now, we need to recalculate all the sync'd dates
                         if ($trial_end > 0 && $trial_end < strtotime($dates['start']) || $next_payment > 0 && $next_payment < strtotime($dates['start'])) {
                             foreach ($subscription->get_items() as $item) {
                                 $product_id = wcs_get_canonical_product_id($item);
                                 if (WC_Subscriptions_Synchroniser::is_product_synced($product_id)) {
                                     $dates['trial_end'] = WC_Subscriptions_Product::get_trial_expiration_date($product_id, $dates['start']);
                                     $dates['next_payment'] = WC_Subscriptions_Synchroniser::calculate_first_payment_date($product_id, 'mysql', $dates['start']);
                                     $dates['end'] = WC_Subscriptions_Product::get_expiration_date($product_id, $dates['start']);
                                     break;
                                 }
                             }
                         }
                     } else {
                         // No sync'ing to mess about with, just add the offset to the existing dates
                         foreach (array('trial_end', 'next_payment', 'end') as $date_type) {
                             if (0 != $subscription->get_time($date_type)) {
                                 $dates[$date_type] = gmdate('Y-m-d H:i:s', $subscription->get_time($date_type) + $new_start_date_offset);
                             }
                         }
                     }
                     $subscription->update_dates($dates);
                 }
                 $subscription->payment_complete();
                 $was_activated = true;
             } elseif ('failed' == $new_order_status) {
                 $subscription->payment_failed();
             }
         }
         if ($was_activated) {
             do_action('subscriptions_activated_for_order', $order_id);
         }
     }
 }
 /**
  * Takes a subscription product's ID and calculates the date on which the next payment is due.
  *
  * Calculation is based on $from_date if specified, otherwise it will fall back to the last
  * completed payment, the subscription's start time, or the current date/time, in that order.
  *
  * The next payment date will occur after any free trial period and up to any expiration date.
  *
  * @param $order mixed A WC_Order object or the ID of the order which the subscription was purchased in.
  * @param $product_id int The product/post ID of the subscription
  * @param $type string (optional) The format for the Either 'mysql' or 'timestamp'.
  * @param $from_date mixed A MySQL formatted date/time string from which to calculate the next payment date, or empty (default), which will use the last payment on the subscription, or today's date/time if no previous payments have been made.
  * @return mixed If there is no future payment set, returns 0, otherwise it will return a date of the next payment in the form specified by $type
  * @since 1.0
  */
 public static function calculate_next_payment_date($order, $product_id, $type = 'mysql', $from_date = '')
 {
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     $subscription = WC_Subscriptions_Manager::get_users_subscription($order->user_id, WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id));
     $subscription_period = self::get_subscription_period($order, $product_id);
     $subscription_interval = self::get_subscription_interval($order, $product_id);
     $subscription_trial_length = self::get_subscription_trial_length($order, $product_id);
     $subscription_trial_period = self::get_subscription_trial_period($order, $product_id);
     $trial_end_time = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, $order->order_date);
     $trial_end_time = strtotime($trial_end_time);
     // If the subscription is not active, there is no next payment date
     if ($subscription['status'] != 'active') {
         $next_payment_timestamp = 0;
         // If the subscription has a free trial period, and we're still in the free trial period, the next payment is due at the end of the free trial
     } elseif ($subscription_trial_length > 0 && $trial_end_time > time() + 120) {
         $next_payment_timestamp = $trial_end_time;
         // The next payment date is {interval} billing periods from the from date
     } else {
         // We have a timestamp
         if (!empty($from_date) && is_numeric($from_date)) {
             $from_date = date('Y-m-d H:i:s', $from_date);
         }
         if (empty($from_date)) {
             if (!empty($subscription['completed_payments'])) {
                 $from_date = array_pop($subscription['completed_payments']);
                 $add_failed_payments = true;
             } else {
                 if (!empty($subscription['start_date'])) {
                     $from_date = $subscription['start_date'];
                     $add_failed_payments = true;
                 } else {
                     $from_date = date('Y-m-d H:i:s');
                     $add_failed_payments = false;
                 }
             }
             $failed_payment_count = self::get_failed_payment_count($order, $product_id);
             // Maybe take into account any failed payments
             if (true === $add_failed_payments && $failed_payment_count > 0) {
                 $failed_payment_periods = $failed_payment_count * $subscription_interval;
                 $from_date = date('Y-m-d H:i:s', strtotime("+ {$failed_payment_periods} {$subscription_period}", strtotime($from_date)));
             }
         }
         $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", strtotime($from_date));
         // Make sure the next payment is in the future
         while ($next_payment_timestamp < time()) {
             $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $next_payment_timestamp);
         }
     }
     // If the subscription has an expiry date and the next billing period comes after the expiration, return 0
     if (isset($subscription['expiry_date']) && 0 != $subscription['expiry_date'] && $next_payment_timestamp > strtotime($subscription['expiry_date'])) {
         $next_payment_timestamp = 0;
     }
     $next_payment = 'mysql' == $type && 0 != $next_payment_timestamp ? date('Y-m-d H:i:s', $next_payment_timestamp) : $next_payment_timestamp;
     return apply_filters('woocommerce_subscriptions_calculated_next_payment_date', $next_payment, $order, $product_id, $type, $from_date);
 }