/** * Bootstraps the class and hooks required actions & filters. * * @since 1.0 */ public static function init() { // Generate an order to keep a record of each subscription payment add_action('processed_subscription_payment', __CLASS__ . '::generate_paid_renewal_order', 10, 2); add_action('processed_subscription_payment_failure', __CLASS__ . '::generate_failed_payment_renewal_order', 10, 2); // If a subscription requires manual payment, generate an order to accept the payment add_action('scheduled_subscription_payment', __CLASS__ . '::maybe_generate_manual_renewal_order', 10, 2); // Make sure *manual* payment on a renewal order is correctly processed add_action('woocommerce_payment_complete', __CLASS__ . '::maybe_record_renewal_order_payment', 10, 1); // Make sure *manual* payment on renewal orders is correctly processed for gateways that do not call WC_Order::payment_complete() add_action('woocommerce_order_status_on-hold_to_processing', __CLASS__ . '::maybe_record_renewal_order_payment', 10, 1); add_action('woocommerce_order_status_on-hold_to_completed', __CLASS__ . '::maybe_record_renewal_order_payment', 10, 1); // Make sure payment on renewal orders is correctly processed when the *automatic* payment had previously failed add_action('woocommerce_order_status_failed_to_processing', __CLASS__ . '::process_failed_renewal_order_payment', 10, 1); add_action('woocommerce_order_status_failed_to_completed', __CLASS__ . '::process_failed_renewal_order_payment', 10, 1); add_action('woocommerce_order_status_failed', __CLASS__ . '::maybe_record_renewal_order_payment_failure', 10, 1); // Check if a user is requesting to create a renewal order for a subscription add_action('wp_loaded', __CLASS__ . '::maybe_create_renewal_order_for_user', 100); // Used to detect if payment on failed order is being made from 'My Account' add_action('before_woocommerce_pay', __CLASS__ . '::before_woocommerce_pay', 10); // To handle virtual/non-downloaded that require store manager approval add_action('woocommerce_payment_complete', __CLASS__ . '::maybe_process_failed_renewal_order_payment', 10, 1); add_action('woocommerce_order_status_on-hold_to_processing', __CLASS__ . '::maybe_process_failed_renewal_order_payment', 10, 1); add_action('woocommerce_order_status_on-hold_to_completed', __CLASS__ . '::maybe_process_failed_renewal_order_payment', 10, 1); // Add a renewal orders section to the Related Orders meta box add_action('woocommerce_subscriptions_related_orders_meta_box', __CLASS__ . '::renewal_orders_meta_box_section', 10, 2); // Allow renewal of limited subscriptions add_filter('woocommerce_subscription_is_purchasable', __CLASS__ . '::is_purchasable', 12, 2); add_filter('woocommerce_subscription_variation_is_purchasable', __CLASS__ . '::is_purchasable', 12, 2); add_action('woocommerce_payment_complete', __CLASS__ . '::trigger_renewal_payment_complete', 10); add_filter('woocommerce_get_checkout_payment_url', __CLASS__ . '::get_checkout_payment_url', 10, 2); add_filter('woocommerce_product_addons_adjust_price', __CLASS__ . '::product_addons_adjust_price', 10, 2); // The notice displayed when a subscription product has been deleted and the custoemr attempts to manually renew or make a renewal payment for a failed recurring payment for that product/subscription self::$product_deleted_error_message = apply_filters('woocommerce_subscriptions_renew_deleted_product_error_message', __('That product has been deleted and can no longer be renewed. Please choose a new product or contact us for assistance.', 'woocommerce-subscriptions')); }
/** * When creating an order at checkout, if the order is for renewing a subscription from a failed * payment, hijack the order creation to make a renewal order - not a vanilla WooCommerce order. * * @since 1.3 */ public static function filter_woocommerce_create_order($order_id, $checkout_object) { global $woocommerce; $cart_item = WC_Subscriptions_Cart::cart_contains_subscription_renewal(); if ($cart_item && 'child' == $cart_item['subscription_renewal']['role']) { $product_id = $cart_item['product_id']; $failed_order_id = $cart_item['subscription_renewal']['failed_order']; $original_order_id = $cart_item['subscription_renewal']['original_order']; $role = $cart_item['subscription_renewal']['role']; remove_action('woocommerce_subscriptions_renewal_order_created', 'WC_Subscriptions_Renewal_Order::maybe_send_customer_renewal_order_email', 10, 1); $renewal_order_args = array('new_order_role' => $role, 'checkout_renewal' => true, 'failed_order_id' => $failed_order_id); $order_id = WC_Subscriptions_Renewal_Order::generate_renewal_order($original_order_id, $product_id, $renewal_order_args); if ($checkout_object->posted['payment_method']) { $available_gateways = $woocommerce->payment_gateways->get_available_payment_gateways(); if (isset($available_gateways[$checkout_object->posted['payment_method']])) { $payment_method = $available_gateways[$checkout_object->posted['payment_method']]; $payment_method->validate_fields(); update_post_meta($order_id, '_payment_method', $payment_method->id); update_post_meta($order_id, '_payment_method_title', $payment_method->get_title()); } } if ($checkout_object->posted['shipping_method']) { $available_shipping_methods = $woocommerce->shipping->get_available_shipping_methods(); if (isset($available_shipping_methods[$checkout_object->posted['shipping_method']])) { $shipping_method = $available_shipping_methods[$checkout_object->posted['shipping_method']]; update_post_meta($order_id, '_shipping_method', $shipping_method->id); update_post_meta($order_id, '_shipping_method_title', $shipping_method->label); } } if (isset($failed_order_id)) { $failed_order = new WC_Order($failed_order_id); if ($failed_order->status == 'failed') { update_post_meta($failed_order_id, '_failed_order_replaced_by', $order_id); } } } return $order_id; }
/** * Registers the "Renewal Orders" meta box for the "Edit Order" page. */ public static function add_related_orders_meta_box() { global $current_screen, $post_id; // Only display the meta box if an order relates to a subscription if ('shop_order' == $current_screen->id && (WC_Subscriptions_Renewal_Order::is_renewal($post_id, array('order_role' => 'child')) || WC_Subscriptions_Order::order_contains_subscription($post_id))) { add_meta_box('subscription_renewal_orders', __('Related Subscription Orders', WC_Subscriptions::$text_domain), __CLASS__ . '::related_orders_meta_box', 'shop_order'); } }
/** * Adjust renew membership URL for subscription-based memberships * * @since 1.0.0 * @param string $url Renew membership URL * @param WC_Memberships_User_Membership $user_membership * @return string Modified renew URL */ public function renew_membership_url($url, WC_Memberships_User_Membership $user_membership) { if ($this->has_membership_plan_subscription($user_membership->get_plan_id())) { // note that we must also check if order contains a subscription since users // can be manually-assigned to memberships and not have an associated subscription // 2.0 onwards if ($this->is_subscriptions_gte_2_0()) { if (wcs_order_contains_subscription($user_membership->get_order())) { $url = wcs_get_users_resubscribe_link($this->get_user_membership_subscription_id($user_membership->get_id())); } } else { if (WC_Subscriptions_Order::order_contains_subscription($user_membership->get_order())) { $subscription_key = $this->get_user_membership_subscription_key($user_membership->get_id()); $url = WC_Subscriptions_Renewal_Order::get_users_renewal_link($subscription_key); } } } return $url; }
/** * Check if a payment is being made on a failed renewal order from 'My Account'. If so, * redirect the order into a cart/checkout payment flow. * * @since 1.3 */ public static function before_woocommerce_pay() { global $woocommerce; if (isset($_GET['pay_for_order']) && isset($_GET['order']) && isset($_GET['order_id'])) { // Pay for existing order $order_key = urldecode($_GET['order']); $order_id = absint($_GET['order_id']); $order = new WC_Order($order_id); $failed_order_replaced_by = get_post_meta($order_id, '_failed_order_replaced_by', true); if (is_numeric($failed_order_replaced_by)) { $woocommerce->add_error(sprintf(__('Sorry, this failed order has already been paid. See order %s.', WC_Subscriptions::$text_domain), $failed_order_replaced_by)); wp_safe_redirect(get_permalink(woocommerce_get_page_id('myaccount'))); exit; } if ($order->id == $order_id && $order->order_key == $order_key && in_array($order->status, array('pending', 'failed')) && WC_Subscriptions_Renewal_Order::is_renewal($order)) { // If order being paid is a parent order, get the original order, else query parent_order if (WC_Subscriptions_Renewal_Order::is_renewal($order_id, array('order_role' => 'parent'))) { $role = 'parent'; $original_order = new WC_Order($order->order_custom_fields['_original_order'][0]); } elseif (WC_Subscriptions_Renewal_Order::is_renewal($order_id, array('order_role' => 'child'))) { $role = 'child'; $original_order = WC_Subscriptions_Renewal_Order::get_parent_order($order_id); } $order_items = WC_Subscriptions_Order::get_recurring_items($original_order); $first_order_item = reset($order_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_order_item); $product = get_product($product_id); // Make sure we don't actually need the variation ID if ($product->is_type(array('variable-subscription'))) { $item = WC_Subscriptions_Order::get_item_by_product_id($original_order, $product_id); $variation_id = $item['variation_id']; $variation = get_product($variation_id); $variation_data = $variation->get_variation_attributes(); } elseif ($product->is_type(array('subscription_variation'))) { // Handle existing renewal orders incorrectly using variation_id as the product_id $product_id = $product->id; $variation_id = $product->get_variation_id(); $variation_data = $product->get_variation_attributes(); } else { $variation_id = ''; $variation_data = array(); } $woocommerce->cart->empty_cart(true); $woocommerce->cart->add_to_cart($product_id, 1, $variation_id, $variation_data, array('subscription_renewal' => array('original_order' => $original_order->id, 'failed_order' => $order_id, 'role' => $role))); wp_safe_redirect($woocommerce->cart->get_checkout_url()); exit; } } }
/** * When creating an order at checkout, if the order is for renewing a subscription from a failed * payment, hijack the order creation to make a renewal order - not a vanilla WooCommerce order. * * @since 1.3 */ public static function filter_woocommerce_create_order($order_id, $checkout_object) { global $woocommerce; $cart_item = WC_Subscriptions_Cart::cart_contains_subscription_renewal(); if ($cart_item && 'child' == $cart_item['subscription_renewal']['role']) { $product_id = $cart_item['product_id']; $failed_order_id = $cart_item['subscription_renewal']['failed_order']; $original_order_id = $cart_item['subscription_renewal']['original_order']; $role = $cart_item['subscription_renewal']['role']; $renewal_order_args = array('new_order_role' => $role, 'checkout_renewal' => true, 'failed_order_id' => $failed_order_id); $renewal_order_id = WC_Subscriptions_Renewal_Order::generate_renewal_order($original_order_id, $product_id, $renewal_order_args); $original_order = new WC_Order($original_order_id); $customer_id = $original_order->customer_user; // Save posted billing address fields to both renewal and original order if ($checkout_object->checkout_fields['billing']) { foreach ($checkout_object->checkout_fields['billing'] as $key => $field) { update_post_meta($renewal_order_id, '_' . $key, $checkout_object->posted[$key]); update_post_meta($original_order->id, '_' . $key, $checkout_object->posted[$key]); // User if ($customer_id && !empty($checkout_object->posted[$key])) { update_user_meta($customer_id, $key, $checkout_object->posted[$key]); // Special fields switch ($key) { case "billing_email": if (!email_exists($checkout_object->posted[$key])) { wp_update_user(array('ID' => $customer_id, 'user_email' => $checkout_object->posted[$key])); } break; case "billing_first_name": wp_update_user(array('ID' => $customer_id, 'first_name' => $checkout_object->posted[$key])); break; case "billing_last_name": wp_update_user(array('ID' => $customer_id, 'last_name' => $checkout_object->posted[$key])); break; } } } } // Save posted shipping address fields to both renewal and original order if ($checkout_object->checkout_fields['shipping'] && ($woocommerce->cart->needs_shipping() || get_option('woocommerce_require_shipping_address') == 'yes')) { foreach ($checkout_object->checkout_fields['shipping'] as $key => $field) { $postvalue = false; if (isset($checkout_object->posted['shiptobilling']) && $checkout_object->posted['shiptobilling'] || isset($checkout_object->posted['ship_to_different_address']) && $checkout_object->posted['ship_to_different_address']) { if (isset($checkout_object->posted[str_replace('shipping_', 'billing_', $key)])) { $postvalue = $checkout_object->posted[str_replace('shipping_', 'billing_', $key)]; update_post_meta($renewal_order_id, '_' . $key, $postvalue); update_post_meta($original_order->id, '_' . $key, $postvalue); } } elseif (isset($checkout_object->posted[$key])) { $postvalue = $checkout_object->posted[$key]; update_post_meta($renewal_order_id, '_' . $key, $postvalue); update_post_meta($original_order->id, '_' . $key, $postvalue); } // User if ($postvalue && $customer_id) { update_user_meta($customer_id, $key, $postvalue); } } } if ($checkout_object->posted['payment_method']) { $available_gateways = $woocommerce->payment_gateways->get_available_payment_gateways(); if (isset($available_gateways[$checkout_object->posted['payment_method']])) { $payment_method = $available_gateways[$checkout_object->posted['payment_method']]; $payment_method->validate_fields(); update_post_meta($renewal_order_id, '_payment_method', $payment_method->id); update_post_meta($renewal_order_id, '_payment_method_title', $payment_method->get_title()); } } // Set the shipping method for WC < 2.1 if ($checkout_object->posted['shipping_method'] && method_exists($woocommerce->shipping, 'get_available_shipping_methods')) { $available_shipping_methods = $woocommerce->shipping->get_available_shipping_methods(); if (isset($available_shipping_methods[$checkout_object->posted['shipping_method']])) { $shipping_method = $available_shipping_methods[$checkout_object->posted['shipping_method']]; update_post_meta($renewal_order_id, '_shipping_method', $shipping_method->id); update_post_meta($renewal_order_id, '_shipping_method_title', $shipping_method->label); } } if (isset($failed_order_id)) { $failed_order = new WC_Order($failed_order_id); if ($failed_order->status == 'failed') { update_post_meta($failed_order_id, '_failed_order_replaced_by', $renewal_order_id); } } // Store fees, any new fees on this order should be applied now foreach ($woocommerce->cart->get_fees() as $fee) { $item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $fee->name, 'order_item_type' => 'fee')); if ($fee->taxable) { woocommerce_add_order_item_meta($item_id, '_tax_class', $fee->tax_class); } else { woocommerce_add_order_item_meta($item_id, '_tax_class', '0'); } woocommerce_add_order_item_meta($item_id, '_line_total', woocommerce_format_decimal($fee->amount)); woocommerce_add_order_item_meta($item_id, '_line_tax', woocommerce_format_decimal($fee->tax)); } // Store tax rows foreach (array_keys($woocommerce->cart->taxes + $woocommerce->cart->shipping_taxes) as $key) { $item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => WC_Tax::get_rate_code($key), 'order_item_type' => 'tax')); // Add line item meta if ($item_id) { woocommerce_add_order_item_meta($item_id, 'rate_id', $key); woocommerce_add_order_item_meta($item_id, 'label', WC_Tax::get_rate_label($key)); woocommerce_add_order_item_meta($item_id, 'compound', absint(WC_Tax::is_compound($key) ? 1 : 0)); woocommerce_add_order_item_meta($item_id, 'tax_amount', woocommerce_format_decimal(isset($woocommerce->cart->taxes[$key]) ? $woocommerce->cart->taxes[$key] : 0)); woocommerce_add_order_item_meta($item_id, 'shipping_tax_amount', woocommerce_format_decimal(isset($woocommerce->cart->shipping_taxes[$key]) ? $woocommerce->cart->shipping_taxes[$key] : 0)); } } // Store shipping for all packages on this order (as this can differ between each order), WC 2.1 if (method_exists($woocommerce->shipping, 'get_packages')) { $packages = $woocommerce->shipping->get_packages(); foreach ($packages as $i => $package) { if (isset($package['rates'][$checkout_object->shipping_methods[$i]])) { $method = $package['rates'][$checkout_object->shipping_methods[$i]]; $item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $method->label, 'order_item_type' => 'shipping')); if ($item_id) { woocommerce_add_order_item_meta($item_id, 'method_id', $method->id); woocommerce_add_order_item_meta($item_id, 'cost', woocommerce_format_decimal($method->cost)); do_action('woocommerce_add_shipping_order_item', $renewal_order_id, $item_id, $i); } } } } update_post_meta($renewal_order_id, '_order_shipping', WC_Subscriptions::format_total($woocommerce->cart->shipping_total)); update_post_meta($renewal_order_id, '_cart_discount', WC_Subscriptions::format_total($woocommerce->cart->get_cart_discount_total())); update_post_meta($renewal_order_id, '_order_tax', WC_Subscriptions::format_total($woocommerce->cart->tax_total)); update_post_meta($renewal_order_id, '_order_shipping_tax', WC_Subscriptions::format_total($woocommerce->cart->shipping_tax_total)); update_post_meta($renewal_order_id, '_order_total', WC_Subscriptions::format_total($woocommerce->cart->total)); // WC < 2.3, set deprecated after tax discount total if (WC_Subscriptions::is_woocommerce_pre('2.3')) { update_post_meta($renewal_order_id, '_order_discount', WC_Subscriptions::format_total($woocommerce->cart->get_total_discount())); } update_post_meta($renewal_order_id, '_order_currency', get_woocommerce_currency()); update_post_meta($renewal_order_id, '_prices_include_tax', get_option('woocommerce_prices_include_tax')); update_post_meta($renewal_order_id, '_customer_ip_address', isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']); update_post_meta($renewal_order_id, '_customer_user_agent', isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''); update_post_meta($renewal_order_id, '_checkout_renewal', 'yes'); // Return the new order's ID to prevent WC creating an order $order_id = $renewal_order_id; } return $order_id; }
/** * Check if a payment is being made on a failed renewal order from 'My Account'. If so, * redirect the order into a cart/checkout payment flow. * * @since 1.3 */ public static function before_woocommerce_pay() { global $woocommerce, $wp; if (isset($_GET['pay_for_order']) && (isset($_GET['order']) && isset($_GET['order_id']) || isset($_GET['key']) && isset($wp->query_vars['order-pay']))) { // Pay for existing order $order_key = isset($_GET['key']) ? $_GET['key'] : $_GET['order']; // WC 2.1 compatibility $order_id = isset($wp->query_vars['order-pay']) ? $wp->query_vars['order-pay'] : absint($_GET['order_id']); $order = new WC_Order($order_id); $failed_order_replaced_by = get_post_meta($order_id, '_failed_order_replaced_by', true); if (is_numeric($failed_order_replaced_by)) { WC_Subscriptions::add_notice(sprintf(__('Sorry, this failed order has already been paid. See order %s.', 'woocommerce-subscriptions'), $failed_order_replaced_by), 'error'); wp_safe_redirect(get_permalink(woocommerce_get_page_id('myaccount'))); exit; } if ($order->id == $order_id && $order->order_key == $order_key && in_array($order->status, array('pending', 'failed')) && WC_Subscriptions_Renewal_Order::is_renewal($order)) { // If order being paid is a parent order, get the original order, else query parent_order if (WC_Subscriptions_Renewal_Order::is_renewal($order_id, array('order_role' => 'parent'))) { $role = 'parent'; $original_order = new WC_Order(WC_Subscriptions_Order::get_meta($order, 'original_order', false)); } elseif (WC_Subscriptions_Renewal_Order::is_renewal($order_id, array('order_role' => 'child'))) { $role = 'child'; $original_order = WC_Subscriptions_Renewal_Order::get_parent_order($order_id); } $order_items = WC_Subscriptions_Order::get_recurring_items($original_order); $first_order_item = reset($order_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_order_item); $product = get_product($product_id); $variation_id = ''; $variation_data = array(); // Display error message for deleted products if (false === $product) { WC_Subscriptions::add_notice(self::$product_deleted_error_message, 'error'); // Make sure we don't actually need the variation ID } elseif ($product->is_type(array('variable-subscription'))) { $item = WC_Subscriptions_Order::get_item_by_product_id($original_order, $product_id); $variation_id = $item['variation_id']; $variation = get_product($variation_id); // Display error message for deleted product variations if (false === $variation) { WC_Subscriptions::add_notice(self::$product_deleted_error_message, 'error'); $variation_data = array(); } else { $variation_data = $variation->get_variation_attributes(); } } elseif ($product->is_type(array('subscription_variation'))) { // Handle existing renewal orders incorrectly using variation_id as the product_id $product_id = $product->id; $variation_id = $product->get_variation_id(); $variation_data = $product->get_variation_attributes(); } $woocommerce->cart->empty_cart(true); $woocommerce->cart->add_to_cart($product_id, 1, $variation_id, $variation_data, array('subscription_renewal' => array('original_order' => $original_order->id, 'failed_order' => $order_id, 'role' => $role))); wp_safe_redirect($woocommerce->cart->get_checkout_url()); exit; } } }
/** * Override the default PayPal standard args in WooCommerce for subscription purchases when * automatic payments are enabled and when the recurring order totals is over $0.00 (because * PayPal doesn't support subscriptions with a $0 recurring total, we need to circumvent it and * manage it entirely ourselves.) * * Based on the HTML Variables documented here: https://developer.paypal.com/webapps/developer/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/#id08A6HI00JQU * * @since 1.0 */ public static function paypal_standard_subscription_args($paypal_args) { extract(self::get_order_id_and_key($paypal_args)); $order = new WC_Order($order_id); if ($cart_item = WC_Subscriptions_Cart::cart_contains_failed_renewal_order_payment() || false !== WC_Subscriptions_Renewal_Order::get_failed_order_replaced_by($order_id)) { $renewal_order = $order; $order = WC_Subscriptions_Renewal_Order::get_parent_order($renewal_order); $order_contains_failed_renewal = true; } else { $order_contains_failed_renewal = false; } if ($order_contains_failed_renewal || WC_Subscriptions_Order::order_contains_subscription($order) && WC_Subscriptions_Order::get_recurring_total($order) > 0 && 'yes' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) { // Only one subscription allowed in the cart when PayPal Standard is active $product = $order->get_product_from_item(array_pop(WC_Subscriptions_Order::get_recurring_items($order))); // It's a subscription $paypal_args['cmd'] = '_xclick-subscriptions'; if (count($order->get_items()) > 1) { foreach ($order->get_items() as $item) { if ($item['qty'] > 1) { $item_names[] = $item['qty'] . ' x ' . self::paypal_item_name($item['name']); } elseif ($item['qty'] > 0) { $item_names[] = self::paypal_item_name($item['name']); } } $paypal_args['item_name'] = self::paypal_item_name(sprintf(__('Order %s', 'woocommerce-subscriptions'), $order->get_order_number() . " - " . implode(', ', $item_names))); } else { $paypal_args['item_name'] = self::paypal_item_name($product->get_title()); } $unconverted_periods = array('billing_period' => WC_Subscriptions_Order::get_subscription_period($order), 'trial_period' => WC_Subscriptions_Order::get_subscription_trial_period($order)); $converted_periods = array(); // Convert period strings into PayPay's format foreach ($unconverted_periods as $key => $period) { switch (strtolower($period)) { case 'day': $converted_periods[$key] = 'D'; break; case 'week': $converted_periods[$key] = 'W'; break; case 'year': $converted_periods[$key] = 'Y'; break; case 'month': default: $converted_periods[$key] = 'M'; break; } } $price_per_period = WC_Subscriptions_Order::get_recurring_total($order); $subscription_interval = WC_Subscriptions_Order::get_subscription_interval($order); $subscription_length = WC_Subscriptions_Order::get_subscription_length($order); $subscription_installments = $subscription_length / $subscription_interval; $is_payment_change = WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment; $is_switch_order = WC_Subscriptions_Switcher::order_contains_subscription_switch($order->id); $is_synced_subscription = WC_Subscriptions_Synchroniser::order_contains_synced_subscription($order->id); $sign_up_fee = $is_payment_change ? 0 : WC_Subscriptions_Order::get_sign_up_fee($order); $initial_payment = $is_payment_change ? 0 : WC_Subscriptions_Order::get_total_initial_payment($order); if ($is_payment_change) { // Add a nonce to the order ID to avoid "This invoice has already been paid" error when changing payment method to PayPal when it was previously PayPal $paypal_args['invoice'] = $paypal_args['invoice'] . '-wcscpm-' . wp_create_nonce(); } elseif ($order_contains_failed_renewal) { // Set the invoice details to the original order's invoice but also append a special string and this renewal orders ID so that we can match it up as a failed renewal order payment later $paypal_args['invoice'] = self::$invoice_prefix . ltrim($order->get_order_number(), '#') . '-wcsfrp-' . $renewal_order->id; $paypal_args['custom'] = serialize(array($order->id, $order->order_key)); } if ($order_contains_failed_renewal) { $sign_up_fee = 0; $initial_payment = $renewal_order->get_total(); // Initial payment can be left in case the customer is purchased other products with the payment $subscription_trial_length = 0; $subscription_installments = max($subscription_installments - WC_Subscriptions_Manager::get_subscriptions_completed_payment_count(WC_Subscriptions_Manager::get_subscription_key($order_id, $product->id)), 0); // If we're changing the payment date or switching subs, we need to set the trial period to the next payment date & installments to be the number of installments left } elseif ($is_payment_change || $is_switch_order || $is_synced_subscription) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order_id, $product->id); // Give a free trial until the next payment date if ($is_switch_order) { $next_payment_timestamp = get_post_meta($order->id, '_switched_subscription_first_payment_timestamp', true); } elseif ($is_synced_subscription) { $next_payment_timestamp = WC_Subscriptions_Synchroniser::calculate_first_payment_date($product, 'timestamp'); } else { $next_payment_timestamp = WC_Subscriptions_Manager::get_next_payment_date($subscription_key, $order->user_id, 'timestamp'); } // When the subscription is on hold if ($next_payment_timestamp != false && !empty($next_payment_timestamp)) { $trial_until = self::calculate_trial_periods_until($next_payment_timestamp); $subscription_trial_length = $trial_until['first_trial_length']; $converted_periods['trial_period'] = $trial_until['first_trial_period']; $second_trial_length = $trial_until['second_trial_length']; $second_trial_period = $trial_until['second_trial_period']; } else { $subscription_trial_length = 0; } // If is a payment change, we need to account for completed payments on the number of installments owing if ($is_payment_change && $subscription_length > 0) { $subscription_installments -= WC_Subscriptions_Manager::get_subscriptions_completed_payment_count($subscription_key); } } else { $subscription_trial_length = WC_Subscriptions_Order::get_subscription_trial_length($order); } if ($subscription_trial_length > 0) { // Specify a free trial period if ($is_switch_order || $is_synced_subscription || $initial_payment != $sign_up_fee) { $paypal_args['a1'] = $initial_payment > 0 ? $initial_payment : 0; } else { $paypal_args['a1'] = $sign_up_fee > 0 ? $sign_up_fee : 0; // Maybe add the sign up fee to the free trial period } // Trial period length $paypal_args['p1'] = $subscription_trial_length; // Trial period $paypal_args['t1'] = $converted_periods['trial_period']; // We need to use a second trial period before we have more than 90 days until the next payment if (isset($second_trial_length) && $second_trial_length > 0) { $paypal_args['a2'] = 0.01; // Alas, although it's undocumented, PayPal appears to require a non-zero value in order to allow a second trial period $paypal_args['p2'] = $second_trial_length; $paypal_args['t2'] = $second_trial_period; } } elseif ($sign_up_fee > 0 || $initial_payment != $price_per_period) { // No trial period, so charge sign up fee and per period price for the first period if ($subscription_installments == 1) { $param_number = 3; } else { $param_number = 1; } $paypal_args['a' . $param_number] = $initial_payment; // Sign Up interval $paypal_args['p' . $param_number] = $subscription_interval; // Sign Up unit of duration $paypal_args['t' . $param_number] = $converted_periods['billing_period']; } // We have a recurring payment if (!isset($param_number) || $param_number == 1) { // Subscription price $paypal_args['a3'] = $price_per_period; // Subscription duration $paypal_args['p3'] = $subscription_interval; // Subscription period $paypal_args['t3'] = $converted_periods['billing_period']; } // Recurring payments if ($subscription_installments == 1 || $sign_up_fee > 0 && $subscription_trial_length == 0 && $subscription_installments == 2) { // Non-recurring payments $paypal_args['src'] = 0; } else { $paypal_args['src'] = 1; if ($subscription_installments > 0) { if ($sign_up_fee > 0 && $subscription_trial_length == 0) { // An initial period is being used to charge a sign-up fee $subscription_installments--; } $paypal_args['srt'] = $subscription_installments; } } // Don't reattempt failed payments, instead let Subscriptions handle the failed payment $paypal_args['sra'] = 0; // Force return URL so that order description & instructions display $paypal_args['rm'] = 2; } return $paypal_args; }
?> " class="cancel" title="<?php _e('Click to cancel this subscription', WC_Subscriptions::$text_domain); ?> ">(<?php _e('Cancel', WC_Subscriptions::$text_domain); ?> )</a> <?php } ?> <?php if (WC_Subscriptions_Renewal_Order::can_subscription_be_renewed($subscription_key, $user_id)) { ?> <a href="<?php echo esc_url(WC_Subscriptions_Renewal_Order::get_users_renewal_link($subscription_key)); ?> " class="renew" title="<?php _e('Click to renew this subscription', WC_Subscriptions::$text_domain); ?> ">(<?php _e('Renew', WC_Subscriptions::$text_domain); ?> )</a> <?php } ?> </td> <td class="subscription-next-payment"> <?php $next_payment_timestamp = WC_Subscriptions_Manager::get_next_payment_date($subscription_key, $user_id, 'timestamp');
/** * Outputs the content for each column. * * @param array $item A singular item (one full row's worth of data) * @param array $column_name The name/slug of the column to be processed * @return string Text or HTML to be placed inside the column <td> * @since 1.0 */ public function column_default($item, $column_name) { global $woocommerce; $current_gmt_time = gmdate('U'); switch ($column_name) { case 'status': $actions = array(); $action_url = add_query_arg(array('page' => $_REQUEST['page'], 'user' => $item['user_id'], 'subscription' => $item['subscription_key'], '_wpnonce' => wp_create_nonce($item['subscription_key']))); if (isset($_REQUEST['status'])) { $action_url = add_query_arg(array('status' => $_REQUEST['status']), $action_url); } $order = new WC_Order($item['order_id']); $all_statuses = array('active' => __('Reactivate', WC_Subscriptions::$text_domain), 'on-hold' => __('Suspend', WC_Subscriptions::$text_domain), 'cancelled' => __('Cancel', WC_Subscriptions::$text_domain), 'trash' => __('Trash', WC_Subscriptions::$text_domain), 'deleted' => __('Delete Permanently', WC_Subscriptions::$text_domain)); foreach ($all_statuses as $status => $label) { if (WC_Subscriptions_Manager::can_subscription_be_changed_to($status, $item['subscription_key'], $item['user_id'])) { $action = 'deleted' == $status ? 'delete' : $status; // For built in CSS $actions[$action] = sprintf('<a href="%s">%s</a>', add_query_arg('new_status', $status, $action_url), $label); } } if ($item['status'] == 'pending') { unset($actions['active']); unset($actions['trash']); } elseif (!in_array($item['status'], array('cancelled', 'expired', 'suspended'))) { unset($actions['trash']); } $actions = apply_filters('woocommerce_subscriptions_list_table_actions', $actions, $item); $column_content = sprintf('<mark class="%s">%s</mark> %s', sanitize_title($item[$column_name]), WC_Subscriptions_Manager::get_status_to_display($item[$column_name], $item['subscription_key'], $item['user_id']), $this->row_actions($actions)); $column_content = apply_filters('woocommerce_subscriptions_list_table_column_status_content', $column_content, $item, $actions, $this); break; case 'title': //Return the title contents $column_content = sprintf('<a href="%s">%s</a>', get_edit_post_link($item['product_id']), WC_Subscriptions_Order::get_item_name($item['order_id'], $item['product_id'])); $column_content .= sprintf('<input type="hidden" class="%1$s" name="%2$s[%3$s][%4$s][][%1$s]" value="%5$s" />', 'product_id', $this->_args['plural'], $item['user_id'], $item['subscription_key'], $item['product_id']); $order = new WC_Order($item['order_id']); $order_item = WC_Subscriptions_Order::get_item_by_product_id($order, $item['product_id']); $product = $order->get_product_from_item($order_item); if (isset($product->variation_data)) { $column_content .= '<br />' . woocommerce_get_formatted_variation($product->variation_data, true); } break; case 'order_id': $order = new WC_Order($item[$column_name]); $column_content = sprintf('<a href="%1$s">%2$s</a>', get_edit_post_link($item[$column_name]), sprintf(__('Order %s', WC_Subscriptions::$text_domain), $order->get_order_number())); $column_content .= sprintf('<input type="hidden" class="%1$s" name="%2$s[%3$s][%4$s][][%1$s]" value="%5$s" />', $column_name, $this->_args['plural'], $item['user_id'], $item['subscription_key'], $item[$column_name]); break; case 'user': $user = get_user_by('id', $item['user_id']); $column_content = sprintf('<a href="%s">%s</a>', admin_url('user-edit.php?user_id=' . $user->ID), ucfirst($user->display_name)); $column_content .= sprintf('<input type="hidden" class="%1$s" name="%2$s[%3$s][%4$s][][%1$s]" value="%5$s" />', 'user_id', $this->_args['plural'], $item['user_id'], $item['subscription_key'], $item['user_id']); break; case 'start_date': case 'expiry_date': case 'end_date': if ($column_name == 'expiry_date' && $item[$column_name] == 0) { $column_content = __('Never', WC_Subscriptions::$text_domain); } else { if ($column_name == 'end_date' && $item[$column_name] == 0) { $column_content = __('Not yet ended', WC_Subscriptions::$text_domain); } else { $gmt_timestamp = strtotime($item[$column_name]); $user_timestamp = $gmt_timestamp + get_option('gmt_offset') * 3600; $column_content = sprintf('<time title="%s">%s</time>', esc_attr($gmt_timestamp), date_i18n(woocommerce_date_format(), $user_timestamp)); } } break; case 'trial_expiry_date': $trial_expiration = WC_Subscriptions_Manager::get_trial_expiration_date($item['subscription_key'], $item['user_id'], 'timestamp'); if (empty($trial_expiration)) { $column_content = '-'; } else { $column_content = sprintf('<time title="%s">%s</time>', esc_attr($trial_expiration), date_i18n(woocommerce_date_format(), $trial_expiration + get_option('gmt_offset') * 3600)); } break; case 'last_payment_date': $last_payment_timestamp = strtotime($item['last_payment_date']); $time_diff = $current_gmt_time - $last_payment_timestamp; if ($time_diff > 0 && $time_diff < 7 * 24 * 60 * 60) { $last_payment = sprintf(__('%s ago', WC_Subscriptions::$text_domain), human_time_diff($last_payment_timestamp, $current_gmt_time)); } else { $last_payment = date_i18n(woocommerce_date_format(), $last_payment_timestamp + get_option('gmt_offset') * 3600); } $column_content = sprintf('<time title="%s">%s</time>', esc_attr($last_payment_timestamp), $last_payment); break; case 'next_payment_date': $next_payment_timestamp = WC_Subscriptions_Manager::get_next_payment_date($item['subscription_key'], $item['user_id'], 'timestamp'); if ($next_payment_timestamp == 0) { $column_content = '-'; } else { // Convert to site time $time_diff = $next_payment_timestamp - $current_gmt_time; if ($time_diff > 0 && $time_diff < 7 * 24 * 60 * 60) { $next_payment = sprintf(__('In %s', WC_Subscriptions::$text_domain), human_time_diff($current_gmt_time, $next_payment_timestamp)); } else { $next_payment = date_i18n(woocommerce_date_format(), $next_payment_timestamp + get_option('gmt_offset') * 3600); } $column_content = sprintf('<time class="next-payment-date" title="%s">%s</time>', esc_attr($next_payment_timestamp), $next_payment); if (WC_Subscriptions_Manager::can_subscription_be_changed_to('new-payment-date', $item['subscription_key'], $item['user_id'])) { $column_content .= '<div class="edit-date-div row-actions hide-if-no-js">'; $column_content .= '<img class="date-picker-icon" src="' . admin_url('images/date-button.gif') . '" title="Date Picker Icon"/>'; $column_content .= '<a href="#edit_timestamp" class="edit-timestamp" tabindex="4">' . __('Change', WC_Subscriptions::$text_domain) . '</a>'; $column_content .= '<div class="date-picker-div hide-if-js">'; $column_content .= WC_Subscriptions_Manager::touch_time(array('date' => date('Y-m-d', $next_payment_timestamp), 'echo' => false, 'multiple' => true, 'include_time' => false)); $column_content .= '</div>'; $column_content .= '</form>'; } } break; case 'renewal_order_count': $count = WC_Subscriptions_Renewal_Order::get_renewal_order_count($item['order_id']); $column_content = sprintf('<a href="%1$s">%2$d</a>', admin_url('edit.php?post_status=all&post_type=shop_order&_renewal_order_parent_id=' . absint($item['order_id'])), $count); break; } return $column_content; }
/** * Check if a given order is a subscription renewal order * * @param $order WC_Order | int The WC_Order object or ID of a WC_Order order. * @deprecated 1.2 * @since 1.0 */ public static function is_renewal($order) { _deprecated_function(__CLASS__ . '::' . __FUNCTION__, '1.2', 'WC_Subscriptions_Renewal_Order::is_renewal( $order )'); return WC_Subscriptions_Renewal_Order::is_renewal($order); }
/** * Version 1.2 introduced child renewal orders to keep a record of each completed subscription * payment. Before 1.2, these orders did not exist, so this function creates them. * * @since 1.2 */ private static function generate_renewal_orders() { global $woocommerce, $wpdb; $subscriptions_grouped_by_user = WC_Subscriptions_Manager::get_all_users_subscriptions(); // Don't send any order emails $email_actions = array('woocommerce_low_stock', 'woocommerce_no_stock', 'woocommerce_product_on_backorder', 'woocommerce_order_status_pending_to_processing', 'woocommerce_order_status_pending_to_completed', 'woocommerce_order_status_pending_to_on-hold', 'woocommerce_order_status_failed_to_processing', 'woocommerce_order_status_failed_to_completed', 'woocommerce_order_status_pending_to_processing', 'woocommerce_order_status_pending_to_on-hold', 'woocommerce_order_status_completed', 'woocommerce_new_customer_note'); foreach ($email_actions as $action) { remove_action($action, array(&$woocommerce, 'send_transactional_email')); } remove_action('woocommerce_payment_complete', 'WC_Subscriptions_Renewal_Order::maybe_record_renewal_order_payment', 10, 1); foreach ($subscriptions_grouped_by_user as $user_id => $users_subscriptions) { foreach ($users_subscriptions as $subscription_key => $subscription) { $order_post = get_post($subscription['order_id']); if (isset($subscription['completed_payments']) && count($subscription['completed_payments']) > 0 && $order_post != null) { foreach ($subscription['completed_payments'] as $payment_date) { $existing_renewal_order = $wpdb->get_var($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_date_gmt = %s AND post_parent = %d AND post_type = 'shop_order'", $payment_date, $subscription['order_id'])); // If a renewal order exists on this date, don't generate another one if (NULL !== $existing_renewal_order) { continue; } $renewal_order_id = WC_Subscriptions_Renewal_Order::generate_renewal_order($subscription['order_id'], $subscription['product_id'], array('new_order_role' => 'child')); if ($renewal_order_id) { // Mark the order as paid $renewal_order = new WC_Order($renewal_order_id); $renewal_order->payment_complete(); // Avoid creating 100s "processing" orders $renewal_order->update_status('completed'); // Set correct dates on the order $renewal_order = array('ID' => $renewal_order_id, 'post_date' => $payment_date, 'post_date_gmt' => $payment_date); wp_update_post($renewal_order); update_post_meta($renewal_order_id, '_paid_date', $payment_date); update_post_meta($renewal_order_id, '_completed_date', $payment_date); } } } } } }
function woo_ce_get_subscription_renewals( $order_id = 0 ) { $renewals = 0; // Check that get_renewal_order_count() exists within the WC_Subscriptions_Renewal_Order class if( method_exists( 'WC_Subscriptions_Renewal_Order', 'get_renewal_order_count' ) ) $renewals = WC_Subscriptions_Renewal_Order::get_renewal_order_count( $order_id ); return $renewals; }
/** * Registers the "Renewal Orders" meta box for the "Edit Order" page. */ public static function add_meta_boxes() { global $current_screen, $post_id; // Only display the meta box if an order relates to a subscription if ('shop_order' == $current_screen->id) { $order_contains_subscription = WC_Subscriptions_Order::order_contains_subscription($post_id); if ($order_contains_subscription || WC_Subscriptions_Renewal_Order::is_renewal($post_id, array('order_role' => 'child'))) { add_meta_box('subscription_renewal_orders', __('Related Subscription Orders', 'woocommerce-subscriptions'), __CLASS__ . '::related_orders_meta_box', 'shop_order', 'normal', 'default'); } if ($order_contains_subscription || 'add' == $current_screen->action) { if (!WC_Subscriptions::is_woocommerce_pre_2_2()) { add_meta_box('woocommerce-order-totals', __('Recurring Totals', 'woocommerce-subscriptions'), __CLASS__ . '::recurring_totals_meta_box', 'shop_order', 'side', 'high'); } else { // WC 2.1 compatibility add_filter('woocommerce_admin_order_totals_after_shipping', 'WC_Subscriptions_Order::recurring_order_totals_meta_box_section', 100, 1); } } } }
<link itemprop="availability" href="http://schema.org/InStock"> <?php do_action('woocommerce_before_add_to_cart_form'); ?> <?php if (!$product->is_purchasable() && 0 != $user_id && 'no' != $product->limit_subscriptions && ('active' == $product->limit_subscriptions && WC_Subscriptions_Manager::user_has_subscription(0, $product->id, 'on-hold') || ($user_has_subscription = WC_Subscriptions_Manager::user_has_subscription($user_id, $product->id, $product->limit_subscriptions)))) { ?> <?php if ('any' == $product->limit_subscriptions && $user_has_subscription && !WC_Subscriptions_Manager::user_has_subscription($user_id, $product->id, 'active') && !WC_Subscriptions_Manager::user_has_subscription($user_id, $product->id, 'on-hold')) { // customer has an inactive subscription, maybe offer the renewal button ?> <?php $renewal_link = WC_Subscriptions_Renewal_Order::get_users_renewal_link_for_product($product->id); ?> <?php if (!empty($renewal_link)) { ?> <a href="<?php echo $renewal_link; ?> " class="button product-renewal-link"><?php _e('Renew', 'woocommerce-subscriptions'); ?> </a> <?php } ?> <?php
/** * Check if a given order is a subscription renewal order * * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. * @deprecated 1.2 * @since 1.0 */ public static function is_renewal($order) { _deprecated_function(__METHOD__, '1.2', 'WC_Subscriptions_Renewal_Order::is_renewal( $order )'); return WC_Subscriptions_Renewal_Order::is_renewal($order); }
/** * Returns the string key for a subscription purchased in an order specified by $order_id * * @param order_id int The ID of the order in which the subscription was purchased. * @param product_id int The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0 */ public static function get_subscription_key($order_id, $product_id = '') { // If we have a child renewal order, we need the parent order's ID if (WC_Subscriptions_Renewal_Order::is_renewal($order_id, array('order_role' => 'child'))) { $order_id = WC_Subscriptions_Renewal_Order::get_parent_order_id($order_id); } if (empty($product_id)) { $order = new WC_Order($order_id); $order_items = WC_Subscriptions_Order::get_recurring_items($order); $first_order_item = reset($order_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_order_item); } $subscription_key = $order_id . '_' . $product_id; return apply_filters('woocommerce_subscription_key', $subscription_key, $order_id, $product_id); }
/** * Returns the string key for a subscription purchased in an order specified by $order_id * * @param order_id int The ID of the order in which the subscription was purchased. * @param product_id int The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0 */ public static function get_subscription_key($order_id, $product_id = '') { // If we have a child renewal order, we need the parent order's ID if (WC_Subscriptions_Renewal_Order::is_renewal($order_id, 'child')) { $order_id = WC_Subscriptions_Renewal_Order::get_parent_order_id($order_id); } if (empty($product_id)) { $order = new WC_Order($order_id); $order_items = $order->get_items(); $product_id = $order_items[0]['id']; } $subscription_key = $order_id . '_' . $product_id; return apply_filters('woocommerce_subscription_key', $subscription_key, $order_id, $product_id); }
/** * Activates a Klarna order for V2 API * * @since 2.0 **/ function activate_order($orderid) { $order = wc_get_order($orderid); if (get_post_meta($orderid, '_klarna_order_reservation', true) && get_post_meta($orderid, '_billing_country', true)) { // Check if this is a subscription order if (class_exists('WC_Subscriptions_Renewal_Order') && WC_Subscriptions_Renewal_Order::is_renewal($order)) { if (!get_post_meta($orderid, '_klarna_order_reservation_recurring', true)) { return; } } $rno = get_post_meta($orderid, '_klarna_order_reservation', true); $country = get_post_meta($orderid, '_billing_country', true); $payment_method = get_post_meta($orderid, '_payment_method', true); $klarna = new Klarna(); $this->configure_klarna($klarna, $country, $payment_method); try { $result = $klarna->activate($rno, null, KlarnaFlags::RSRV_SEND_BY_EMAIL); $risk = $result[0]; // returns 'ok' or 'no_risk' $invNo = $result[1]; // returns invoice number $order->add_order_note(sprintf(__('Klarna order activated. Invoice number %s - risk status %s.', 'woocommerce-gateway-klarna'), $invNo, $risk)); update_post_meta($orderid, '_klarna_order_activated', time()); update_post_meta($orderid, '_klarna_invoice_number', $invNo); update_post_meta($orderid, '_transaction_id', $invNo); } catch (Exception $e) { $order->add_order_note(sprintf(__('Klarna order activation failed. Error code %s. Error message %s', 'woocommerce-gateway-klarna'), $e->getCode(), utf8_encode($e->getMessage()))); } } }
/** * If no sort order set, default to title. If no sort order, default to descending. * * @since 1.0 */ function sort_subscriptions($a, $b) { $order_by = !empty($_REQUEST['orderby']) ? $_REQUEST['orderby'] : 'start_date'; $order = !empty($_REQUEST['order']) ? $_REQUEST['order'] : 'desc'; switch ($order_by) { case 'product_name': $product_name_a = get_the_title($a['product_id']); $product_name_b = get_the_title($b['product_id']); $result = strcasecmp($product_name_a, $product_name_b); break; case 'user': $user_a = get_user_by('id', $a['user_id']); $user_b = get_user_by('id', $b['user_id']); $result = strcasecmp($user_a->display_name, $user_b->display_name); break; case 'expiry_date': if ($order == 'asc') { $result = self::sort_with_zero_at_end($a[$order_by], $b[$order_by]); } else { $result = self::sort_with_zero_at_beginning($a[$order_by], $b[$order_by]); } break; case 'end_date': $result = self::sort_with_zero_at_end($a[$order_by], $b[$order_by]); // Display subscriptions that have not ended at the end of the list break; case 'next_payment_date': $next_payment_a = WC_Subscriptions_Manager::get_next_payment_date($a['subscription_key'], $a['user_id'], 'mysql'); $next_payment_b = WC_Subscriptions_Manager::get_next_payment_date($b['subscription_key'], $b['user_id'], 'mysql'); $result = self::sort_with_zero_at_end($next_payment_a, $next_payment_b); // Display subscriptions with no future payments at the end break; case 'last_payment_date': $last_payment_a = empty($a['completed_payments']) ? 0 : strtotime(array_pop($a['completed_payments'])); $last_payment_b = empty($b['completed_payments']) ? 0 : strtotime(array_pop($b['completed_payments'])); $result = self::sort_with_zero_at_end($last_payment_a, $last_payment_b); // Display subscriptions with no compelted payments at the end break; case 'trial_expiry_date': $trial_expiration_a = WC_Subscriptions_Manager::get_trial_expiration_date($a['subscription_key'], $a['user_id'], 'mysql'); $trial_expiration_b = WC_Subscriptions_Manager::get_trial_expiration_date($b['subscription_key'], $b['user_id'], 'mysql'); $result = self::sort_with_zero_at_end($trial_expiration_a, $trial_expiration_b); break; case 'renewal_order_count': $result = strcmp(WC_Subscriptions_Renewal_Order::get_renewal_order_count($a['order_id']), WC_Subscriptions_Renewal_Order::get_renewal_order_count($b['order_id'])); break; case 'order_id': $result = strnatcmp($a[$order_by], $b[$order_by]); break; default: $result = strcmp($a[$order_by], $b[$order_by]); break; } return $order == 'asc' ? $result : -$result; // Send final sort direction to usort }
/** * Loads the my-subscriptions.php template on the My Account page. * * @since 1.0 */ public static function get_my_subscriptions_template() { $subscriptions = WC_Subscriptions_Manager::get_users_subscriptions(); $user_id = get_current_user_id(); $all_actions = array(); foreach ($subscriptions as $subscription_key => $subscription_details) { $actions = array(); if ($subscription_details['status'] == 'trash') { unset($subscriptions[$subscription_key]); continue; } $admin_with_suspension_disallowed = current_user_can('manage_woocommerce') && 0 == get_option(WC_Subscriptions_Admin::$option_prefix . '_max_customer_suspensions', 0) ? true : false; if (WC_Subscriptions_Manager::can_subscription_be_changed_to('on-hold', $subscription_key, $user_id) && WC_Subscriptions_Manager::current_user_can_suspend_subscription($subscription_key) && !$admin_with_suspension_disallowed) { $actions['suspend'] = array('url' => WC_Subscriptions_Manager::get_users_change_status_link($subscription_key, 'on-hold'), 'name' => __('Suspend', 'woocommerce-subscriptions')); } elseif (WC_Subscriptions_Manager::can_subscription_be_changed_to('active', $subscription_key, $user_id) && !WC_Subscriptions_Manager::subscription_requires_payment($subscription_key, $user_id)) { $actions['reactivate'] = array('url' => WC_Subscriptions_Manager::get_users_change_status_link($subscription_key, 'active'), 'name' => __('Reactivate', 'woocommerce-subscriptions')); } if (WC_Subscriptions_Renewal_Order::can_subscription_be_renewed($subscription_key, $user_id)) { $actions['renew'] = array('url' => WC_Subscriptions_Renewal_Order::get_users_renewal_link($subscription_key), 'name' => __('Renew', 'woocommerce-subscriptions')); } $renewal_orders = WC_Subscriptions_Renewal_Order::get_renewal_orders($subscription_details['order_id'], 'ID'); $last_order_id = end($renewal_orders); if ($last_order_id) { $renewal_order = new WC_Order($last_order_id); if (WC_Subscriptions_Manager::can_subscription_be_changed_to('active', $subscription_key, $user_id) && in_array($renewal_order->status, array('pending', 'failed')) && !is_numeric(get_post_meta($renewal_order->id, '_failed_order_replaced_by', true))) { $actions['pay'] = array('url' => $renewal_order->get_checkout_payment_url(), 'name' => __('Pay', 'woocommerce-subscriptions')); } } else { // Check if the master order still needs to be paid $order = new WC_Order($subscription_details['order_id']); if ('pending' == $order->status && WC_Subscriptions_Manager::can_subscription_be_changed_to('active', $subscription_key, $user_id)) { $actions['pay'] = array('url' => $order->get_checkout_payment_url(), 'name' => __('Pay', 'woocommerce-subscriptions')); } } if (WC_Subscriptions_Manager::can_subscription_be_changed_to('cancelled', $subscription_key, $user_id) && $subscription_details['interval'] != $subscription_details['length']) { $actions['cancel'] = array('url' => WC_Subscriptions_Manager::get_users_change_status_link($subscription_key, 'cancelled'), 'name' => __('Cancel', 'woocommerce-subscriptions')); } $all_actions[$subscription_key] = $actions; } $all_actions = apply_filters('woocommerce_my_account_my_subscriptions_actions', $all_actions, $subscriptions); woocommerce_get_template('myaccount/my-subscriptions.php', array('subscriptions' => $subscriptions, 'actions' => $all_actions, 'user_id' => $user_id), '', plugin_dir_path(__FILE__) . 'templates/'); }
/** * Outputs the contents of the "Renewal Orders" meta box. * * @param object $post Current post data. */ public static function renewal_orders_meta_box($post) { $order = new WC_Order(absint($post->ID)); if (WC_Subscriptions_Renewal_Order::is_renewal($order, array('order_role' => 'child'))) { $parent_id = WC_Subscriptions_Renewal_Order::get_parent_order_id($order); } else { if (WC_Subscriptions_Order::order_contains_subscription($order)) { $parent_id = $post->ID; } } //Find any renewal orders associated with this order. $items = get_posts(array('post_type' => $post->post_type, 'post_parent' => $parent_id, 'numberposts' => -1)); if (WC_Subscriptions_Renewal_Order::is_renewal($order, array('order_role' => 'child'))) { $parent_order = new WC_Order($parent_id); printf('<p>%1$s <a href="%2$s">%3$s</a></p>', __('Initial Order:', WC_Subscriptions::$text_domain), get_edit_post_link($parent_id), $parent_order->get_order_number()); } if (empty($items)) { printf(' <p class="renewal-subtitle">%s</p>', __('No renewal payments yet.', WC_Subscriptions::$text_domain)); } else { printf('<p class="renewal-subtitle">%s</p>', __('Renewal Orders:', WC_Subscriptions::$text_domain)); echo '<ul class="renewal-orders">'; foreach ($items as $item) { $renewal_order = new WC_Order($item->ID); if ($item->ID == $post->ID) { printf('<li><strong>%s</strong></li>', $renewal_order->get_order_number()); } else { printf('<li><a href="%1$s">%2$s</a></li>', get_edit_post_link($item->ID), $renewal_order->get_order_number()); } } echo '</ul>'; } }
/** * If viewing a renewal order on the the Edit Order screen, set the available email actions for the order to use * renewal order emails, not core WooCommerce order emails. * * @param int $user_id The ID of the user who the subscription belongs to * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() * @return void */ public static function renewal_order_emails_available($available_emails) { global $theorder; if (WC_Subscriptions_Renewal_Order::is_renewal($theorder->id, array('order_role' => 'child'))) { $available_emails = array('new_renewal_order', 'customer_processing_renewal_order', 'customer_completed_renewal_order', 'customer_renewal_invoice'); } return $available_emails; }
/** * Get PayPal Args for passing to PP * * Based on the HTML Variables documented here: https://developer.paypal.com/webapps/developer/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/#id08A6HI00JQU * * @param WC_Order $order * @return array */ public static function get_paypal_args($paypal_args, $order) { $is_payment_change = WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment; $order_contains_failed_renewal = false; // Payment method changes act on the subscription not the original order if ($is_payment_change) { $subscriptions = array(wcs_get_subscription($order->id)); $subscription = array_pop($subscriptions); $order = $subscription->order; // We need the subscription's total remove_filter('woocommerce_order_amount_total', 'WC_Subscriptions_Change_Payment_Gateway::maybe_zero_total', 11, 2); } else { // Otherwise the order is the $order if ($cart_item = wcs_cart_contains_failed_renewal_order_payment() || false !== WC_Subscriptions_Renewal_Order::get_failed_order_replaced_by($order->id)) { $subscriptions = wcs_get_subscriptions_for_renewal_order($order); $order_contains_failed_renewal = true; } else { $subscriptions = wcs_get_subscriptions_for_order($order); } // Only one subscription allowed per order with PayPal $subscription = array_pop($subscriptions); } if ($order_contains_failed_renewal || !empty($subscription) && $subscription->get_total() > 0 && 'yes' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) { // It's a subscription $paypal_args['cmd'] = '_xclick-subscriptions'; // Store the subscription ID in the args sent to PayPal so we can access them later $paypal_args['custom'] = wcs_json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key, 'subscription_id' => $subscription->id, 'subscription_key' => $subscription->order_key)); foreach ($subscription->get_items() as $item) { if ($item['qty'] > 1) { $item_names[] = $item['qty'] . ' x ' . wcs_get_paypal_item_name($item['name']); } elseif ($item['qty'] > 0) { $item_names[] = wcs_get_paypal_item_name($item['name']); } } // translators: 1$: subscription ID, 2$: order ID, 3$: names of items, comma separated $paypal_args['item_name'] = wcs_get_paypal_item_name(sprintf(_x('Subscription %1$s (Order %2$s) - %3$s', 'item name sent to paypal', 'woocommerce-subscriptions'), $subscription->get_order_number(), $order->get_order_number(), implode(', ', $item_names))); $unconverted_periods = array('billing_period' => $subscription->billing_period, 'trial_period' => $subscription->trial_period); $converted_periods = array(); // Convert period strings into PayPay's format foreach ($unconverted_periods as $key => $period) { switch (strtolower($period)) { case 'day': $converted_periods[$key] = 'D'; break; case 'week': $converted_periods[$key] = 'W'; break; case 'year': $converted_periods[$key] = 'Y'; break; case 'month': default: $converted_periods[$key] = 'M'; break; } } $price_per_period = $subscription->get_total(); $subscription_interval = $subscription->billing_interval; $start_timestamp = $subscription->get_time('start'); $trial_end_timestamp = $subscription->get_time('trial_end'); $next_payment_timestamp = $subscription->get_time('next_payment'); $is_synced_subscription = WC_Subscriptions_Synchroniser::subscription_contains_synced_product($subscription->id); if ($is_synced_subscription) { $length_from_timestamp = $next_payment_timestamp; } elseif ($trial_end_timestamp > 0) { $length_from_timestamp = $trial_end_timestamp; } else { $length_from_timestamp = $start_timestamp; } $subscription_length = wcs_estimate_periods_between($length_from_timestamp, $subscription->get_time('end'), $subscription->billing_period); $subscription_installments = $subscription_length / $subscription_interval; $initial_payment = $is_payment_change ? 0 : $order->get_total(); if ($order_contains_failed_renewal || $is_payment_change) { if ($is_payment_change) { // Add a nonce to the order ID to avoid "This invoice has already been paid" error when changing payment method to PayPal when it was previously PayPal $suffix = '-wcscpm-' . wp_create_nonce(); } else { // Failed renewal order, append a descriptor and renewal order's ID $suffix = '-wcsfrp-' . $order->id; } // Change the 'invoice' and the 'custom' values to be for the original order (if there is one) if (false === $subscription->order) { // No original order so we need to use the subscriptions values instead $order_number = ltrim($subscription->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions')) . '-subscription'; $order_id_key = array('order_id' => $subscription->id, 'order_key' => $subscription->order_key); } else { $order_number = ltrim($subscription->order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions')); $order_id_key = array('order_id' => $subscription->order->id, 'order_key' => $subscription->order->order_key); } $order_details = false !== $subscription->order ? $subscription->order : $subscription; // Set the invoice details to the original order's invoice but also append a special string and this renewal orders ID so that we can match it up as a failed renewal order payment later $paypal_args['invoice'] = WCS_PayPal::get_option('invoice_prefix') . $order_number . $suffix; $paypal_args['custom'] = wcs_json_encode(array_merge($order_id_key, array('subscription_id' => $subscription->id, 'subscription_key' => $subscription->order_key))); } if ($order_contains_failed_renewal) { $subscription_trial_length = 0; $subscription_installments = max($subscription_installments - $subscription->get_completed_payment_count(), 0); // If we're changing the payment date or switching subs, we need to set the trial period to the next payment date & installments to be the number of installments left } elseif ($is_payment_change || $is_synced_subscription) { $next_payment_timestamp = $subscription->get_time('next_payment'); // When the subscription is on hold if (false != $next_payment_timestamp && !empty($next_payment_timestamp)) { $trial_until = wcs_calculate_paypal_trial_periods_until($next_payment_timestamp); $subscription_trial_length = $trial_until['first_trial_length']; $converted_periods['trial_period'] = $trial_until['first_trial_period']; $second_trial_length = $trial_until['second_trial_length']; $second_trial_period = $trial_until['second_trial_period']; } else { $subscription_trial_length = 0; } // If this is a payment change, we need to account for completed payments on the number of installments owing if ($is_payment_change && $subscription_length > 0) { $subscription_installments = max($subscription_installments - $subscription->get_completed_payment_count(), 0); } } else { $subscription_trial_length = wcs_estimate_periods_between($start_timestamp, $trial_end_timestamp, $subscription->trial_period); } if ($subscription_trial_length > 0) { // Specify a free trial period $paypal_args['a1'] = $initial_payment > 0 ? $initial_payment : 0; // Trial period length $paypal_args['p1'] = $subscription_trial_length; // Trial period $paypal_args['t1'] = $converted_periods['trial_period']; // We need to use a second trial period before we have more than 90 days until the next payment if (isset($second_trial_length) && $second_trial_length > 0) { $paypal_args['a2'] = 0.01; // Alas, although it's undocumented, PayPal appears to require a non-zero value in order to allow a second trial period $paypal_args['p2'] = $second_trial_length; $paypal_args['t2'] = $second_trial_period; } } elseif ($initial_payment != $price_per_period) { // No trial period, but initial amount includes a sign-up fee and/or other items, so charge it as a separate period if (1 == $subscription_installments) { $param_number = 3; } else { $param_number = 1; } $paypal_args['a' . $param_number] = $initial_payment; // Sign Up interval $paypal_args['p' . $param_number] = $subscription_interval; // Sign Up unit of duration $paypal_args['t' . $param_number] = $converted_periods['billing_period']; } // We have a recurring payment if (!isset($param_number) || 1 == $param_number) { // Subscription price $paypal_args['a3'] = $price_per_period; // Subscription duration $paypal_args['p3'] = $subscription_interval; // Subscription period $paypal_args['t3'] = $converted_periods['billing_period']; } // Recurring payments if (1 == $subscription_installments || $initial_payment != $price_per_period && 0 == $subscription_trial_length && 2 == $subscription_installments) { // Non-recurring payments $paypal_args['src'] = 0; } else { $paypal_args['src'] = 1; if ($subscription_installments > 0) { // An initial period is being used to charge a sign-up fee if ($initial_payment != $price_per_period && 0 == $subscription_trial_length) { $subscription_installments--; } $paypal_args['srt'] = $subscription_installments; } } // Don't reattempt failed payments, instead let Subscriptions handle the failed payment $paypal_args['sra'] = 0; // Force return URL so that order description & instructions display $paypal_args['rm'] = 2; // Reattach the filter we removed earlier if ($is_payment_change) { add_filter('woocommerce_order_amount_total', 'WC_Subscriptions_Change_Payment_Gateway::maybe_zero_total', 11, 2); } } return $paypal_args; }
/** * Returns the string key for a subscription purchased in an order specified by $order_id * * @param order_id int The ID of the order in which the subscription was purchased. * @param product_id int The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0 */ public static function get_subscription_key($order_id, $product_id = '') { _deprecated_function(__METHOD__, '2.0', 'wcs_get_old_subscription_key( WC_Subscription $subscription )'); // If we have a child renewal order, we need the parent order's ID if (wcs_order_contains_renewal($order_id)) { $order_id = WC_Subscriptions_Renewal_Order::get_parent_order_id($order_id); } // Get the ID of the first order item in a subscription created by this order if (empty($product_id)) { $subscriptions = wcs_get_subscriptions_for_order($order_id, array('order_type' => 'parent')); foreach ($subscriptions as $subscription) { $subscription_items = $subscription->get_items(); if (!empty($subscription_items)) { break; } } if (!empty($subscription_items)) { $first_item = reset($subscription_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_item); } else { $product_id = ''; } } $subscription_key = $order_id . '_' . $product_id; return apply_filters('woocommerce_subscription_key', $subscription_key, $order_id, $product_id); }
if ('failed' == $payment_status) { $subscription->payment_failed(); } else { $subscription->payment_complete(); $subscription->update_status('active'); } } } } /** * Adds a renewal orders section to the Related Orders meta box displayed on subscription orders. * * @deprecated 2.0 * @since 1.2 */ public static function renewal_orders_meta_box_section($order, $post) { _deprecated_function(__METHOD__, '2.0'); } /** * Trigger a hook when a subscription suspended due to a failed renewal payment is reactivated * * @since 1.3 */ public static function trigger_processed_failed_renewal_order_payment_hook($user_id, $subscription_key) { _deprecated_function(__METHOD__, '2.0', __CLASS__ . '::maybe_record_subscription_payment( $order_id, $orders_old_status, $orders_new_status )'); } } WC_Subscriptions_Renewal_Order::init();
/** * Processing Order * * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. * @deprecated 1.2 * @since 1.0 */ public static function send_customer_renewal_order_email($order) { _deprecated_function(__METHOD__, '1.2', 'WC_Subscriptions_Renewal_Order::send_customer_renewal_order_email( $order )'); WC_Subscriptions_Renewal_Order::send_customer_renewal_order_email($order); }
/** * Check if order contains subscriptions. * * @param int $order_id * @return bool */ protected function order_contains_subscription($order_id) { return class_exists('WC_Subscriptions_Order') && (WC_Subscriptions_Order::order_contains_subscription($order_id) || WC_Subscriptions_Renewal_Order::is_renewal($order_id)); }