You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WooCommerce订单合并及重复订单项合并结账报错排查

WooCommerce订单合并功能:同款商品合并导致结账错误问题

我正在开发WooCommerce自动合并订单功能,需求是同一客户的所有订单在状态修改前自动合并。当前遇到的问题:客户下单包含历史订单同款商品时,订单项会重复显示,给拣货员工造成困扰。我添加了一段检查商品是否存在、更新数量而非新增订单项的代码后,结账时出现**"Error processing checkout. Please try later."**错误;移除这段代码后,功能能正常运行。

原代码如下:

// Add a custom function to check and combine orders
function combine_orders_on_status_change($order_id) {
    // Get the current order
    $current_order = wc_get_order($order_id);

    // Check if the order status is 'on-hold'
    if ($current_order->get_status() === 'on-hold') {
        // Get user ID of the current order
        $user_id = $current_order->get_user_id();

        // Get all orders of the same user with status 'on-hold'
        $args = array(
            'post_type'      => 'shop_order',
            'post_status'    => 'wc-on-hold',
            'posts_per_page' => -1,
            'meta_query'     => array(
                array(
                    'key'   => '_customer_user',
                    'value' => $user_id,
                ),
            ),
        );

        $orders = get_posts($args);

        // Find the older order with added items to combine with
        $older_order = null;
        foreach ($orders as $order_post) {
            $order_to_combine = wc_get_order($order_post->ID);

            // Check if it's not the same order and it's not already combined
            if ($order_to_combine && $order_to_combine->get_id() !== $order_id && $order_to_combine->get_status() !== 'combined') {
                // Check if shipping addresses match (if present in both orders)
                $current_shipping_address = $current_order->get_address('shipping');
                $combine_shipping_address = $order_to_combine->get_address('shipping');
                if (!empty($current_shipping_address) && !empty($combine_shipping_address) && $current_shipping_address !== $combine_shipping_address) {
                    continue; // Skip combining if shipping addresses don't match
                }

                // Check if delivery dates match (if present in both orders)
                $current_delivery_date = $current_order->get_meta('_orddd_timestamp', true);
                $combine_delivery_date = $order_to_combine->get_meta('_orddd_timestamp', true);
                if (!empty($current_delivery_date) && !empty($combine_delivery_date) && $current_delivery_date !== $combine_delivery_date) {
                    continue; // Skip combining if delivery dates don't match
                }

                // Set the older order with added items to combine with
                $older_order = $order_to_combine;
                break; // Stop after finding the first eligible order
            }
        }

        // If an older order with added items was found, combine orders
        if ($older_order) {
            // Check if the older order is transitioning from 'received' to 'processing'
            if ($older_order->get_status() === 'on-hold' && $current_order->get_status() === 'processing') {
                return;
            }

            // Combine orders by copying items
            $combined_items_list = array();
            foreach ($current_order->get_items() as $item) {
                $product_id = $item->get_product_id();
                $quantity = $item->get_quantity();

                // Check if the product already exists in the combined order
                $existing_item = $older_order->get_item($product_id);

                if ($existing_item) {
                    // If the product exists, update the quantity
                    $existing_quantity = $existing_item->get_quantity();
                    $older_order->set_quantity($existing_item, $existing_quantity + $quantity);
                } else {
                    // If the product does not exist, add it to the combined order
                    $older_order->add_product(wc_get_product($product_id), $quantity);
                }
                
                $combined_items_list[] = $item->get_name() . ' x' . $quantity;
                
                // Restore stock for each item
                wc_update_product_stock($product_id, $quantity, 'increase');
            }
            
            // Copy customer notes with clear delineation
            $custom_order_number = $current_order->get_order_number();
            $customer_note = $current_order->get_customer_note();
            if (!empty($customer_note)) {
                $existing_notes = $older_order->get_customer_note();
                $combined_notes = $existing_notes . "\n--- Note from Order #" . $custom_order_number . " ---
" . $customer_note;
                $older_order->set_customer_note($combined_notes);
            }

            // Move the current order with added items to 'combined' status
            $current_order->update_status('combined', __('Orders combined', 'astra'));

            // Add a note to the order log
            $combined_items_log = implode(', ', $combined_items_list);
            $note_combined = sprintf(__('Order combined with Order #%s. Items added: %s', 'astra'), $custom_order_number, $combined_items_log);
            $older_order->add_order_note($note_combined);

            // Log entry for the newer order
            $note_newer = sprintf(__('Items added to Order #%s during combination.', 'astra'), $older_order->get_order_number());
            $current_order->add_order_note($note_newer);
            
            $older_order->calculate_totals();

            // Save changes to the orders
            $older_order->save();
            $current_order->save();
            
            // Fire the update order action hook after the old_order is saved
            wc_reduce_stock_levels($older_order->get_id());
        }
    }
}

// Hook into order status change
add_action('woocommerce_order_status_changed', 'combine_orders_on_status_change', 10, 3);

问题分析与修复方案

核心问题点

  1. get_item()方法使用错误:WooCommerce的$order->get_item()是通过item ID获取,不是product ID,直接传product ID会返回false,导致逻辑出错。
  2. 库存操作冲突:结账过程中订单状态变更时直接修改库存,会和WooCommerce自带的库存处理逻辑冲突,引发错误。
  3. 钩子参数不匹配:woocommerce_order_status_changed钩子实际传递4个参数($order_id, $old_status, $new_status, $order),原函数只接收1个,可能导致上下文混乱。
  4. item元数据丢失:直接用add_product()添加商品,会丢失原订单item的属性(如商品变体、自定义字段)。

修复后的代码

// 合并同一客户的待支付订单,同款商品合并数量
function combine_orders_on_status_change($order_id, $old_status, $new_status, $current_order) {
    // 仅处理新订单变为on-hold的情况
    if ($new_status !== 'on-hold') {
        return;
    }

    $user_id = $current_order->get_user_id();
    if (!$user_id) { // 游客订单不处理
        return;
    }

    // 查询该用户所有未合并的on-hold订单(排除当前订单)
    $args = array(
        'post_type'      => 'shop_order',
        'post_status'    => 'wc-on-hold',
        'posts_per_page' => -1,
        'meta_query'     => array(
            array(
                'key'   => '_customer_user',
                'value' => $user_id,
            ),
            array(
                'key'     => '_order_status',
                'value'   => 'combined',
                'compare' => '!=',
            )
        ),
        'post__not_in'   => array($order_id),
        'orderby'        => 'ID',
        'order'          => 'ASC' // 取最早的订单合并
    );

    $orders = get_posts($args);
    if (empty($orders)) {
        return;
    }

    $older_order = wc_get_order($orders[0]->ID);
    if (!$older_order) {
        return;
    }

    // 校验配送地址一致
    $current_shipping = $current_order->get_address('shipping');
    $older_shipping = $older_order->get_address('shipping');
    if (!empty($current_shipping) && !empty($older_shipping) && $current_shipping !== $older_shipping) {
        return;
    }

    // 校验配送日期一致(如果使用了orddd插件)
    $current_delivery = $current_order->get_meta('_orddd_timestamp', true);
    $older_delivery = $older_order->get_meta('_orddd_timestamp', true);
    if (!empty($current_delivery) && !empty($older_delivery) && $current_delivery !== $older_delivery) {
        return;
    }

    $combined_items = array();
    // 遍历当前订单的商品,合并到旧订单
    foreach ($current_order->get_items() as $item) {
        $product_id = $item->get_product_id();
        $variation_id = $item->get_variation_id();
        $quantity = $item->get_quantity();
        $item_name = $item->get_name();

        // 查找旧订单中是否存在同款商品(包含变体)
        $existing_item = null;
        foreach ($older_order->get_items() as $old_item) {
            if ($old_item->get_product_id() === $product_id && $old_item->get_variation_id() === $variation_id) {
                $existing_item = $old_item;
                break;
            }
        }

        if ($existing_item) {
            // 更新已有商品数量
            $new_qty = $existing_item->get_quantity() + $quantity;
            $existing_item->set_quantity($new_qty);
            $existing_item->save();
        } else {
            // 复制当前订单的item到旧订单(保留所有元数据)
            $new_item = $item->copy();
            $older_order->add_item($new_item);
        }

        $combined_items[] = "{$item_name} x{$quantity}";

        // 恢复当前订单的商品库存(因为要合并,当前订单会被标记为已合并)
        wc_update_product_stock(wc_get_product($product_id), $quantity, 'increase');
    }

    // 合并客户备注
    $current_note = $current_order->get_customer_note();
    if (!empty($current_note)) {
        $older_note = $older_order->get_customer_note();
        $new_note = $older_note . "\n--- 来自订单 #" . $current_order->get_order_number() . " 的备注 ---\n" . $current_note;
        $older_order->set_customer_note($new_note);
    }

    // 更新旧订单的总计
    $older_order->calculate_totals();
    $older_order->save();

    // 标记当前订单为已合并
    $current_order->update_status('combined', __('已合并至订单 #' . $older_order->get_order_number(), 'astra'));
    $current_order->add_order_note("订单已合并至 #" . $older_order->get_order_number());
    $older_order->add_order_note("合并了订单 #" . $current_order->get_order_number() . " 的商品:" . implode(', ', $combined_items));

    // 重新计算旧订单的库存
    wc_reduce_stock_levels($older_order->get_id());
}

// 修正钩子参数,优先级调整为20,确保在WooCommerce默认逻辑之后执行
add_action('woocommerce_order_status_changed', 'combine_orders_on_status_change', 20, 4);

关键修改说明

  • 修正了商品item的查找逻辑,通过遍历订单item匹配product_id和variation_id,确保同款(含变体)商品被正确识别
  • 调整钩子参数为完整的4个,仅在订单变为on-hold时触发
  • 使用$item->copy()复制原订单item,保留所有元数据(如变体属性、自定义字段)
  • 调整库存操作时机,先恢复当前订单的库存,再重新计算合并后订单的库存
  • 增加游客订单过滤,仅处理登录用户的订单
  • 调整钩子优先级为20,确保在WooCommerce默认的库存处理之后执行,避免冲突

内容的提问来源于stack exchange,提问作者AlexTSF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 11:28:14