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);
问题分析与修复方案
核心问题点
get_item()方法使用错误:WooCommerce的$order->get_item()是通过item ID获取,不是product ID,直接传product ID会返回false,导致逻辑出错。- 库存操作冲突:结账过程中订单状态变更时直接修改库存,会和WooCommerce自带的库存处理逻辑冲突,引发错误。
- 钩子参数不匹配:
woocommerce_order_status_changed钩子实际传递4个参数($order_id, $old_status, $new_status, $order),原函数只接收1个,可能导致上下文混乱。 - 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
相关产品推荐
相关产品推荐

