/** * Make sure the expiration date is calculated from the synced start date for products where the start date * will be synced. * * @param string $expiration_date MySQL formatted date on which the subscription is set to expire * @param mixed $product_id The product/post ID of the subscription * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time. * @since 1.5 */ public static function recalculate_product_expiration_date($expiration_date, $product_id, $from_date) { if (self::is_product_synced($product_id) && ($subscription_length = WC_Subscriptions_Product::get_length($product_id)) > 0) { $subscription_period = WC_Subscriptions_Product::get_period($product_id); $first_payment_date = self::calculate_first_payment_date($product_id, 'timestamp'); $expiration_date = date('Y-m-d H:i:s', wcs_add_time($subscription_length, $subscription_period, $first_payment_date)); } return $expiration_date; }
/** * '_subscription_end_date': if the subscription has a '_subscription_length' value and status of expired, the length can be used to * calculate the end date as it will be the same as the expiration date. If no length is set, or the subscription has a cancelled status, * some time within 24 hours after the last renewal order's date can be used to provide a rough estimate. * * @param array $subscription data about the subscription * @param numeric $item_id the id of the product we're missing variation id for * @param array $item_meta meta data about the product * @return array repaired data about the subscription */ public static function repair_end_date($subscription, $item_id, $item_meta) { $subscription = self::repair_from_item_meta($subscription, $item_id, $item_meta, 'end_date', '_subscription_end_date', ''); if ('' !== $subscription['end_date']) { return $subscription; } if ('expired' == $subscription['status'] && array_key_exists('expiry_date', $subscription) && !empty($subscription['expiry_date'])) { $subscription['end_date'] = $subscription['expiry_date']; } elseif ('cancelled' == $subscription['status'] || !array_key_exists('length', $subscription) || empty($subscription['length'])) { // get renewal orders $renewal_orders = self::get_renewal_orders($subscription); $last_order = array_shift($renewal_orders); if (empty($last_order)) { $subscription['end_date'] = 0; } else { $subscription['end_date'] = wcs_add_time(5, 'hours', strtotime($last_order->order_date)); } } else { // if everything failed, let's have an empty one $subscription['end_date'] = 0; } return $subscription; }
/** * Determine if the subscription is for one payment only. * * @return bool whether the subscription is for only one payment * @since 2.0.17 */ public function is_one_payment() { $is_one_payment = false; if (0 != ($end_time = $this->get_time('end'))) { $from_timestamp = $this->get_time('start'); if (0 != $this->get_time('trial_end') || WC_Subscriptions_Synchroniser::subscription_contains_synced_product($this)) { $subscription_order_count = count($this->get_related_orders()); // when we have a sync'd subscription before its 1st payment, we need to base the calculations for the next payment on the first/next payment timestamp. if ($subscription_order_count < 2 && 0 != ($next_payment_timestamp = $this->get_time('next_payment'))) { $from_timestamp = $next_payment_timestamp; // when we have a sync'd subscription after its 1st payment, we need to base the calculations for the next payment on the last payment timestamp. } else { if (!($subscription_order_count > 2) && 0 != ($last_payment_timestamp = $this->get_time('last_payment'))) { $from_timestamp = $last_payment_timestamp; } } } $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $from_timestamp); if ($next_payment_timestamp + DAY_IN_SECONDS - 1 > $end_time) { $is_one_payment = true; } } return apply_filters('woocommerce_subscription_is_one_payment', $is_one_payment, $this); }
/** * Calculates the next payment date for a subscription. * * Although an inactive subscription does not have a next payment date, this function will still calculate the date * so that it can be used to determine the date the next payment should be charged for inactive subscriptions. * * @return int | string Zero if the subscription has no next payment date, or a MySQL formatted date time if there is a next payment date */ protected function calculate_next_payment_date() { $next_payment_date = 0; // If the subscription is not active, there is no next payment date $start_time = $this->get_time('start'); $next_payment_time = $this->get_time('next_payment'); $trial_end_time = $this->get_time('trial_end'); $last_payment_time = $this->get_time('last_payment'); $end_time = $this->get_time('end'); // 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 if ($trial_end_time > current_time('timestamp', true)) { $next_payment_timestamp = $trial_end_time; } else { // The next payment date is {interval} billing periods from the start date, trial end date or last payment date if (0 !== $next_payment_time && $next_payment_time < gmdate('U') && 1 <= $this->get_completed_payment_count()) { $from_timestamp = $next_payment_time; } elseif ($last_payment_time > $start_time && apply_filters('wcs_calculate_next_payment_from_last_payment', true, $this)) { $from_timestamp = $last_payment_time; } elseif ($next_payment_time > $start_time) { // Use the currently scheduled next payment to preserve synchronisation $from_timestamp = $next_payment_time; } else { $from_timestamp = $start_time; } $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $from_timestamp); // Make sure the next payment is in the future $i = 1; while ($next_payment_timestamp < current_time('timestamp', true) && $i < 30) { $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $next_payment_timestamp); $i += 1; } } // If the subscription has an end date and the next billing period comes after that, return 0 if (0 != $end_time && $next_payment_timestamp + 120 > $end_time) { $next_payment_timestamp = 0; } if ($next_payment_timestamp > 0) { $next_payment_date = date('Y-m-d H:i:s', $next_payment_timestamp); } return $next_payment_date; }
/** * Takes a subscription product's ID and returns the date on which the subscription product will expire, * based on the subscription's length and calculated from either the $from_date if specified, or the current date/time. * * @param mixed $product_id The product/post ID of the subscription * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time. * @since 1.0 */ public static function get_expiration_date($product_id, $from_date = '') { $subscription_length = self::get_length($product_id); if ($subscription_length > 0) { if (empty($from_date)) { $from_date = gmdate('Y-m-d H:i:s'); } if (self::get_trial_length($product_id) > 0) { $from_date = self::get_trial_expiration_date($product_id, $from_date); } $expiration_date = gmdate('Y-m-d H:i:s', wcs_add_time($subscription_length, self::get_period($product_id), strtotime($from_date))); } else { $expiration_date = 0; } return apply_filters('woocommerce_subscriptions_product_expiration_date', $expiration_date, $product_id, $from_date); }