/** * Creates a new order for renewing a subscription product based on the details of a previous order. * * @param $order WC_Order | int The WC_Order object or ID of the order for which the a new order should be created. * @param $meta_key string The ID of the subscription product in the order which needs to be added to the new order. * @since 1.0 */ public static function generate_renewal_order($original_order, $product_id) { global $wpdb; if (!is_object($original_order)) { $original_order = new WC_Order($original_order); } // Create the new order $renewal_order_data = array('post_type' => 'shop_order', 'post_title' => 'Renewal Order – ' . date('F j, Y @ h:i A'), 'post_status' => 'publish', 'ping_status' => 'closed', 'post_excerpt' => $original_order->customer_note, 'post_author' => 1, 'post_password' => uniqid('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'); // Carry all the post meta from the old order over to the new order $order_meta_items = $wpdb->get_results("SELECT `meta_key`, `meta_value` FROM {$wpdb->postmeta} WHERE `post_id` = {$original_order->id}", 'ARRAY_A'); foreach ($order_meta_items as $order_meta_item) { add_post_meta($renewal_order_id, $order_meta_item['meta_key'], maybe_unserialize($order_meta_item['meta_value']), true); } $outstanding_balance = self::get_outstanding_balance($original_order, $product_id); // If there are outstanding payment amounts, add them as a sign-up fee to the order, otherwise set the sign-up fee to 0 if ($outstanding_balance > 0 && 'yes' == get_option(WC_Subscriptions_Admin::$option_prefix . '_add_outstanding_balance')) { $failed_payment_count = self::get_failed_payment_count($original_order, $product_id); $sign_up_fee_subtotal = 0; $sign_up_fee_subtotal_ex_tax = 0; // Calculate foreach ($original_order->get_items() as $item) { if ($item['id'] != $product_id) { continue; } $base_price = WC_Subscriptions_Product::get_price($item['id']) * $item['qty']; // Row price if ($original_order->prices_include_tax) { // Sub total is based on base prices (without discounts or shipping) $sign_up_fee_subtotal += $base_price * $failed_payment_count; $sign_up_fee_subtotal_ex_tax += ($base_price - $item['line_tax']) * $failed_payment_count; } else { // Sub total is based on base prices (without discounts) $sign_up_fee_subtotal += ($base_price + $item['line_tax']) * $failed_payment_count; $sign_up_fee_subtotal_ex_tax += $base_price * $failed_payment_count; } } update_post_meta($renewal_order_id, '_sign_up_fee_total', $outstanding_balance); update_post_meta($renewal_order_id, '_sign_up_fee_subtotal', $sign_up_fee_subtotal); update_post_meta($renewal_order_id, '_sign_up_fee_subtotal_ex_tax', $sign_up_fee_subtotal_ex_tax); $sign_up_fee_taxes = get_post_meta($renewal_order_id, '_sign_up_fee_taxes', true); foreach ($original_order->get_taxes() as $key => $tax) { $sign_up_fee_taxes[$key]['cart_tax'] = $tax['cart_tax'] * $failed_payment_count; $sign_up_fee_taxes[$key]['shipping_tax'] = $tax['shipping_tax'] * $failed_payment_count; } update_post_meta($renewal_order_id, '_sign_up_fee_taxes', $sign_up_fee_taxes); update_post_meta($renewal_order_id, '_sign_up_fee_tax_total', $original_order->get_total_tax() * $failed_payment_count); update_post_meta($renewal_order_id, '_sign_up_fee_discount_cart', $original_order->cart_discount * $failed_payment_count); update_post_meta($renewal_order_id, '_sign_up_fee_discount_total', $original_order->order_discount * $failed_payment_count); } else { // Remove all sign-up fees foreach (array('_cart_contents_sign_up_fee_total', '_cart_contents_sign_up_fee_count', '_sign_up_fee_total', '_sign_up_fee_subtotal', '_sign_up_fee_subtotal_ex_tax', '_sign_up_fee_tax_total', '_sign_up_fee_discount_cart', '_sign_up_fee_discount_total') as $meta_key) { update_post_meta($renewal_order_id, $meta_key, 0); } update_post_meta($renewal_order_id, '_sign_up_fee_taxes', array()); } // Keep a record of the original order's ID on the renewal order add_post_meta($renewal_order_id, '_original_order', $original_order->id, true); // Make sure the original order is cancelled and keep a note of the renewal order if (!in_array($original_order->status, array('cancelled', 'expired', 'failed'))) { $original_order->cancel_order(); } $original_order->add_order_note(sprintf(__('Order superseded by renewal order %s.', WC_Subscriptions::$text_domain), $renewal_order_data)); $renewal_order = new WC_Order($renewal_order_id); WC_Subscriptions_Manager::process_subscriptions_on_checkout($renewal_order_id); do_action('woocommerce_subscriptions_renewal_order_created', $renewal_order, $original_order, $product_id); }
/** * 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 $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 $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 $order WC_Order | int The WC_Order object or ID of the order for which the a new order should be created. * @param $product_id string The ID of the subscription product in the order which needs to be added to the new order. * @param $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. * @since 1.2 */ public static function generate_renewal_order($original_order, $product_id, $new_order_role = 'parent') { global $wpdb; 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_a_subscription($original_order, $product_id)) { return false; } if (self::is_renewal($original_order, 'child')) { $original_order = self::get_parent_order($original_order); } $renewal_order_key = uniqid('order_'); // Create the new order $renewal_order_data = array('post_type' => 'shop_order', 'post_title' => sprintf(__('Subscription Renewal Order – %s', WC_Subscriptions::$text_domain), strftime(_x('%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', WC_Subscriptions::$text_domain))), 'post_status' => 'publish', 'ping_status' => 'closed', 'post_excerpt' => $original_order->customer_note, 'post_author' => 1, 'post_password' => $renewal_order_key); if ('child' == $new_order_role) { $renewal_order_data['post_parent'] = $original_order->id; } $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` FROM {$wpdb->postmeta} WHERE `post_id` = {$original_order->id} 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' == $new_order_role) { $order_meta_query .= " AND `meta_key` NOT IN ('_payment_method', '_payment_method_title')"; } else { $order_meta_query .= " AND `meta_key` NOT LIKE '_order_recurring_%'"; } // 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, $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, $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); // 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')) { $failed_payment_multiplier = WC_Subscriptions_Order::get_failed_payment_count($original_order, $product_id); } else { $failed_payment_multiplier = 1; } // 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_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_tax', $order_tax); update_post_meta($renewal_order_id, '_order_total', $order_total); // Set order taxes based on recurring taxes from the original order $recurring_order_taxes = get_post_meta($original_order->id, '_order_recurring_taxes', true); foreach ($recurring_order_taxes as $index => $recurring_order_tax) { if (isset($recurring_order_tax['cart_tax']) && $recurring_order_tax['cart_tax'] > 0) { $recurring_order_taxes[$index]['cart_tax'] = $failed_payment_multiplier * $recurring_order_tax['cart_tax']; } else { $recurring_order_taxes[$index]['cart_tax'] = 0; } if (isset($recurring_order_tax['shipping_tax']) && $recurring_order_tax['shipping_tax'] > 0) { $recurring_order_taxes[$index]['shipping_tax'] = $failed_payment_multiplier * $recurring_order_tax['shipping_tax']; } else { $recurring_order_taxes[$index]['shipping_tax'] = 0; } } update_post_meta($renewal_order_id, '_order_taxes', $recurring_order_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, $new_order_role); foreach ($order_items as $item_index => $order_item) { $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_index => $meta_item) { switch ($meta_item['meta_name']) { case '_recurring_line_total': $order_items[$item_index]['line_total'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_tax': $order_items[$item_index]['line_tax'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_subtotal': $order_items[$item_index]['line_subtotal'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_subtotal_tax': $order_items[$item_index]['line_subtotal_tax'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_total': case '_recurring_line_tax': case '_recurring_line_subtotal': case '_recurring_line_subtotal_tax': 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_length': case '_subscription_trial_period': if ('child' == $new_order_role) { unset($item_meta->meta[$meta_index]); } break; } } if ('child' == $new_order_role) { $order_items[$item_index]['name'] = sprintf(__('Renewal of "%s" purchased in Order %s', WC_Subscriptions::$text_domain), $order_item['name'], $original_order->get_order_number()); } $order_items[$item_index]['item_meta'] = $item_meta->meta; } // Save the item meta on the new order update_post_meta($renewal_order_id, '_order_items', $order_items); // 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' == $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.', WC_Subscriptions::$text_domain), $renewal_order->get_order_number())); } do_action('woocommerce_subscriptions_renewal_order_created', $renewal_order, $original_order, $product_id, $new_order_role); return apply_filters('woocommerce_subscriptions_renewal_order_id', $renewal_order_id, $original_order, $product_id, $new_order_role); }
/** * 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. 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.', WC_Subscriptions::$text_domain)); $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', WC_Subscriptions::$text_domain), strftime(_x('%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', WC_Subscriptions::$text_domain))), '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); if (true === $args['checkout_renewal']) { $checkout_object = $woocommerce->checkout; $customer_id = $original_order->customer_user; // Save posted billing 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 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 ($checkout_object->posted['shiptobilling']) { 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); } } else { $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); } } } } $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); if (true === $args['checkout_renewal']) { $failed_payment_multiplier = 1; update_post_meta($renewal_order_id, '_order_shipping', woocommerce_format_total($woocommerce->cart->shipping_total)); update_post_meta($renewal_order_id, '_order_discount', woocommerce_format_total($woocommerce->cart->get_order_discount_total())); update_post_meta($renewal_order_id, '_cart_discount', woocommerce_format_total($woocommerce->cart->get_cart_discount_total())); update_post_meta($renewal_order_id, '_order_tax', woocommerce_format_total($woocommerce->cart->tax_total)); update_post_meta($renewal_order_id, '_order_shipping_tax', woocommerce_format_total($woocommerce->cart->shipping_tax_total)); update_post_meta($renewal_order_id, '_order_total', woocommerce_format_total($woocommerce->cart->total)); update_post_meta($renewal_order_id, '_checkout_renewal', 'yes'); } else { // 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); } else { $failed_payment_multiplier = 1; } // 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); 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); // 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) { if (function_exists('woocommerce_update_order_item_meta')) { // WC 2.0+ $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']); } } } else { // WC 1.x if (isset($recurring_order_tax['cart_tax']) && $recurring_order_tax['cart_tax'] > 0) { $recurring_order_taxes[$index]['cart_tax'] = $failed_payment_multiplier * $recurring_order_tax['cart_tax']; } else { $recurring_order_taxes[$index]['cart_tax'] = 0; } if (isset($recurring_order_tax['shipping_tax']) && $recurring_order_tax['shipping_tax'] > 0) { $recurring_order_taxes[$index]['shipping_tax'] = $failed_payment_multiplier * $recurring_order_tax['shipping_tax']; } else { $recurring_order_taxes[$index]['shipping_tax'] = 0; } // Inefficient but keeps WC 1.x code grouped together update_post_meta($renewal_order_id, '_order_taxes', $recurring_order_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']); foreach ($order_items as $item_index => $order_item) { $item_meta = new WC_Order_Item_Meta($order_item['item_meta']); // WC 2.0+ order item structure - as of WC 2.0 item_meta is stored as $key => meta pairs, not 'meta_name'/'meta_value' if (function_exists('woocommerce_add_order_item_meta')) { if ('child' == $args['new_order_role']) { $renewal_order_item_name = sprintf(__('Renewal of "%s" purchased in Order %s', WC_Subscriptions::$text_domain), $order_item['name'], $original_order->get_order_number()); } else { $renewal_order_item_name = $order_item['name']; } // 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')); // 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]; // 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_add_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_add_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_add_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_add_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); } } } else { // WC 1.x order item structure foreach ($item_meta->meta as $meta_index => $meta_item) { switch ($meta_item['meta_name']) { case '_recurring_line_total': $order_items[$item_index]['line_total'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_tax': $order_items[$item_index]['line_tax'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_subtotal': $order_items[$item_index]['line_subtotal'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_subtotal_tax': $order_items[$item_index]['line_subtotal_tax'] = $failed_payment_multiplier * $meta_item['meta_value']; case '_recurring_line_total': case '_recurring_line_tax': case '_recurring_line_subtotal': case '_recurring_line_subtotal_tax': 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_length': case '_subscription_trial_period': if ('child' == $args['new_order_role']) { unset($item_meta->meta[$meta_index]); } break; case '_subscription_trial_length': // We never want to duplicate free trials on renewal orders if ('child' == $args['new_order_role']) { unset($item_meta->meta[$meta_index]); } else { $item_meta->meta[$meta_index] = 0; } break; } if ('child' == $args['new_order_role']) { $order_items[$item_index]['name'] = sprintf(__('Renewal of "%s" purchased in Order %s', WC_Subscriptions::$text_domain), $order_item['name'], $original_order->get_order_number()); } $order_items[$item_index]['item_meta'] = $item_meta->meta; } // Save the item meta on the new order update_post_meta($renewal_order_id, '_order_items', $order_items); } } // 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.', WC_Subscriptions::$text_domain), $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']); }
/** * 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(); } $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 = maybe_unserialize($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_update_order_item_meta($recurring_item_id, '_line_total', $failed_payment_multiplier * woocommerce_format_decimal($meta_value)); break; case '_recurring_line_tax': woocommerce_update_order_item_meta($recurring_item_id, '_line_tax', $failed_payment_multiplier * woocommerce_format_decimal($meta_value)); break; case '_recurring_line_subtotal': woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal', $failed_payment_multiplier * woocommerce_format_decimal($meta_value)); break; case '_recurring_line_subtotal_tax': woocommerce_update_order_item_meta($recurring_item_id, '_line_subtotal_tax', $failed_payment_multiplier * woocommerce_format_decimal($meta_value)); 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)) { $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': $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) { 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']); }