很多 B2B 站把询价做成一个联系表单:姓名、邮箱、留言。客户填「需要 500 个」,销售回信的第一句永远是「请问您要的是哪个型号」。型号、数量、用途全靠邮件来回对齐,一封询价三天才落到报价上。
WooCommerce 的购物车本来就在干这件事——产品关联、数量、变体、后台列表、导出都是现成的,缺的只是付款那一步。下面把它拆成一张询价单。
询价单本质是一次没有支付的订单。用订单表存它,产品、数量、客户信息、后台搜索和导出全都白拿。代价是它和真实订单混在同一张表里,所以每张询价单都必须打标记,否则财务对账时会来找你。
如果一天几十条询价、要分派给多个销售、要记录每次回访,那就不是 WooCommerce 该干的事了,那是 CRM 的活。这条线先划好,后面的代码才有意义。需要把询价单接到 ERP、或者做成按参数出配置器的,属于定制开发的范畴,边界可以看 AumCreate 的服务页。
新建 wp-content/mu-plugins/inquiry-cart.php,目录不存在就建一个。mu-plugin 不能在后台被误停用,适合放这种改流程的代码。
add_filter( 'woocommerce_product_single_add_to_cart_text', function ( $text, $product ) {
return '加入询价单';
}, 10, 2 );
add_filter( 'woocommerce_product_add_to_cart_text', function ( $text, $product ) {
return '加入询价单';
}, 10, 2 );购物车、结算按钮上的词也得跟着换,客户看到「购物车里有 3 件」会以为马上要付款:
add_filter( 'gettext', function ( $translated, $original, $domain ) {
if ( 'woocommerce' !== $domain ) {
return $translated;
}
static $map = null;
if ( null === $map ) {
$map = array(
'Add to cart' => '加入询价单',
'View cart' => '查看询价单',
'Cart' => '询价单',
'Proceed to checkout' => '填写询价信息',
);
}
return isset( $map[ $original ] ) ? $map[ $original ] : $translated;
}, 10, 3 );gettext 每次字符串翻译都会跑一遍,短词(比如 Cart)容易误伤到别的句子。站点翻译量大就别长期挂着,改成语言包覆盖。
add_filter( 'woocommerce_cart_needs_payment', function ( $needs_payment, $cart ) {
return false;
}, 10, 2 );
add_filter( 'woocommerce_order_button_text', function () {
return '提交询价单';
} );这里有个坑:needs_payment 返回 false 之后,WooCommerce 仍然会调 payment_complete(),虚拟产品会直接变成「已完成」。下单后要把状态拉回来。
add_action( 'woocommerce_checkout_order_processed', function ( $order_id, $posted_data, $order ) {
if ( 'yes' !== $order->get_meta( '_is_inquiry' ) ) {
return;
}
if ( 'on-hold' !== $order->get_status() ) {
$order->update_status( 'on-hold', '询价单,等待报价。' );
}
}, 10, 3 );客户还会收到一封「您的订单已完成」,顺手关掉:
add_filter( 'woocommerce_email_enabled_customer_completed_order', function ( $enabled, $order ) {
if ( $order instanceof WC_Order && 'yes' === $order->get_meta( '_is_inquiry' ) ) {
return false;
}
return $enabled;
}, 10, 2 );B2B 询价不需要邮编、州省、第二行地址。用 woocommerce_checkout_fields 一次性改:
add_filter( 'woocommerce_checkout_fields', function ( $fields ) {
unset(
$fields['billing']['billing_last_name'],
$fields['billing']['billing_address_1'],
$fields['billing']['billing_address_2'],
$fields['billing']['billing_city'],
$fields['billing']['billing_state'],
$fields['billing']['billing_postcode'],
$fields['billing']['billing_country'],
$fields['order']['order_comments'],
$fields['shipping']
);
$fields['billing']['billing_first_name']['label'] = '称呼';
$fields['billing']['billing_email']['label'] = '工作邮箱';
$fields['billing']['billing_phone']['required'] = true;
$fields['billing']['billing_company']['required'] = true;
$fields['billing']['inquiry_annual_qty'] = array(
'label' => '年用量',
'required' => false,
'type' => 'select',
'options' => array(
'' => '请选择',
'lt1k' => '1000 以内',
'1k-10k' => '1000 – 10000',
'gt10k' => '10000 以上',
'tbd' => '还没定',
),
'class' => array( 'form-row-first' ),
'priority' => 60,
);
$fields['billing']['inquiry_target_price'] = array(
'label' => '目标单价(可选)',
'required' => false,
'class' => array( 'form-row-last' ),
'priority' => 61,
);
$fields['order']['inquiry_note'] = array(
'label' => '补充说明:用途、认证要求、期望交期',
'required' => false,
'type' => 'textarea',
'class' => array( 'form-row-wide' ),
'priority' => 70,
);
return $fields;
} );自定义字段要自己存。WooCommerce 只会自动把 billing_*、shipping_* 前缀的字段写进地址簿,inquiry_* 不会。同时在这里打上询价标记:
add_action( 'woocommerce_checkout_create_order', function ( $order, $data ) {
$order->update_meta_data( '_is_inquiry', 'yes' );
foreach ( array( 'inquiry_annual_qty', 'inquiry_target_price', 'inquiry_note' ) as $key ) {
if ( ! empty( $data[ $key ] ) ) {
$order->update_meta_data( '_' . $key, sanitize_text_field( $data[ $key ] ) );
}
}
}, 10, 2 );带下划线前缀的 meta 在后台默认折叠在「自定义字段」里,销售看不到,所以要在订单详情页显式打出来:
add_action( 'woocommerce_admin_order_data_after_billing_address', function ( $order ) {
if ( 'yes' !== $order->get_meta( '_is_inquiry' ) ) {
return;
}
$rows = array(
'年用量' => $order->get_meta( '_inquiry_annual_qty' ),
'目标单价' => $order->get_meta( '_inquiry_target_price' ),
'补充说明' => $order->get_meta( '_inquiry_note' ),
);
echo '<div class="inquiry-meta"><h3>询价信息</h3><p>';
foreach ( $rows as $label => $value ) {
if ( ! $value ) {
continue;
}
printf( '<strong>%s:</strong>%s<br>', esc_html( $label ), esc_html( $value ) );
}
echo '</p></div>';
} );woocommerce_email_recipient_new_order 直接改收件人,按产品分类分流:
add_filter( 'woocommerce_email_recipient_new_order', function ( $recipient, $order ) {
if ( ! $order instanceof WC_Order || 'yes' !== $order->get_meta( '_is_inquiry' ) ) {
return $recipient;
}
$recipient = 'sales@example.com';
foreach ( $order->get_items() as $item ) {
if ( has_term( 'mosfet', 'product_cat', $item->get_product_id() ) ) {
$recipient = 'fae@example.com';
break;
}
}
return $recipient;
}, 10, 2 );收件人可以是多个,逗号分隔。但别一上来就抄送老板和销售群,先发给一个明确的人,由他转。抄送一多,没人觉得是自己的单。
客户那封确认邮件:订单落在 on-hold,客户会收到「您的订单已挂起」。要么在 WooCommerce → 设置 → 邮件里把 On-hold 那封关掉,只留站内的感谢页;要么改掉它的标题:
add_filter( 'woocommerce_email_subject_customer_on_hold_order', function ( $subject ) {
return '我们收到你的询价单了';
} );
add_filter( 'woocommerce_email_heading_customer_on_hold_order', function ( $heading ) {
return '询价单已收到';
} );感谢页的文案也要换成承诺:
add_filter( 'woocommerce_thankyou_order_received_text', function ( $text, $order ) {
if ( $order instanceof WC_Order && 'yes' === $order->get_meta( '_is_inquiry' ) ) {
return '询价单已收到。我们核对型号和数量后,会在 1 个工作日内回一封带报价的邮件。';
}
return $text;
}, 10, 2 );单条询价看订单详情就够了。麻烦的是同一家公司问过几次、问到哪一步了——在订单列表里翻很费劲,加一个汇总页:
add_action( 'admin_menu', function () {
add_submenu_page(
'woocommerce',
'询价汇总',
'询价汇总',
'manage_woocommerce',
'inquiry-summary',
'inquiry_summary_page'
);
} );
function inquiry_summary_page() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
$orders = wc_get_orders( array(
'limit' => 300,
'status' => array( 'wc-on-hold' ),
'orderby' => 'date',
'order' => 'DESC',
) );
$rows = array();
foreach ( $orders as $order ) {
if ( 'yes' !== $order->get_meta( '_is_inquiry' ) ) {
continue;
}
$company = $order->get_billing_company();
if ( ! $company ) {
$company = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
}
$key = strtolower( $company );
if ( ! isset( $rows[ $key ] ) ) {
$rows[ $key ] = array(
'company' => $company,
'count' => 0,
'last' => $order->get_date_created() ? $order->get_date_created()->date_i18n( 'Y-m-d H:i' ) : '',
'order' => $order,
);
}
$rows[ $key ]['count']++;
}
echo '<div class="wrap"><h1>询价汇总</h1>';
echo '<p>只统计状态为「待处理」且带询价标记的订单,最多 300 条。</p>';
echo '<table class="widefat striped"><thead><tr><th>客户</th><th>询价次数</th><th>最近一次</th><th>操作</th></tr></thead><tbody>';
foreach ( $rows as $row ) {
printf(
'<tr><td>%s</td><td>%d</td><td>%s</td><td><a href="%s">查看最近一单</a></td></tr>',
esc_html( $row['company'] ),
(int) $row['count'],
esc_html( $row['last'] ),
esc_url( $row['order']->get_edit_order_url() )
);
}
echo '</tbody></table></div>';
}wc_get_orders() 在传统订单存储和 HPOS 下都能用,所以这里不写 SQL。公司名大小写、后缀不一致会把同一家拆成两行(「ABC Ltd」和「abc」),量大了要么在结账处给输入提示,要么改成从客户账户带出公司名,别指望代码自动合并。
_is_inquiry 写进去了、四个自定义字段在订单详情页都有显示form-row-first / form-row-last 的字段有没有挤成两行错位最后一条最容易漏:把测试单删掉再上线,别让第一条真询价排在「测试 test」后面。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。