/** * Add subscription related order item meta when a subscription product is added as an item to an order via Ajax. * * @param item_id int An order_item_id as returned by the insert statement of @see woocommerce_add_order_item() * @since 1.2.5 * @version 1.4 * @return void */ public static function prefill_order_item_meta($item, $item_id) { $order_id = $_POST['order_id']; $product_id = $item['variation_id'] ? $item['variation_id'] : $item['product_id']; if ($item_id && WC_Subscriptions_Product::is_subscription($product_id)) { $order = new WC_Order($order_id); $_product = get_product($product_id); $recurring_amount = $_product->get_price_excluding_tax(); $sign_up_fee = $_product->get_sign_up_fee_excluding_tax(); $free_trial_length = WC_Subscriptions_Product::get_trial_length($product_id); woocommerce_add_order_item_meta($item_id, '_subscription_period', WC_Subscriptions_Product::get_period($product_id)); woocommerce_add_order_item_meta($item_id, '_subscription_interval', WC_Subscriptions_Product::get_interval($product_id)); woocommerce_add_order_item_meta($item_id, '_subscription_length', WC_Subscriptions_Product::get_length($product_id)); woocommerce_add_order_item_meta($item_id, '_subscription_trial_length', $free_trial_length); woocommerce_add_order_item_meta($item_id, '_subscription_trial_period', WC_Subscriptions_Product::get_trial_period($product_id)); woocommerce_add_order_item_meta($item_id, '_subscription_recurring_amount', $recurring_amount); woocommerce_add_order_item_meta($item_id, '_subscription_sign_up_fee', $sign_up_fee); woocommerce_add_order_item_meta($item_id, '_recurring_line_total', $recurring_amount); woocommerce_add_order_item_meta($item_id, '_recurring_line_tax', 0); woocommerce_add_order_item_meta($item_id, '_recurring_line_subtotal', $recurring_amount); woocommerce_add_order_item_meta($item_id, '_recurring_line_subtotal_tax', 0); WC_Subscriptions_Manager::create_pending_subscription_for_order($order_id, $item['product_id']); switch ($order->status) { case 'completed': case 'processing': woocommerce_update_order_item_meta($item_id, '_subscription_status', 'active'); break; case 'on-hold': woocommerce_update_order_item_meta($item_id, '_subscription_status', 'on-hold'); break; case 'failed': case 'cancelled': woocommerce_add_order_item_meta($item_id, '_subscription_status', 'cancelled'); break; } // We need to override the line totals to $0 when there is a free trial if ($free_trial_length > 0 || $sign_up_fee > 0) { $line_total_keys = array('line_subtotal', 'line_total'); // Make sure sign up fees are included in the total (or $0 if no sign up fee and a free trial) foreach ($line_total_keys as $line_total_key) { $item[$line_total_key] = $sign_up_fee; } // If there is no free trial, make sure line totals include sign up fee and recurring fees if (0 == $free_trial_length) { foreach ($line_total_keys as $line_total_key) { $item[$line_total_key] += $recurring_amount; } } foreach ($line_total_keys as $line_total_key) { $item[$line_total_key] = WC_Subscriptions::format_total($item[$line_total_key], 2); } } else { $item['line_subtotal'] = $recurring_amount; $item['line_total'] = $recurring_amount; } woocommerce_update_order_item_meta($item_id, '_line_subtotal', $item['line_subtotal']); woocommerce_update_order_item_meta($item_id, '_line_total', $item['line_total']); } return $item; }
/** * When a new order is inserted, add subscriptions related order meta. * * @since 1.0 */ public static function add_order_meta($order_id, $posted) { global $woocommerce; if (!WC_Subscriptions_Cart::cart_contains_subscription_renewal('child') && WC_Subscriptions_Order::order_contains_subscription($order_id)) { // This works because the 'woocommerce_add_order_item_meta' runs before the 'woocommerce_checkout_update_order_meta' hook // Set the recurring totals so totals display correctly on order page update_post_meta($order_id, '_order_recurring_discount_cart', WC_Subscriptions_Cart::get_recurring_discount_cart()); update_post_meta($order_id, '_order_recurring_discount_cart_tax', WC_Subscriptions_Cart::get_recurring_discount_cart_tax()); update_post_meta($order_id, '_order_recurring_discount_total', WC_Subscriptions_Cart::get_recurring_discount_total()); update_post_meta($order_id, '_order_recurring_shipping_tax_total', WC_Subscriptions_Cart::get_recurring_shipping_tax_total()); update_post_meta($order_id, '_order_recurring_shipping_total', WC_Subscriptions_Cart::get_recurring_shipping_total()); update_post_meta($order_id, '_order_recurring_tax_total', WC_Subscriptions_Cart::get_recurring_total_tax()); update_post_meta($order_id, '_order_recurring_total', WC_Subscriptions_Cart::get_recurring_total()); // Set the recurring payment method - it starts out the same as the original by may change later update_post_meta($order_id, '_recurring_payment_method', get_post_meta($order_id, '_payment_method', true)); update_post_meta($order_id, '_recurring_payment_method_title', get_post_meta($order_id, '_payment_method_title', true)); $order = new WC_Order($order_id); $order_fees = $order->get_fees(); // the fee order items have already been set, we just need to to add the recurring total meta $cart_fees = $woocommerce->cart->get_fees(); foreach ($order->get_fees() as $item_id => $order_fee) { // Find the matching fee in the cart foreach ($cart_fees as $fee_index => $cart_fee) { if (sanitize_title($order_fee['name']) == $cart_fee->id) { woocommerce_add_order_item_meta($item_id, '_recurring_line_total', wc_format_decimal($cart_fee->recurring_amount)); woocommerce_add_order_item_meta($item_id, '_recurring_line_tax', wc_format_decimal($cart_fee->recurring_tax)); unset($cart_fees[$fee_index]); break; } } } // Get recurring taxes into same format as _order_taxes $order_recurring_taxes = array(); foreach (WC_Subscriptions_Cart::get_recurring_taxes() as $tax_key => $tax_amount) { $item_id = woocommerce_add_order_item($order_id, array('order_item_name' => WC_Tax::get_rate_code($tax_key), 'order_item_type' => 'recurring_tax')); if ($item_id) { wc_add_order_item_meta($item_id, 'rate_id', $tax_key); wc_add_order_item_meta($item_id, 'label', WC_Tax::get_rate_label($tax_key)); wc_add_order_item_meta($item_id, 'compound', absint(WC_Tax::is_compound($tax_key) ? 1 : 0)); wc_add_order_item_meta($item_id, 'tax_amount', wc_format_decimal(isset(WC()->cart->recurring_taxes[$tax_key]) ? WC()->cart->recurring_taxes[$tax_key] : 0)); wc_add_order_item_meta($item_id, 'shipping_tax_amount', wc_format_decimal(isset(WC()->cart->recurring_shipping_taxes[$tax_key]) ? WC()->cart->recurring_shipping_taxes[$tax_key] : 0)); } } $payment_gateways = $woocommerce->payment_gateways->payment_gateways(); if ('yes' == get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) { update_post_meta($order_id, '_wcs_requires_manual_renewal', 'true'); } elseif (isset($payment_gateways[$posted['payment_method']]) && !$payment_gateways[$posted['payment_method']]->supports('subscriptions')) { update_post_meta($order_id, '_wcs_requires_manual_renewal', 'true'); } $cart_item = WC_Subscriptions_Cart::cart_contains_subscription_renewal(); if (isset($cart_item['subscription_renewal']) && 'parent' == $cart_item['subscription_renewal']['role']) { update_post_meta($order_id, '_original_order', $cart_item['subscription_renewal']['original_order']); } // WC 2.1+ if (!WC_Subscriptions::is_woocommerce_pre('2.1')) { // Recurring coupons if ($applied_coupons = $woocommerce->cart->get_coupons()) { foreach ($applied_coupons as $code => $coupon) { if (!isset($woocommerce->cart->recurring_coupon_discount_amounts[$code])) { continue; } $item_id = woocommerce_add_order_item($order_id, array('order_item_name' => $code, 'order_item_type' => 'recurring_coupon')); // Add line item meta if ($item_id) { woocommerce_add_order_item_meta($item_id, 'discount_amount', isset($woocommerce->cart->recurring_coupon_discount_amounts[$code]) ? $woocommerce->cart->recurring_coupon_discount_amounts[$code] : 0); } } } // Add recurring shipping order items if (WC_Subscriptions_Cart::cart_contains_subscriptions_needing_shipping()) { $packages = $woocommerce->shipping->get_packages(); $checkout = $woocommerce->checkout(); foreach ($packages as $i => $package) { if (isset($package['rates'][$checkout->shipping_methods[$i]])) { $method = $package['rates'][$checkout->shipping_methods[$i]]; $item_id = woocommerce_add_order_item($order_id, array('order_item_name' => $method->label, 'order_item_type' => 'recurring_shipping')); if ($item_id) { woocommerce_add_order_item_meta($item_id, 'method_id', $method->id); woocommerce_add_order_item_meta($item_id, 'cost', WC_Subscriptions::format_total($method->cost)); woocommerce_add_order_item_meta($item_id, 'taxes', array_map('wc_format_decimal', $method->taxes)); do_action('woocommerce_subscriptions_add_recurring_shipping_order_item', $order_id, $item_id, $i); } } } } // Remove shipping on original order if it was added but is not required if (!WC_Subscriptions_Cart::charge_shipping_up_front()) { foreach ($order->get_shipping_methods() as $order_item_id => $shipping_method) { woocommerce_update_order_item_meta($order_item_id, 'cost', WC_Subscriptions::format_total(0)); } } } else { update_post_meta($order_id, '_recurring_shipping_method', get_post_meta($order_id, '_shipping_method', true), true); update_post_meta($order_id, '_recurring_shipping_method_title', get_post_meta($order_id, '_shipping_method_title', true), true); } } }
/** * Creates a new order for renewing a subscription product based on the details of a previous order. * * No trial periods or sign up fees are applied to the renewal order. However, if the order has failed * payments and the store manager has set failed payments to be added to renewal orders, then the * orders totals will be set to include the outstanding balance. * * If the $args['new_order_role'] flag is set to 'parent', then the renewal order will supersede the existing * order. The existing order and subscription associated with it will be cancelled. A new order and * subscription will be created. * * If the $args['new_order_role'] flag is 'child', the $original_order will remain the master order for the * subscription and the new order is just for accepting a recurring payment on the subscription. * * Renewal orders have the same meta data as the original order. If the renewal order is set to be a 'child' * then any subscription related meta data will not be stored on the new order. This is to keep subscription * meta data associated only with the one master order for the subscription. * * @param WC_Order|int $order The WC_Order object or ID of the order for which the a new order should be created. * @param string $product_id The ID of the subscription product in the order which needs to be added to the new order. * @param array $args (optional) An array of name => value flags: * 'new_order_role' string A flag to indicate whether the new order should become the master order for the subscription. Accepts either 'parent' or 'child'. Defaults to 'parent' - replace the existing order. * 'checkout_renewal' bool Indicates if invoked from an interactive cart/checkout session and certain order items are not set, like taxes, shipping as they need to be set in teh calling function, like @see WC_Subscriptions_Checkout::filter_woocommerce_create_order(). Default false. * 'failed_order_id' int For checkout_renewal true, indicates order id being replaced * @since 1.2 */ public static function generate_renewal_order($original_order, $product_id, $args = array()) { global $wpdb, $woocommerce; if (!is_object($original_order)) { $original_order = new WC_Order($original_order); } if (!WC_Subscriptions_Order::order_contains_subscription($original_order) || !WC_Subscriptions_Order::is_item_subscription($original_order, $product_id)) { return false; } if (self::is_renewal($original_order, array('order_role' => 'child'))) { $original_order = self::get_parent_order($original_order); } if (!is_array($args)) { _deprecated_argument(__CLASS__ . '::' . __FUNCTION__, '1.3', __('Third parameter is now an array of name => value pairs. Use array( "new_order_role" => "parent" ) instead.', 'woocommerce-subscriptions')); $args = array('new_order_role' => $args); } $args = wp_parse_args($args, array('new_order_role' => 'parent', 'checkout_renewal' => false)); $renewal_order_key = uniqid('order_'); // Create the new order $renewal_order_data = array('post_type' => 'shop_order', 'post_title' => sprintf(__('Subscription Renewal Order – %s', 'woocommerce-subscriptions'), strftime(_x('%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce-subscriptions'))), 'post_status' => 'publish', 'ping_status' => 'closed', 'post_excerpt' => $original_order->customer_note, 'post_author' => 1, 'post_password' => $renewal_order_key); $create_new_order = true; if ('child' == $args['new_order_role']) { $renewal_order_data['post_parent'] = $original_order->id; } if (true === $args['checkout_renewal']) { $renewal_order_id = null; if ($woocommerce->session->order_awaiting_payment > 0) { $renewal_order_id = absint($woocommerce->session->order_awaiting_payment); } elseif (isset($args['failed_order_id'])) { $failed_order_id = $args['failed_order_id']; /* Check order is unpaid by getting its status */ $terms = wp_get_object_terms($failed_order_id, 'shop_order_status', array('fields' => 'slugs')); $order_status = isset($terms[0]) ? $terms[0] : 'pending'; /* If paying on a pending order, we are resuming */ if ($order_status == 'pending') { $renewal_order_id = $failed_order_id; } } if ($renewal_order_id) { /* Check order is unpaid by getting its status */ $terms = wp_get_object_terms($renewal_order_id, 'shop_order_status', array('fields' => 'slugs')); $order_status = isset($terms[0]) ? $terms[0] : 'pending'; // Resume the unpaid order if its pending if ($order_status == 'pending' || $order_status == 'failed') { // Update the existing order as we are resuming it $create_new_order = false; $renewal_order_data['ID'] = $renewal_order_id; wp_update_post($renewal_order_data); // Clear the old line items - we'll add these again in case they changed $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $renewal_order_id)); $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $renewal_order_id)); } } } if ($create_new_order) { $renewal_order_id = wp_insert_post($renewal_order_data); } // Set the order as pending wp_set_object_terms($renewal_order_id, 'pending', 'shop_order_status'); // Set a unique key for this order update_post_meta($renewal_order_id, '_order_key', $renewal_order_key); $order_meta_query = "SELECT `meta_key`, `meta_value`\n\t\t\t\t\t\t\t FROM {$wpdb->postmeta}\n\t\t\t\t\t\t\t WHERE `post_id` = {$original_order->id}\n\t\t\t\t\t\t\t AND `meta_key` NOT IN ('_paid_date', '_completed_date', '_order_key', '_edit_lock', '_original_order')"; // Superseding existing order so don't carry over payment details if ('parent' == $args['new_order_role'] || true === $args['checkout_renewal']) { $order_meta_query .= " AND `meta_key` NOT IN ('_payment_method', '_payment_method_title', '_recurring_payment_method', '_recurring_payment_method_title', '_shipping_method', '_shipping_method_title', '_recurring_shipping_method', '_recurring_shipping_method_title')"; } else { $order_meta_query .= " AND `meta_key` NOT LIKE '_order_recurring_%' AND `meta_key` NOT IN ('_payment_method', '_payment_method_title', '_recurring_payment_method', '_recurring_payment_method_title', '_shipping_method', '_shipping_method_title', '_recurring_shipping_method', '_recurring_shipping_method_title')"; } // Allow extensions to add/remove order meta $order_meta_query = apply_filters('woocommerce_subscriptions_renewal_order_meta_query', $order_meta_query, $original_order->id, $renewal_order_id, $args['new_order_role']); // Carry all the required meta from the old order over to the new order $order_meta = $wpdb->get_results($order_meta_query, 'ARRAY_A'); $order_meta = apply_filters('woocommerce_subscriptions_renewal_order_meta', $order_meta, $original_order->id, $renewal_order_id, $args['new_order_role']); foreach ($order_meta as $meta_item) { add_post_meta($renewal_order_id, $meta_item['meta_key'], maybe_unserialize($meta_item['meta_value']), true); } $outstanding_balance = WC_Subscriptions_Order::get_outstanding_balance($original_order, $product_id); $failed_payment_multiplier = 1; if (false == $args['checkout_renewal']) { // If there are outstanding payment amounts, add them to the order, otherwise set the order details to the values of the recurring totals if ($outstanding_balance > 0 && 'yes' == get_option(WC_Subscriptions_Admin::$option_prefix . '_add_outstanding_balance', 'no')) { $failed_payment_multiplier = WC_Subscriptions_Order::get_failed_payment_count($original_order, $product_id); } // Set order totals based on recurring totals from the original order $cart_discount = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_discount_cart', true); $order_discount = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_discount_total', true); $order_shipping_tax = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_shipping_tax_total', true); $order_shipping = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_shipping_total', true); $order_tax = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_tax_total', true); $order_total = $failed_payment_multiplier * get_post_meta($original_order->id, '_order_recurring_total', true); update_post_meta($renewal_order_id, '_cart_discount', $cart_discount); update_post_meta($renewal_order_id, '_order_discount', $order_discount); update_post_meta($renewal_order_id, '_order_shipping_tax', $order_shipping_tax); update_post_meta($renewal_order_id, '_order_shipping', $order_shipping); update_post_meta($renewal_order_id, '_order_tax', $order_tax); update_post_meta($renewal_order_id, '_order_total', $order_total); // Set shipping for orders created with WC 2.0.n (or when we are using WC 2.0.n) if (WC_Subscriptions::is_woocommerce_pre_2_1() || isset($original_order->recurring_shipping_method)) { update_post_meta($renewal_order_id, '_shipping_method', $original_order->recurring_shipping_method); update_post_meta($renewal_order_id, '_shipping_method_title', $original_order->recurring_shipping_method_title); // Also set recurring shipping as it's a parent renewal order if ('parent' == $args['new_order_role']) { update_post_meta($renewal_order_id, '_recurring_shipping_method', $original_order->recurring_shipping_method); update_post_meta($renewal_order_id, '_recurring_shipping_method_title', $original_order->recurring_shipping_method_title); } } // Apply the recurring shipping & payment methods to child renewal orders if ('child' == $args['new_order_role']) { update_post_meta($renewal_order_id, '_payment_method', $original_order->recurring_payment_method); update_post_meta($renewal_order_id, '_payment_method_title', $original_order->recurring_payment_method_title); } // Set order taxes based on recurring taxes from the original order $recurring_order_taxes = WC_Subscriptions_Order::get_recurring_taxes($original_order); foreach ($recurring_order_taxes as $index => $recurring_order_tax) { $item_ids = array(); $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_order_tax['name'], 'order_item_type' => 'tax')); // Also set recurring taxes on parent renewal orders if ('parent' == $args['new_order_role']) { $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_order_tax['name'], 'order_item_type' => 'recurring_tax')); } // Add line item meta foreach ($item_ids as $item_id) { woocommerce_add_order_item_meta($item_id, 'compound', absint(isset($recurring_order_tax['compound']) ? $recurring_order_tax['compound'] : 0)); woocommerce_add_order_item_meta($item_id, 'tax_amount', woocommerce_clean($failed_payment_multiplier * $recurring_order_tax['tax_amount'])); woocommerce_add_order_item_meta($item_id, 'shipping_tax_amount', woocommerce_clean($failed_payment_multiplier * $recurring_order_tax['shipping_tax_amount'])); if (isset($recurring_order_tax['rate_id'])) { woocommerce_add_order_item_meta($item_id, 'rate_id', $recurring_order_tax['rate_id']); } if (isset($recurring_order_tax['label'])) { woocommerce_add_order_item_meta($item_id, 'label', $recurring_order_tax['label']); } } } // Set up shipping items on renewal order $recurring_shipping_items = WC_Subscriptions_Order::get_recurring_shipping_methods($original_order); foreach ($recurring_shipping_items as $index => $recurring_shipping_item) { $item_ids = array(); $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_shipping_item['name'], 'order_item_type' => 'shipping')); // Also set recurring shipping as it's a parent renewal order if ('parent' == $args['new_order_role']) { $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_shipping_item['name'], 'order_item_type' => 'recurring_shipping')); } // Add shipping item meta foreach ($item_ids as $item_id) { woocommerce_add_order_item_meta($item_id, 'method_id', $recurring_shipping_item['method_id']); woocommerce_add_order_item_meta($item_id, 'cost', woocommerce_clean($failed_payment_multiplier * $recurring_shipping_item['cost'])); } } } // Set line totals to be recurring line totals and remove the subscription/recurring related item meta from each order item $order_items = WC_Subscriptions_Order::get_recurring_items($original_order); // Allow extensions to add/remove items or item meta $order_items = apply_filters('woocommerce_subscriptions_renewal_order_items', $order_items, $original_order->id, $renewal_order_id, $product_id, $args['new_order_role']); if (true === $args['checkout_renewal']) { $cart_items = $woocommerce->cart->get_cart(); } foreach ($order_items as $item_index => $order_item) { if ('child' == $args['new_order_role']) { $renewal_order_item_name = sprintf(__('Renewal of "%s" purchased in Order %s', 'woocommerce-subscriptions'), $order_item['name'], $original_order->get_order_number()); } else { $renewal_order_item_name = $order_item['name']; } $renewal_order_item_name = apply_filters('woocommerce_subscriptions_renewal_order_item_name', $renewal_order_item_name, $order_item, $original_order); // Create order line item on the renewal order $recurring_item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $renewal_order_item_name, 'order_item_type' => 'line_item')); if (true === $args['checkout_renewal']) { $cart_item = array(); foreach ($cart_items as $item) { if ($item['product_id'] == $order_item['product_id'] && (empty($order_item['variation_id']) || $item['variation_id'] == $order_item['variation_id'])) { $cart_item = $item; } } if (!empty($cart_item)) { woocommerce_update_order_item_meta($recurring_item_id, '_line_total', woocommerce_format_decimal($cart_item['line_total'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_tax', woocommerce_format_decimal($cart_item['line_tax'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal', woocommerce_format_decimal($cart_item['line_subtotal'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal_tax', woocommerce_format_decimal($cart_item['line_subtotal_tax'])); if (is_object($cart_item['data'])) { woocommerce_update_order_item_meta($recurring_item_id, '_tax_class', $cart_item['data']->get_tax_class()); } } $cart_items = $woocommerce->cart->get_cart(); } $item_meta = new WC_Order_Item_Meta($order_item['item_meta']); // Remove recurring line items and set item totals based on recurring line totals foreach ($item_meta->meta as $meta_key => $meta) { // $meta is an array, so the item needs to be extracted from $meta[0] (just like order meta on a WC Order) $meta_value = $meta[0]; if (false === $args['checkout_renewal']) { // Already set earlier // Map line item totals based on recurring line totals switch ($meta_key) { case '_recurring_line_total': woocommerce_delete_order_item_meta($recurring_item_id, '_line_total'); woocommerce_update_order_item_meta($recurring_item_id, '_line_total', woocommerce_format_decimal($failed_payment_multiplier * $meta_value)); break; case '_recurring_line_tax': woocommerce_delete_order_item_meta($recurring_item_id, '_line_tax'); woocommerce_update_order_item_meta($recurring_item_id, '_line_tax', woocommerce_format_decimal($failed_payment_multiplier * $meta_value)); break; case '_recurring_line_subtotal': woocommerce_delete_order_item_meta($recurring_item_id, '_line_subtotal'); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal', woocommerce_format_decimal($failed_payment_multiplier * $meta_value)); break; case '_recurring_line_subtotal_tax': woocommerce_delete_order_item_meta($recurring_item_id, '_line_subtotal_tax'); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal_tax', woocommerce_format_decimal($failed_payment_multiplier * $meta_value)); break; default: break; } } // Copy over line item meta data, with some parent/child role based exceptions for recurring amounts $copy_to_renewal_item = true; switch ($meta_key) { case '_recurring_line_total': case '_recurring_line_tax': case '_recurring_line_subtotal': case '_recurring_line_subtotal_tax': case '_subscription_recurring_amount': case '_subscription_sign_up_fee': case '_subscription_period': case '_subscription_interval': case '_subscription_length': case '_subscription_trial_period': case '_subscription_end_date': case '_subscription_expiry_date': case '_subscription_start_date': case '_subscription_status': case '_subscription_completed_payments': if ('child' == $args['new_order_role']) { $copy_to_renewal_item = false; } break; case '_subscription_trial_length': // We never want to duplicate free trials on renewal orders $copy_to_renewal_item = false; break; case '_subscription_suspension_count': // We want to reset some values for the new order // We want to reset some values for the new order case '_subscription_trial_expiry_date': case '_subscription_failed_payments': $copy_to_renewal_item = false; $meta_value = 0; break; default: break; } // Copy existing item over to new recurring order item if ($copy_to_renewal_item) { woocommerce_add_order_item_meta($recurring_item_id, $meta_key, $meta_value); } } } if (false == $args['checkout_renewal']) { // Add fees foreach ($original_order->get_fees() as $item_id => $order_fee) { if (!isset($order_fee['recurring_line_total'])) { continue; } $item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $order_fee['name'], 'order_item_type' => 'fee')); woocommerce_add_order_item_meta($item_id, '_tax_class', $order_fee['tax_class']); woocommerce_add_order_item_meta($item_id, '_line_total', WC_Subscriptions::format_total($order_fee['recurring_line_total'])); woocommerce_add_order_item_meta($item_id, '_line_tax', WC_Subscriptions::format_total($order_fee['recurring_line_tax'])); } } // Keep a record of the original order's ID on the renewal order update_post_meta($renewal_order_id, '_original_order', $original_order->id, true); $renewal_order = new WC_Order($renewal_order_id); if ('parent' == $args['new_order_role']) { WC_Subscriptions_Manager::process_subscriptions_on_checkout($renewal_order_id); $original_order->add_order_note(sprintf(__('Order superseded by Renewal Order %s.', 'woocommerce-subscriptions'), $renewal_order->get_order_number())); } do_action('woocommerce_subscriptions_renewal_order_created', $renewal_order, $original_order, $product_id, $args['new_order_role']); return apply_filters('woocommerce_subscriptions_renewal_order_id', $renewal_order_id, $original_order, $product_id, $args['new_order_role']); }
/** * When an order is added or updated from the admin interface, check if a subscription product * has been manually added to the order or the details of the subscription have been modified, * and create/update the subscription as required. * * Save subscription order meta items * * @param int $post_id The ID of the post which is the WC_Order object. * @param Object $post The post object of the order. * @since 1.1 */ public static function pre_process_shop_order_meta($post_id, $post) { global $woocommerce, $wpdb; $order_contains_subscription = false; $order = new WC_Order($post_id); $existing_product_ids = array(); foreach ($order->get_items() as $existing_item) { $existing_product_ids[] = self::get_items_product_id($existing_item); } $product_ids = array(); if (isset($_POST['order_item_id'])) { foreach ($_POST['order_item_id'] as $order_item_id) { $product_ids[$order_item_id] = woocommerce_get_order_item_meta($order_item_id, '_product_id'); } } // Check if there are new subscription products to be added, or the order already has a subscription item foreach (array_merge($product_ids, $existing_product_ids) as $order_item_id => $product_id) { $is_existing_item = false; if (in_array($product_id, $existing_product_ids)) { $is_existing_item = true; } // If this is a new item and it's a subscription product, we have a subscription if (!$is_existing_item && WC_Subscriptions_Product::is_subscription($product_id)) { $order_contains_subscription = true; } // If this is an existing item and it's a subscription item, we have a subscription if ($is_existing_item && self::is_item_subscription($order, $product_id)) { $order_contains_subscription = true; } } if (!$order_contains_subscription) { return $post_id; } $existing_payment_method = get_post_meta($post_id, '_recurring_payment_method', true); $chosen_payment_method = isset($_POST['_recurring_payment_method']) ? stripslashes($_POST['_recurring_payment_method']) : ''; // If the recurring payment method is changing, or it isn't set make sure we have correct manual payment flag set if (isset($_POST['_recurring_payment_method']) || empty($existing_payment_method) && ($chosen_payment_method != $existing_payment_method || empty($chosen_payment_method))) { $payment_gateways = $woocommerce->payment_gateways->payment_gateways(); // Make sure the subscription is cancelled with the current gateway if (!empty($existing_payment_method) && isset($payment_gateways[$existing_payment_method]) && $payment_gateways[$existing_payment_method]->supports('subscriptions')) { foreach ($product_ids as $product_id) { WC_Subscriptions_Payment_Gateways::trigger_gateway_cancelled_subscription_hook(absint($_POST['customer_user']), WC_Subscriptions_Manager::get_subscription_key($post_id, $product_id)); } } if (!empty($chosen_payment_method) && isset($payment_gateways[$chosen_payment_method]) && $payment_gateways[$chosen_payment_method]->supports('subscriptions')) { $manual_renewal = 'false'; } else { $manual_renewal = 'true'; } update_post_meta($post_id, '_wcs_requires_manual_renewal', $manual_renewal); if (!empty($chosen_payment_method)) { update_post_meta($post_id, '_recurring_payment_method', stripslashes($_POST['_recurring_payment_method'])); } } // Make sure the recurring order totals are correct update_post_meta($post_id, '_order_recurring_discount_total', stripslashes($_POST['_order_recurring_discount_total'])); update_post_meta($post_id, '_order_recurring_total', stripslashes($_POST['_order_recurring_total'])); // Update fields for WC < 2.1 if (WC_Subscriptions::is_woocommerce_pre_2_1()) { // Also allow updates to the recurring payment method's title if (isset($_POST['_recurring_payment_method_title'])) { update_post_meta($post_id, '_recurring_payment_method_title', stripslashes($_POST['_recurring_payment_method_title'])); } else { // it's been deleted update_post_meta($post_id, '_recurring_payment_method_title', ''); } if (isset($_POST['_order_recurring_discount_cart'])) { update_post_meta($post_id, '_order_recurring_discount_cart', stripslashes($_POST['_order_recurring_discount_cart'])); } else { // it's been deleted update_post_meta($post_id, '_order_recurring_discount_cart', 0); } if (isset($_POST['_order_recurring_tax_total'])) { // WC < 2.1 update_post_meta($post_id, '_order_recurring_tax_total', stripslashes($_POST['_order_recurring_tax_total'])); } else { // it's been deleted update_post_meta($post_id, '_order_recurring_tax_total', 0); } if (isset($_POST['_order_recurring_shipping_tax_total'])) { update_post_meta($post_id, '_order_recurring_shipping_tax_total', stripslashes($_POST['_order_recurring_shipping_tax_total'])); } else { // it's been deleted update_post_meta($post_id, '_order_recurring_shipping_tax_total', 0); } if (isset($_POST['_order_recurring_shipping_total'])) { update_post_meta($post_id, '_order_recurring_shipping_total', stripslashes($_POST['_order_recurring_shipping_total'])); } else { // it's been deleted update_post_meta($post_id, '_order_recurring_shipping_total', 0); } } // Save tax rows $total_tax = 0; $total_shipping_tax = 0; if (isset($_POST['recurring_order_taxes_id'])) { // WC 2.0+ $tax_keys = array('recurring_order_taxes_id', 'recurring_order_taxes_rate_id', 'recurring_order_taxes_amount', 'recurring_order_taxes_shipping_amount'); foreach ($tax_keys as $tax_key) { ${$tax_key} = isset($_POST[$tax_key]) ? $_POST[$tax_key] : array(); } foreach ($recurring_order_taxes_id as $item_id => $value) { $item_id = absint($item_id); $rate_id = absint($recurring_order_taxes_rate_id[$item_id]); if ($rate_id) { $rate = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $rate_id)); $label = $rate->tax_rate_name ? $rate->tax_rate_name : $woocommerce->countries->tax_or_vat(); $compound = $rate->tax_rate_compound ? 1 : 0; $code = array(); $code[] = $rate->tax_rate_country; $code[] = $rate->tax_rate_state; $code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX'; $code[] = absint($rate->tax_rate_priority); $code = strtoupper(implode('-', array_filter($code))); } else { $code = ''; $label = $woocommerce->countries->tax_or_vat(); } $wpdb->update($wpdb->prefix . "woocommerce_order_items", array('order_item_name' => woocommerce_clean($code)), array('order_item_id' => $item_id), array('%s'), array('%d')); woocommerce_update_order_item_meta($item_id, 'rate_id', $rate_id); woocommerce_update_order_item_meta($item_id, 'label', $label); woocommerce_update_order_item_meta($item_id, 'compound', $compound); if (isset($recurring_order_taxes_amount[$item_id])) { woocommerce_update_order_item_meta($item_id, 'tax_amount', woocommerce_clean($recurring_order_taxes_amount[$item_id])); $total_tax += WC_Subscriptions::format_total($recurring_order_taxes_amount[$item_id]); } if (isset($recurring_order_taxes_shipping_amount[$item_id])) { woocommerce_update_order_item_meta($item_id, 'shipping_tax_amount', woocommerce_clean($recurring_order_taxes_shipping_amount[$item_id])); $total_shipping_tax += WC_Subscriptions::format_total($recurring_order_taxes_shipping_amount[$item_id]); } } } if (!isset($_POST['_order_recurring_tax_total']) && function_exists('wc_round_tax_total')) { // WC 2.1+ update_post_meta($post_id, '_order_recurring_tax_total', wc_round_tax_total($total_tax)); } if (!isset($_POST['_order_recurring_shipping_tax_total']) && function_exists('wc_round_tax_total')) { // WC 2.1+ update_post_meta($post_id, '_order_recurring_shipping_tax_total', wc_round_tax_total($total_shipping_tax)); } // And that shipping methods are updated as required if (isset($_POST['_recurring_shipping_method']) || isset($_POST['_recurring_shipping_method_title'])) { // WC < 2.1 update_post_meta($post_id, '_recurring_shipping_method', stripslashes($_POST['_recurring_shipping_method'])); update_post_meta($post_id, '_recurring_shipping_method_title', stripslashes($_POST['_recurring_shipping_method_title'])); } // Shipping Rows $recurring_order_shipping = 0; if (isset($_POST['recurring_shipping_method_id'])) { // WC 2.1+ $get_values = array('recurring_shipping_method_id', 'recurring_shipping_method_title', 'recurring_shipping_method', 'recurring_shipping_cost'); foreach ($get_values as $value) { ${$value} = isset($_POST[$value]) ? $_POST[$value] : array(); } foreach ($recurring_shipping_method_id as $item_id => $value) { if ('new' == $item_id) { foreach ($value as $new_key => $new_value) { $method_id = woocommerce_clean($recurring_shipping_method[$item_id][$new_key]); $method_title = woocommerce_clean($recurring_shipping_method_title[$item_id][$new_key]); $cost = WC_Subscriptions::format_total($recurring_shipping_cost[$item_id][$new_key]); $new_id = woocommerce_add_order_item($post_id, array('order_item_name' => $method_title, 'order_item_type' => 'recurring_shipping')); if ($new_id) { woocommerce_add_order_item_meta($new_id, 'method_id', $method_id); woocommerce_add_order_item_meta($new_id, 'cost', $cost); } $recurring_order_shipping += $cost; } } elseif ('old' == $item_id) { // Migrate a WC 2.0.n shipping method to WC 2.1 format $method_id = woocommerce_clean($recurring_shipping_method[$item_id]); $method_title = woocommerce_clean($recurring_shipping_method_title[$item_id]); $cost = WC_Subscriptions::format_total($recurring_shipping_cost[$item_id]); $new_id = woocommerce_add_order_item($post_id, array('order_item_name' => $method_title, 'order_item_type' => 'recurring_shipping')); if ($new_id) { woocommerce_add_order_item_meta($new_id, 'method_id', $method_id); woocommerce_add_order_item_meta($new_id, 'cost', $cost); } $recurring_order_shipping += $cost; delete_post_meta($post_id, '_recurring_shipping_method'); delete_post_meta($post_id, '_recurring_shipping_method_title'); } else { $item_id = absint($item_id); $method_id = woocommerce_clean($recurring_shipping_method[$item_id]); $method_title = woocommerce_clean($recurring_shipping_method_title[$item_id]); $cost = WC_Subscriptions::format_total($recurring_shipping_cost[$item_id]); $wpdb->update($wpdb->prefix . "woocommerce_order_items", array('order_item_name' => $method_title), array('order_item_id' => $item_id), array('%s'), array('%d')); woocommerce_update_order_item_meta($item_id, 'method_id', $method_id); woocommerce_update_order_item_meta($item_id, 'cost', $cost); $recurring_order_shipping += $cost; } } } if (!isset($_POST['_order_recurring_shipping_total'])) { // WC 2.1+ update_post_meta($post_id, '_order_recurring_shipping_total', $recurring_order_shipping); } // Check if all the subscription products on the order have associated subscriptions on the user's account, and if not, add a new one foreach ($product_ids as $order_item_id => $product_id) { $is_existing_item = false; if (in_array($product_id, $existing_product_ids)) { $is_existing_item = true; } // If this is a new item and it's not a subscription product, ignore it if (!$is_existing_item && !WC_Subscriptions_Product::is_subscription($product_id)) { continue; } // If this is an existing item and it's not a subscription, ignore it if ($is_existing_item && !self::is_item_subscription($order, $product_id)) { continue; } $subscription_key = WC_Subscriptions_Manager::get_subscription_key($post_id, $product_id); $subscription = array(); // In case it's a new order or the customer has changed $order->customer_user = $order->user_id = (int) $_POST['customer_user']; $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key); if (empty($subscription)) { // Add a new subscription // The order may not exist yet, so we need to set a few things ourselves if (empty($order->order_key)) { $order->order_key = uniqid('order_'); add_post_meta($post_id, '_order_key', $order->order_key, true); } if (empty($_POST['order_date'])) { $start_date = gmdate('Y-m-d H:i:s'); } else { $start_date = get_gmt_from_date($_POST['order_date'] . ' ' . (int) $_POST['order_date_hour'] . ':' . (int) $_POST['order_date_minute'] . ':00'); } WC_Subscriptions_Manager::create_pending_subscription_for_order($order, $product_id, array('start_date' => $start_date)); // Add the subscription meta for this item to the order $functions_and_meta = array('get_period' => '_order_subscription_periods', 'get_interval' => '_order_subscription_intervals', 'get_length' => '_order_subscription_lengths'); foreach ($functions_and_meta as $function_name => $meta_key) { $subscription_meta = self::get_meta($order, $meta_key, array()); $subscription_meta[$product_id] = WC_Subscriptions_Product::$function_name($product_id); update_post_meta($order->id, $meta_key, $subscription_meta); } // This works because meta is added when the item is added via Ajax self::process_shop_order_item_meta($post_id, $post); // If the order's existing status is something other than pending and the order status is not being changed, manually set the subscription's status (otherwise, it will be handled when WC transitions the order's status) if ($order->status == $_POST['order_status'] && 'pending' != $order->status) { switch ($order->status) { case 'completed': case 'processing': WC_Subscriptions_Manager::activate_subscription($order->customer_user, $subscription_key); break; case 'refunded': case 'cancelled': WC_Subscriptions_Manager::cancel_subscription($order->customer_user, $subscription_key); break; case 'failed': WC_Subscriptions_Manager::failed_subscription_signup($order->customer_user, $subscription_key); break; } } } } // Determine whether we need to update any subscription dates for existing subscriptions (before the item meta is updated) if (!empty($product_ids)) { $start_date = $_POST['order_date'] . ' ' . (int) $_POST['order_date_hour'] . ':' . (int) $_POST['order_date_minute'] . ':00'; // Start date changed for an existing order if (!empty($order->order_date) && $order->order_date != $start_date) { self::$requires_update['expiration_date'] = array_values($product_ids); self::$requires_update['trial_expiration'] = array_values($product_ids); self::$requires_update['next_billing_date'] = array_values($product_ids); } elseif (isset($_POST['meta_key'])) { $item_meta_keys = isset($_POST['meta_key']) ? $_POST['meta_key'] : array(); $new_meta_values = isset($_POST['meta_value']) ? $_POST['meta_value'] : array(); foreach ($item_meta_keys as $item_meta_id => $meta_key) { $meta_data = self::get_item_meta_data($item_meta_id); $product_id = woocommerce_get_order_item_meta($meta_data->order_item_id, '_product_id'); // Set flags to update payment dates if required switch ($meta_key) { case '_subscription_period': case '_subscription_interval': if ($new_meta_values[$item_meta_id] != $meta_data->meta_value) { self::$requires_update['next_billing_date'][] = $product_id; } break; case '_subscription_start_date': case '_subscription_trial_length': case '_subscription_trial_period': if ($new_meta_values[$item_meta_id] != $meta_data->meta_value) { self::$requires_update['expiration_date'][] = $product_id; self::$requires_update['trial_expiration'][] = $product_id; self::$requires_update['next_billing_date'][] = $product_id; } break; case '_subscription_length': if ($new_meta_values[$item_meta_id] != $meta_data->meta_value) { self::$requires_update['expiration_date'][] = $product_id; self::$requires_update['next_billing_date'][] = $product_id; } break; case '_subscription_trial_expiry_date': if ($new_meta_values[$item_meta_id] != $meta_data->meta_value) { self::$requires_update['trial_expiration'][] = $product_id; } break; case '_subscription_expiry_date': if ($new_meta_values[$item_meta_id] != $meta_data->meta_value) { self::$requires_update['expiration_date'][] = $product_id; } break; } } } } }
/** * Version 1.2 introduced a massive change to the order meta data schema. This function goes * through and upgrades the existing data on all orders to the new schema. * * The upgrade process is timeout safe as it keeps a record of the orders upgraded and only * deletes this record once all orders have been upgraded successfully. If operating on a huge * number of orders and the upgrade process times out, only the orders not already upgraded * will be upgraded in future requests that trigger this function. * * @since 1.2 */ private static function upgrade_database_to_1_2() { global $wpdb; // Get IDs only and use a direct DB query for efficiency $orders_to_upgrade = $wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE post_type = 'shop_order' AND post_parent = 0"); $upgraded_orders = get_option('wcs_1_2_upgraded_order_ids', array()); // Transition deprecated subscription status if we aren't in the middle of updating orders if (empty($upgraded_orders)) { $wpdb->query($wpdb->prepare("UPDATE {$wpdb->usermeta} SET meta_value = replace( meta_value, 's:9:\"suspended\"', 's:7:\"on-hold\"' ) WHERE meta_key LIKE %s", '%_woocommerce_subscriptions')); $wpdb->query($wpdb->prepare("UPDATE {$wpdb->usermeta} SET meta_value = replace( meta_value, 's:6:\"failed\"', 's:9:\"cancelled\"' ) WHERE meta_key LIKE %s", '%_woocommerce_subscriptions')); } $orders_to_upgrade = array_diff($orders_to_upgrade, $upgraded_orders); // Upgrade all _sign_up_{field} order meta to new order data format foreach ($orders_to_upgrade as $order_id) { $order = new WC_Order($order_id); // Manually check if a product in an order is a subscription, we can't use WC_Subscriptions_Order::order_contains_subscription( $order ) because it relies on the new data structure $contains_subscription = false; foreach ($order->get_items() as $order_item) { if (WC_Subscriptions_Product::is_subscription(WC_Subscriptions_Order::get_items_product_id($order_item))) { $contains_subscription = true; break; } } if (!$contains_subscription) { continue; } $trial_lengths = WC_Subscriptions_Order::get_meta($order, '_order_subscription_trial_lengths', array()); $trial_length = array_pop($trial_lengths); $has_trial = !empty($trial_length) && $trial_length > 0 ? true : false; $sign_up_fee_total = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_total', 0); // Create recurring_* meta data from existing cart totals $cart_discount = $order->get_cart_discount(); update_post_meta($order_id, '_order_recurring_discount_cart', $cart_discount); $order_discount = $order->get_order_discount(); update_post_meta($order_id, '_order_recurring_discount_total', $order_discount); $order_shipping_tax = get_post_meta($order_id, '_order_shipping_tax', true); update_post_meta($order_id, '_order_recurring_shipping_tax_total', $order_shipping_tax); $order_tax = get_post_meta($order_id, '_order_tax', true); // $order->get_total_tax() includes shipping tax update_post_meta($order_id, '_order_recurring_tax_total', $order_tax); $order_total = $order->get_total(); update_post_meta($order_id, '_order_recurring_total', $order_total); // Set order totals to include sign up fee fields, if there was a sign up fee on the order and a trial period (other wise, the recurring totals are correct) if ($sign_up_fee_total > 0) { // Order totals need to be changed to be equal to sign up fee totals if ($has_trial) { $cart_discount = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_discount_cart', 0); $order_discount = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_discount_total', 0); $order_tax = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_tax_total', 0); $order_total = $sign_up_fee_total; } else { // No trial, sign up fees need to be added to order totals $cart_discount += WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_discount_cart', 0); $order_discount += WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_discount_total', 0); $order_tax += WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_tax_total', 0); $order_total += $sign_up_fee_total; } update_post_meta($order_id, '_order_total', $order_total); update_post_meta($order_id, '_cart_discount', $cart_discount); update_post_meta($order_id, '_order_discount', $order_discount); update_post_meta($order_id, '_order_tax', $order_tax); } // Make sure we get order taxes in WC 1.x format if (false == self::$is_wc_version_2) { $order_taxes = $order->get_taxes(); } else { $order_tax_row = $wpdb->get_row($wpdb->prepare("\n\t\t\t\t\tSELECT * FROM {$wpdb->postmeta}\n\t\t\t\t\tWHERE meta_key = '_order_taxes_old'\n\t\t\t\t\tAND post_id = %s\n\t\t\t\t\t", $order_id)); $order_taxes = (array) maybe_unserialize($order_tax_row->meta_value); } // Set recurring taxes to order taxes, if using WC 2.0, this will be migrated to the new format in @see self::upgrade_to_latest_wc() update_post_meta($order_id, '_order_recurring_taxes', $order_taxes); $sign_up_fee_taxes = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_taxes', array()); // Update order taxes to include sign up fee taxes foreach ($sign_up_fee_taxes as $index => $sign_up_tax) { if ($has_trial && $sign_up_fee_total > 0) { // Order taxes need to be set to the same as the sign up fee taxes if (isset($sign_up_tax['cart_tax']) && $sign_up_tax['cart_tax'] > 0) { $order_taxes[$index]['cart_tax'] = $sign_up_tax['cart_tax']; } } elseif (!$has_trial && $sign_up_fee_total > 0) { // Sign up fee taxes need to be added to order taxes if (isset($sign_up_tax['cart_tax']) && $sign_up_tax['cart_tax'] > 0) { $order_taxes[$index]['cart_tax'] += $sign_up_tax['cart_tax']; } } } if (false == self::$is_wc_version_2) { // Doing it right: updated Subs *before* updating WooCommerce, the WooCommerce updater will take care of data migration update_post_meta($order_id, '_order_taxes', $order_taxes); } else { // Doing it wrong: updated Subs *after* updating WooCommerce, need to store in WC2.0 tax structure $index = 0; $new_order_taxes = $order->get_taxes(); foreach ($new_order_taxes as $item_id => $order_tax) { $index = $index + 1; if (!isset($order_taxes[$index]['label']) || !isset($order_taxes[$index]['cart_tax']) || !isset($order_taxes[$index]['shipping_tax'])) { continue; } // Add line item meta if ($item_id) { woocommerce_update_order_item_meta($item_id, 'compound', absint(isset($order_taxes[$index]['compound']) ? $order_taxes[$index]['compound'] : 0)); woocommerce_update_order_item_meta($item_id, 'tax_amount', WC_Subscriptions::format_total($order_taxes[$index]['cart_tax'])); woocommerce_update_order_item_meta($item_id, 'shipping_tax_amount', WC_Subscriptions::format_total($order_taxes[$index]['shipping_tax'])); } } } /* Upgrade each order item to use new Item Meta schema */ $order_subscription_periods = WC_Subscriptions_Order::get_meta($order_id, '_order_subscription_periods', array()); $order_subscription_intervals = WC_Subscriptions_Order::get_meta($order_id, '_order_subscription_intervals', array()); $order_subscription_lengths = WC_Subscriptions_Order::get_meta($order_id, '_order_subscription_lengths', array()); $order_subscription_trial_lengths = WC_Subscriptions_Order::get_meta($order_id, '_order_subscription_trial_lengths', array()); $order_items = $order->get_items(); foreach ($order_items as $index => $order_item) { $product_id = WC_Subscriptions_Order::get_items_product_id($order_item); if (WC_Subscriptions::is_woocommerce_pre('2.4')) { $item_meta = new WC_Order_Item_Meta($order_item['item_meta']); } else { $item_meta = new WC_Order_Item_Meta($order_item); } $subscription_interval = isset($order_subscription_intervals[$product_id]) ? $order_subscription_intervals[$product_id] : 1; $subscription_length = isset($order_subscription_lengths[$product_id]) ? $order_subscription_lengths[$product_id] : 0; $subscription_trial_length = isset($order_subscription_trial_lengths[$product_id]) ? $order_subscription_trial_lengths[$product_id] : 0; $subscription_sign_up_fee = WC_Subscriptions_Order::get_meta($order, '_cart_contents_sign_up_fee_total', 0); if ($sign_up_fee_total > 0) { // Discounted price * Quantity $sign_up_fee_line_total = WC_Subscriptions_Order::get_meta($order, '_cart_contents_sign_up_fee_total', 0); $sign_up_fee_line_tax = WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_tax_total', 0); // Base price * Quantity $sign_up_fee_line_subtotal = WC_Subscriptions_Order::get_meta($order, '_cart_contents_sign_up_fee_total', 0) + WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_discount_cart', 0); $sign_up_fee_propotion = $sign_up_fee_line_total > 0 ? $sign_up_fee_line_subtotal / $sign_up_fee_line_total : 0; $sign_up_fee_line_subtotal_tax = WC_Subscriptions_Manager::get_amount_from_proportion(WC_Subscriptions_Order::get_meta($order, '_sign_up_fee_tax_total', 0), $sign_up_fee_propotion); if ($has_trial) { // Set line item totals equal to sign up fee totals $order_item['line_subtotal'] = $sign_up_fee_line_subtotal; $order_item['line_subtotal_tax'] = $sign_up_fee_line_subtotal_tax; $order_item['line_total'] = $sign_up_fee_line_total; $order_item['line_tax'] = $sign_up_fee_line_tax; } else { // No trial period, sign up fees need to be added to order totals $order_item['line_subtotal'] += $sign_up_fee_line_subtotal; $order_item['line_subtotal_tax'] += $sign_up_fee_line_subtotal_tax; $order_item['line_total'] += $sign_up_fee_line_total; $order_item['line_tax'] += $sign_up_fee_line_tax; } } // Upgrading with WC 1.x if (method_exists($item_meta, 'add')) { $item_meta->add('_subscription_period', $order_subscription_periods[$product_id]); $item_meta->add('_subscription_interval', $subscription_interval); $item_meta->add('_subscription_length', $subscription_length); $item_meta->add('_subscription_trial_length', $subscription_trial_length); $item_meta->add('_subscription_recurring_amount', $order_item['line_subtotal']); // WC_Subscriptions_Product::get_price() would return a price without filters applied $item_meta->add('_subscription_sign_up_fee', $subscription_sign_up_fee); // Set recurring amounts for the item $item_meta->add('_recurring_line_total', $order_item['line_total']); $item_meta->add('_recurring_line_tax', $order_item['line_tax']); $item_meta->add('_recurring_line_subtotal', $order_item['line_subtotal']); $item_meta->add('_recurring_line_subtotal_tax', $order_item['line_subtotal_tax']); $order_item['item_meta'] = $item_meta->meta; $order_items[$index] = $order_item; } else { // Ignoring all advice, upgrading 4 months after version 1.2 was released, and doing it with WC 2.0 installed woocommerce_add_order_item_meta($index, '_subscription_period', $order_subscription_periods[$product_id]); woocommerce_add_order_item_meta($index, '_subscription_interval', $subscription_interval); woocommerce_add_order_item_meta($index, '_subscription_length', $subscription_length); woocommerce_add_order_item_meta($index, '_subscription_trial_length', $subscription_trial_length); woocommerce_add_order_item_meta($index, '_subscription_trial_period', $order_subscription_periods[$product_id]); woocommerce_add_order_item_meta($index, '_subscription_recurring_amount', $order_item['line_subtotal']); woocommerce_add_order_item_meta($index, '_subscription_sign_up_fee', $subscription_sign_up_fee); // Calculated recurring amounts for the item woocommerce_add_order_item_meta($index, '_recurring_line_total', $order_item['line_total']); woocommerce_add_order_item_meta($index, '_recurring_line_tax', $order_item['line_tax']); woocommerce_add_order_item_meta($index, '_recurring_line_subtotal', $order_item['line_subtotal']); woocommerce_add_order_item_meta($index, '_recurring_line_subtotal_tax', $order_item['line_subtotal_tax']); if ($sign_up_fee_total > 0) { // Order totals have changed woocommerce_update_order_item_meta($index, '_line_subtotal', woocommerce_format_decimal($order_item['line_subtotal'])); woocommerce_update_order_item_meta($index, '_line_subtotal_tax', woocommerce_format_decimal($order_item['line_subtotal_tax'])); woocommerce_update_order_item_meta($index, '_line_total', woocommerce_format_decimal($order_item['line_total'])); woocommerce_update_order_item_meta($index, '_line_tax', woocommerce_format_decimal($order_item['line_tax'])); } } } // Save the new meta on the order items for WC 1.x (the API functions already saved the data for WC2.x) if (false == self::$is_wc_version_2) { update_post_meta($order_id, '_order_items', $order_items); } $upgraded_orders[] = $order_id; update_option('wcs_1_2_upgraded_order_ids', $upgraded_orders); } }
/** * Creates a new order for renewing a subscription product based on the details of a previous order. * * No trial periods or sign up fees are applied to the renewal order. However, if the order has failed * payments and the store manager has set failed payments to be added to renewal orders, then the * orders totals will be set to include the outstanding balance. * * If the $args['new_order_role'] flag is set to 'parent', then the renewal order will supersede the existing * order. The existing order and subscription associated with it will be cancelled. A new order and * subscription will be created. * * If the $args['new_order_role'] flag is 'child', the $original_order will remain the master order for the * subscription and the new order is just for accepting a recurring payment on the subscription. * * Renewal orders have the same meta data as the original order. If the renewal order is set to be a 'child' * then any subscription related meta data will not be stored on the new order. This is to keep subscription * meta data associated only with the one master order for the subscription. * * @param WC_Order|int $order The WC_Order object or ID of the order for which the a new order should be created. * @param string $product_id The ID of the subscription product in the order which needs to be added to the new order. * @param array $args (optional) An array of name => value flags: * 'new_order_role' string A flag to indicate whether the new order should become the master order for the subscription. Accepts either 'parent' or 'child'. Defaults to 'parent' - replace the existing order. * 'checkout_renewal' bool Indicates if invoked from an interactive cart/checkout session and certain order items are not set, like taxes, shipping as they need to be set in teh calling function, like @see WC_Subscriptions_Checkout::filter_woocommerce_create_order(). Default false. * 'failed_order_id' int For checkout_renewal true, indicates order id being replaced * @since 1.2 */ public static function generate_renewal_order($original_order, $product_id, $args = array()) { global $wpdb, $woocommerce; if (!is_object($original_order)) { $original_order = new WC_Order($original_order); } if (!WC_Subscriptions_Order::order_contains_subscription($original_order) || !WC_Subscriptions_Order::is_item_subscription($original_order, $product_id)) { return false; } if (self::is_renewal($original_order, array('order_role' => 'child'))) { $original_order = self::get_parent_order($original_order); } if (!is_array($args)) { _deprecated_argument(__CLASS__ . '::' . __FUNCTION__, '1.3', __('Third parameter is now an array of name => value pairs. Use array( "new_order_role" => "parent" ) instead.', 'woocommerce-subscriptions')); $args = array('new_order_role' => $args); } $args = wp_parse_args($args, array('new_order_role' => 'parent', 'checkout_renewal' => false)); $renewal_order_key = uniqid('order_'); // Create the new order $renewal_order_data = array('post_type' => 'shop_order', 'post_title' => sprintf(__('Subscription Renewal Order – %s', 'woocommerce-subscriptions'), strftime(_x('%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce-subscriptions'))), 'ping_status' => 'closed', 'post_excerpt' => $original_order->customer_note, 'post_author' => 1, 'post_password' => $renewal_order_key); $create_new_order = true; if (!WC_Subscriptions::is_woocommerce_pre('2.2')) { // WC 2.2 order status $renewal_order_data['post_status'] = 'wc-pending'; } else { $renewal_order_data['post_status'] = 'publish'; } if ('child' == $args['new_order_role']) { $renewal_order_data['post_parent'] = $original_order->id; } if (true === $args['checkout_renewal']) { $renewal_order_id = null; if ($woocommerce->session->order_awaiting_payment > 0) { $renewal_order_id = absint($woocommerce->session->order_awaiting_payment); } elseif (isset($args['failed_order_id'])) { $failed_order_id = $args['failed_order_id']; if (WC_Subscriptions::is_woocommerce_pre('2.2')) { // WC 2.1 - need to use taxonomy /* Check order is unpaid by getting its status */ $terms = wp_get_object_terms($failed_order_id, 'shop_order_status', array('fields' => 'slugs')); $order_status = isset($terms[0]) ? $terms[0] : 'pending'; } else { // WC 2.2+ $failed_order = wc_get_order($failed_order_id); $order_status = $failed_order->get_status(); } /* If paying on a pending order, we are resuming */ if ($order_status == 'pending') { $renewal_order_id = $failed_order_id; } } if ($renewal_order_id) { if (WC_Subscriptions::is_woocommerce_pre('2.2')) { // WC 2.1 - need to use taxonomy /* Check order is unpaid by getting its status */ $terms = wp_get_object_terms($renewal_order_id, 'shop_order_status', array('fields' => 'slugs')); $order_status = isset($terms[0]) ? $terms[0] : 'pending'; } else { // WC 2.2+ $existing_order = wc_get_order($renewal_order_id); $order_status = $existing_order->get_status(); } // Resume the unpaid order if its pending if ($order_status == 'pending' || $order_status == 'failed') { // Update the existing order as we are resuming it $create_new_order = false; $renewal_order_data['ID'] = $renewal_order_id; wp_update_post($renewal_order_data); // Clear the old line items - we'll add these again in case they changed $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $renewal_order_id)); $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $renewal_order_id)); } } } if ($create_new_order) { $renewal_order_id = wp_insert_post($renewal_order_data); } if (WC_Subscriptions::is_woocommerce_pre('2.2')) { // WC 2.1 order status // Set the order as pending wp_set_object_terms($renewal_order_id, 'pending', 'shop_order_status'); } // Set a unique key for this order update_post_meta($renewal_order_id, '_order_key', $renewal_order_key); $order_meta_query = "SELECT `meta_key`, `meta_value`\n\t\t\t\t\t\t\t FROM {$wpdb->postmeta}\n\t\t\t\t\t\t\t WHERE `post_id` = {$original_order->id}\n\t\t\t\t\t\t\t AND `meta_key` NOT IN ('_paid_date', '_completed_date', '_order_key', '_edit_lock', '_original_order', '_wc_points_earned', '_transaction_id')"; // Superseding existing order so don't carry over payment details if ('parent' == $args['new_order_role'] || true === $args['checkout_renewal']) { $order_meta_query .= " AND `meta_key` NOT IN ('_payment_method', '_payment_method_title', '_recurring_payment_method', '_recurring_payment_method_title', '_shipping_method', '_shipping_method_title', '_recurring_shipping_method', '_recurring_shipping_method_title')"; } else { $order_meta_query .= " AND `meta_key` NOT LIKE '_order_recurring_%' AND `meta_key` NOT IN ('_payment_method', '_payment_method_title', '_recurring_payment_method', '_recurring_payment_method_title', '_shipping_method', '_shipping_method_title', '_recurring_shipping_method', '_recurring_shipping_method_title')"; } // Allow extensions to add/remove order meta $order_meta_query = apply_filters('woocommerce_subscriptions_renewal_order_meta_query', $order_meta_query, $original_order->id, $renewal_order_id, $args['new_order_role']); // Carry all the required meta from the old order over to the new order $order_meta = $wpdb->get_results($order_meta_query, 'ARRAY_A'); $order_meta = apply_filters('woocommerce_subscriptions_renewal_order_meta', $order_meta, $original_order->id, $renewal_order_id, $args['new_order_role']); foreach ($order_meta as $meta_item) { add_post_meta($renewal_order_id, $meta_item['meta_key'], maybe_unserialize($meta_item['meta_value']), true); } $outstanding_balance = WC_Subscriptions_Order::get_outstanding_balance($original_order, $product_id); $failed_payment_multiplier = 1; if (false == $args['checkout_renewal']) { // If there are outstanding payment amounts, add them to the order, otherwise set the order details to the values of the recurring totals if ($outstanding_balance > 0 && 'yes' == get_option(WC_Subscriptions_Admin::$option_prefix . '_add_outstanding_balance', 'no')) { $failed_payment_multiplier += WC_Subscriptions_Order::get_failed_payment_count($original_order, $product_id); } // Set order totals based on recurring totals from the original order $cart_discount = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_discount_cart', true)); $order_discount = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_discount_total', true)); $order_shipping_tax = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_shipping_tax_total', true)); $order_shipping = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_shipping_total', true)); $order_tax = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_tax_total', true)); $order_total = $failed_payment_multiplier * wc_format_decimal(get_post_meta($original_order->id, '_order_recurring_total', true)); update_post_meta($renewal_order_id, '_cart_discount', $cart_discount); update_post_meta($renewal_order_id, '_order_discount', $order_discount); update_post_meta($renewal_order_id, '_order_shipping_tax', $order_shipping_tax); update_post_meta($renewal_order_id, '_order_shipping', $order_shipping); update_post_meta($renewal_order_id, '_order_tax', $order_tax); update_post_meta($renewal_order_id, '_order_total', $order_total); // Set shipping for orders created with WC 2.0.n (or when we are using WC 2.0.n) if (WC_Subscriptions::is_woocommerce_pre('2.1') || isset($original_order->recurring_shipping_method)) { update_post_meta($renewal_order_id, '_shipping_method', $original_order->recurring_shipping_method); update_post_meta($renewal_order_id, '_shipping_method_title', $original_order->recurring_shipping_method_title); // Also set recurring shipping as it's a parent renewal order if ('parent' == $args['new_order_role']) { update_post_meta($renewal_order_id, '_recurring_shipping_method', $original_order->recurring_shipping_method); update_post_meta($renewal_order_id, '_recurring_shipping_method_title', $original_order->recurring_shipping_method_title); } } // Apply the recurring shipping & payment methods to child renewal orders if ('child' == $args['new_order_role']) { update_post_meta($renewal_order_id, '_payment_method', $original_order->recurring_payment_method); update_post_meta($renewal_order_id, '_payment_method_title', $original_order->recurring_payment_method_title); } // Set order taxes based on recurring taxes from the original order $recurring_order_taxes = WC_Subscriptions_Order::get_recurring_taxes($original_order); foreach ($recurring_order_taxes as $index => $recurring_order_tax) { $item_ids = array(); $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_order_tax['name'], 'order_item_type' => 'tax')); // Also set recurring taxes on parent renewal orders if ('parent' == $args['new_order_role']) { $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_order_tax['name'], 'order_item_type' => 'recurring_tax')); } // Add line item meta foreach ($item_ids as $item_id) { woocommerce_add_order_item_meta($item_id, 'compound', absint(isset($recurring_order_tax['compound']) ? $recurring_order_tax['compound'] : 0)); woocommerce_add_order_item_meta($item_id, 'tax_amount', $failed_payment_multiplier * WC_Subscriptions::format_total($recurring_order_tax['tax_amount'])); woocommerce_add_order_item_meta($item_id, 'shipping_tax_amount', $failed_payment_multiplier * WC_Subscriptions::format_total($recurring_order_tax['shipping_tax_amount'])); if (isset($recurring_order_tax['rate_id'])) { woocommerce_add_order_item_meta($item_id, 'rate_id', $recurring_order_tax['rate_id']); } if (isset($recurring_order_tax['label'])) { woocommerce_add_order_item_meta($item_id, 'label', $recurring_order_tax['label']); } } } // Set up shipping items on renewal order $recurring_shipping_items = WC_Subscriptions_Order::get_recurring_shipping_methods($original_order); foreach ($recurring_shipping_items as $recurring_shipping_item_id => $recurring_shipping_item) { $item_ids = array(); $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_shipping_item['name'], 'order_item_type' => 'shipping')); // Also set recurring shipping as it's a parent renewal order if ('parent' == $args['new_order_role']) { $item_ids[] = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $recurring_shipping_item['name'], 'order_item_type' => 'recurring_shipping')); } // Add shipping item meta foreach ($item_ids as $item_id) { woocommerce_add_order_item_meta($item_id, 'method_id', $recurring_shipping_item['method_id']); woocommerce_add_order_item_meta($item_id, 'cost', $failed_payment_multiplier * WC_Subscriptions::format_total($recurring_shipping_item['cost'])); // Set line taxes for shipping if (isset($recurring_shipping_item['taxes'])) { $taxes = maybe_unserialize($recurring_shipping_item['taxes']); } else { // try to determine recurring shipping taxes from original order's shipping taxes $taxes = array(); foreach ($original_order->get_shipping_methods() as $original_shipping_item_id => $original_shipping_item) { // We need to use the amount of the same shipping method as this one if ($recurring_shipping_item['method_id'] != $original_shipping_item['method_id']) { continue; } // If we don't have taxes on the matching shipping item, there's nothing we can do if (!isset($original_shipping_item['taxes'])) { break; } $original_shipping_item['taxes'] = maybe_unserialize($original_shipping_item['taxes']); // Make sure we account for any amount on the original original shipping not applied to renewals by determining what proportion of the initial amount the recurring total represents if (0 != $original_shipping_item['cost'] && $recurring_shipping_item['cost'] != $original_shipping_item['cost']) { $recurring_ratio = $recurring_shipping_item['cost'] / $original_shipping_item['cost']; foreach ($original_shipping_item['taxes'] as $tax_id => $tax_amount) { $original_shipping_item['taxes'][$tax_id] = $recurring_ratio * $tax_amount; } } foreach ($original_shipping_item['taxes'] as $tax_id => $tax_amount) { $taxes[$tax_id] = WC_Subscriptions::format_total($failed_payment_multiplier * $tax_amount); } } } woocommerce_add_order_item_meta($item_id, 'taxes', $taxes); } } } // Set line totals to be recurring line totals and remove the subscription/recurring related item meta from each order item $order_items = WC_Subscriptions_Order::get_recurring_items($original_order); // Allow extensions to add/remove items or item meta $order_items = apply_filters('woocommerce_subscriptions_renewal_order_items', $order_items, $original_order->id, $renewal_order_id, $product_id, $args['new_order_role']); if (true === $args['checkout_renewal']) { $cart_items = $woocommerce->cart->get_cart(); } foreach ($order_items as $item_index => $order_item) { $renewal_order_item_name = apply_filters('woocommerce_subscriptions_renewal_order_item_name', $order_item['name'], $order_item, $original_order); // Create order line item on the renewal order $recurring_item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $renewal_order_item_name, 'order_item_type' => 'line_item')); if (true === $args['checkout_renewal']) { $cart_item = array(); foreach ($cart_items as $item) { if ($item['product_id'] == $order_item['product_id'] && (empty($order_item['variation_id']) || $item['variation_id'] == $order_item['variation_id'])) { $cart_item = $item; } } if (!empty($cart_item)) { woocommerce_update_order_item_meta($recurring_item_id, '_line_total', woocommerce_format_decimal($cart_item['line_total'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_tax', woocommerce_format_decimal($cart_item['line_tax'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal', woocommerce_format_decimal($cart_item['line_subtotal'])); woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal_tax', woocommerce_format_decimal($cart_item['line_subtotal_tax'])); if (is_object($cart_item['data'])) { woocommerce_update_order_item_meta($recurring_item_id, '_tax_class', $cart_item['data']->get_tax_class()); } } $cart_items = $woocommerce->cart->get_cart(); } if (WC_Subscriptions::is_woocommerce_pre('2.4')) { $item_meta = new WC_Order_Item_Meta($order_item['item_meta']); } else { $item_meta = new WC_Order_Item_Meta($order_item); } // Remove recurring line items and set item totals based on recurring line totals foreach ($item_meta->meta as $meta_key => $meta) { // meta value might be an array. If it is, we need to preserve all values foreach ($meta as $key => $value) { $meta_values[$key] = maybe_unserialize($value); } if (false === $args['checkout_renewal']) { // Already set earlier // Map line item totals based on recurring line totals switch ($meta_key) { case '_recurring_line_total': woocommerce_update_order_item_meta($recurring_item_id, '_line_total', $failed_payment_multiplier * woocommerce_format_decimal($meta_values[0])); break; case '_recurring_line_tax': woocommerce_update_order_item_meta($recurring_item_id, '_line_tax', $failed_payment_multiplier * woocommerce_format_decimal($meta_values[0])); break; case '_recurring_line_subtotal': woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal', $failed_payment_multiplier * woocommerce_format_decimal($meta_values[0])); break; case '_recurring_line_subtotal_tax': woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal_tax', $failed_payment_multiplier * woocommerce_format_decimal($meta_values[0])); break; case '_line_tax_data': // Copy line tax data if the order doesn't have a _recurring_line_tax_data (for backward compatibility) if (!array_key_exists('_recurring_line_tax_data', $item_meta->meta)) { $meta_value = $meta_values[0]; $line_total = $item_meta->meta['_line_total'][0]; $recurring_line_total = $item_meta->meta['_recurring_line_total'][0]; // There will only be recurring tax data if the recurring amount is > 0 and we can only retroactively calculate recurring amount from initial amoutn if it is > 0 if ($line_total > 0 && $recurring_line_total > 0) { // Make sure we account for any sign-up fees by determining what proportion of the initial amount the recurring total represents $recurring_ratio = $recurring_line_total / $line_total; $recurring_tax_data = array(); $tax_data_keys = array('total', 'subtotal'); foreach ($tax_data_keys as $tax_data_key) { foreach ($meta_value[$tax_data_key] as $tax_index => $tax_value) { // Use total tax amount for both total and subtotal because we don't want any initial discounts to be applied to recurring amounts $total_tax_amount = $meta_value['total'][$tax_index]; $recurring_tax_data[$tax_data_key][$tax_index] = woocommerce_format_decimal($failed_payment_multiplier * ($recurring_ratio * $total_tax_amount)); } } } else { $recurring_tax_data = array('total' => array(), 'subtotal' => array()); } woocommerce_update_order_item_meta($recurring_item_id, '_line_tax_data', $recurring_tax_data); } break; case '_recurring_line_tax_data': $meta_value = $meta_values[0]; $recurring_tax_data = array(); $tax_data_keys = array('total', 'subtotal'); foreach ($tax_data_keys as $tax_data_key) { foreach ($meta_value[$tax_data_key] as $tax_index => $tax_value) { $recurring_tax_data[$tax_data_key][$tax_index] = woocommerce_format_decimal($failed_payment_multiplier * $tax_value); } } woocommerce_update_order_item_meta($recurring_item_id, '_line_tax_data', $recurring_tax_data); break; default: break; } } // Copy over line item meta data, with some parent/child role based exceptions for recurring amounts $copy_to_renewal_item = true; switch ($meta_key) { case '_recurring_line_total': case '_recurring_line_tax': case '_recurring_line_subtotal': case '_recurring_line_subtotal_tax': case '_recurring_line_tax_data': case '_line_tax_data': case '_subscription_recurring_amount': case '_subscription_sign_up_fee': case '_subscription_period': case '_subscription_interval': case '_subscription_length': case '_subscription_trial_period': case '_subscription_end_date': case '_subscription_expiry_date': case '_subscription_start_date': case '_subscription_status': case '_subscription_completed_payments': if ('child' == $args['new_order_role']) { $copy_to_renewal_item = false; } break; case '_subscription_trial_length': // We never want to duplicate free trials on renewal orders $copy_to_renewal_item = false; break; case '_subscription_suspension_count': // We want to reset some values for the new order // We want to reset some values for the new order case '_subscription_trial_expiry_date': case '_subscription_failed_payments': $copy_to_renewal_item = false; $meta_value = 0; break; default: break; } // Copy existing item over to new recurring order item if ($copy_to_renewal_item) { // cast it to an array. Arrays will be kept, strings will be an array. Tested back to PHP 5.2.16 foreach ($meta_values as $meta_value) { woocommerce_add_order_item_meta($recurring_item_id, $meta_key, $meta_value); } } } } if (false == $args['checkout_renewal']) { // Add fees foreach ($original_order->get_fees() as $item_id => $order_fee) { if (!isset($order_fee['recurring_line_total'])) { continue; } $item_id = woocommerce_add_order_item($renewal_order_id, array('order_item_name' => $order_fee['name'], 'order_item_type' => 'fee')); woocommerce_add_order_item_meta($item_id, '_tax_class', $order_fee['tax_class']); woocommerce_add_order_item_meta($item_id, '_line_total', WC_Subscriptions::format_total($order_fee['recurring_line_total'])); woocommerce_add_order_item_meta($item_id, '_line_tax', WC_Subscriptions::format_total($order_fee['recurring_line_tax'])); } } // Keep a record of the original order's ID on the renewal order update_post_meta($renewal_order_id, '_original_order', $original_order->id, true); $renewal_order = new WC_Order($renewal_order_id); if ('parent' == $args['new_order_role']) { WC_Subscriptions_Manager::process_subscriptions_on_checkout($renewal_order_id); $original_order->add_order_note(sprintf(__('Order superseded by Renewal Order %s.', 'woocommerce-subscriptions'), $renewal_order->get_order_number())); } do_action('woocommerce_subscriptions_renewal_order_created', $renewal_order, $original_order, $product_id, $args['new_order_role']); return apply_filters('woocommerce_subscriptions_renewal_order_id', $renewal_order_id, $original_order, $product_id, $args['new_order_role']); }