WooCommerce自定义结账字段交互及折扣功能修复请求
问题分析与修复方案
你的代码存在几个核心问题导致会员相关折扣不生效,以下是问题点和完整修复代码:
核心问题点
- POST数据获取逻辑错误:WooCommerce结账AJAX更新时,自定义字段不会直接通过
$_POST传递,依赖session存储的状态才是正确方式。 - 会员折扣计算错误:需求是每件商品减200瑞典克朗,但原代码只减了固定200,未乘以商品数量。
- 会员选项未实现互斥:原JS没有处理两个会员选项的互斥逻辑,勾选一个时未自动取消另一个。
- 字段缺少必填验证:会员号输入框未设置必填属性,且前端/后端验证不完整。
- Nonce验证无效:原代码只做了验证,但没有输出对应的nonce字段,导致验证永远失败,阻断会员逻辑。
- Session重置逻辑不合理:每次加载结账页面就重置session,导致用户勾选的状态丢失。
修复后的完整代码
// 添加结账自定义字段 add_action('woocommerce_review_order_before_submit', 'custom_checkout_fields'); function custom_checkout_fields() { echo '<div class="woocommerce-additional-fields">'; // 1. 我已是会员 woocommerce_form_field('is_member_checkbox', array( 'type' => 'checkbox', 'class' => array('input-checkbox member-option'), 'label' => __('我已是会员'), ), WC()->session->get('is_member_checkbox')); echo '<div class="is-member-fields" style="display:none;margin:10px 0 20px;">'; woocommerce_form_field('member_number', array( 'type' => 'text', 'class' => array('input-text'), 'label' => __('会员号'), 'placeholder' => __('请输入6位会员号'), 'required' => true, // 设置必填 ), WC()->session->get('member_number')); echo '</div>'; // 2. 我当过志愿者 woocommerce_form_field('volunteer_checkbox', array( 'type' => 'checkbox', 'class' => array('input-checkbox'), 'label' => __('我当过志愿者'), ), WC()->session->get('volunteer_checkbox')); // 3. 我是其他俱乐部会员 woocommerce_form_field('other_club_checkbox', array( 'type' => 'checkbox', 'class' => array('input-checkbox member-option'), 'label' => __('我是其他俱乐部会员'), ), WC()->session->get('other_club_checkbox')); echo '<div class="other-club-fields" style="display:none;margin:10px 0 20px;">'; woocommerce_form_field('other_club_number', array( 'type' => 'text', 'class' => array('input-text'), 'label' => __('其他俱乐部会员号'), 'placeholder' => __('请输入6位会员号'), 'required' => true, // 设置必填 ), WC()->session->get('other_club_number')); echo '</div>'; // 输出Nonce字段 wp_nonce_field('custom_checkout_fields_nonce', 'custom_checkout_nonce'); echo '</div>'; // JS逻辑:显示/隐藏字段 + 会员选项互斥 + 触发结账更新 ?> <script> jQuery(document).ready(function($) { // 初始化字段显示状态 if ($('#is_member_checkbox').is(':checked')) { $('.is-member-fields').show(); } if ($('#other_club_checkbox').is(':checked')) { $('.other-club-fields').show(); } // 会员选项互斥逻辑 $('.member-option').change(function() { if ($(this).is(':checked')) { $('.member-option').not(this).prop('checked', false).trigger('change'); } recalculateCart(); }); // 显示/隐藏会员号字段 $('#is_member_checkbox').change(function() { $('.is-member-fields').toggle($(this).is(':checked')); }); $('#other_club_checkbox').change(function() { $('.other-club-fields').toggle($(this).is(':checked')); }); // 志愿者选项触发更新 $('#volunteer_checkbox').change(function() { recalculateCart(); }); // 会员号输入改变时触发更新 $('#member_number, #other_club_number').on('input', function() { recalculateCart(); }); function recalculateCart() { // 存储字段状态到session(通过AJAX传递) const formData = { action: 'update_checkout_session', nonce: $('#custom_checkout_nonce').val(), is_member: $('#is_member_checkbox').is(':checked') ? '1' : '0', member_number: $('#member_number').val(), other_club: $('#other_club_checkbox').is(':checked') ? '1' : '0', other_club_number: $('#other_club_number').val(), volunteer: $('#volunteer_checkbox').is(':checked') ? '1' : '0' }; $.post(wc_checkout_params.ajax_url, formData, function() { $('body').trigger('update_checkout'); }); } }); </script> <?php } // AJAX更新session存储的字段状态 add_action('wp_ajax_update_checkout_session', 'update_checkout_session'); add_action('wp_ajax_nopriv_update_checkout_session', 'update_checkout_session'); function update_checkout_session() { if (!wp_verify_nonce($_POST['nonce'], 'custom_checkout_fields_nonce')) { wp_die('Invalid nonce'); } WC()->session->set('is_member_checkbox', $_POST['is_member'] === '1'); WC()->session->set('member_number', sanitize_text_field($_POST['member_number'])); WC()->session->set('other_club_checkbox', $_POST['other_club'] === '1'); WC()->session->set('other_club_number', sanitize_text_field($_POST['other_club_number'])); WC()->session->set('volunteer_checkbox', $_POST['volunteer'] === '1'); wp_die(); } // 应用折扣逻辑 add_action('woocommerce_cart_calculate_fees', 'custom_apply_discounts'); function custom_apply_discounts($cart) { if (is_admin() && !defined('DOING_AJAX')) return; $is_member = WC()->session->get('is_member_checkbox'); $member_number = WC()->session->get('member_number'); $other_club = WC()->session->get('other_club_checkbox'); $other_club_number = WC()->session->get('other_club_number'); $volunteer = WC()->session->get('volunteer_checkbox'); // 移除已存在的折扣,避免重复添加 foreach ($cart->get_fees() as $fee_key => $fee) { if (in_array($fee->get_name(), ['会员折扣', '志愿者折扣'])) { $cart->remove_fee($fee_key); } } // 本俱乐部会员折扣:每件商品减200瑞典克朗 if ($is_member && !empty($member_number) && preg_match('/^\d{6}$/', $member_number)) { $item_count = 0; foreach ($cart->get_cart() as $item) { $item_count += $item['quantity']; } $discount_amount = 200 * $item_count; $cart->add_fee(__('会员折扣'), -$discount_amount, false); } // 志愿者折扣:订单总价减100瑞典克朗 if ($volunteer) { $cart->add_fee(__('志愿者折扣'), -100, false); } // 其他俱乐部会员:仅验证,无折扣,但取消本俱乐部会员折扣 if ($other_club && !empty($other_club_number) && preg_match('/^\d{6}$/', $other_club_number)) { // 已通过移除所有折扣后重新添加,这里不需要额外操作 } } // 结账字段验证 add_action('woocommerce_after_checkout_validation', 'custom_checkout_validation'); function custom_checkout_validation($posted_data) { // 本俱乐部会员验证 if (isset($posted_data['is_member_checkbox']) && $posted_data['is_member_checkbox'] === '1') { if (empty($posted_data['member_number']) || !preg_match('/^\d{6}$/', $posted_data['member_number'])) { wc_add_notice(__('请输入有效的6位本俱乐部会员号'), 'error'); } } // 其他俱乐部会员验证 if (isset($posted_data['other_club_checkbox']) && $posted_data['other_club_checkbox'] === '1') { if (empty($posted_data['other_club_number']) || !preg_match('/^\d{6}$/', $posted_data['other_club_number'])) { wc_add_notice(__('请输入有效的6位其他俱乐部会员号'), 'error'); } } // 两个会员选项不能同时勾选(前端已处理,后端二次验证) if (isset($posted_data['is_member_checkbox'], $posted_data['other_club_checkbox']) && $posted_data['is_member_checkbox'] === '1' && $posted_data['other_club_checkbox'] === '1') { wc_add_notice(__('只能选择一个会员选项'), 'error'); } } // 订单完成后重置session add_action('woocommerce_thankyou', 'reset_checkout_session_after_order'); function reset_checkout_session_after_order() { WC()->session->set('is_member_checkbox', false); WC()->session->set('member_number', ''); WC()->session->set('other_club_checkbox', false); WC()->session->set('other_club_number', ''); WC()->session->set('volunteer_checkbox', false); }
修复说明
- 新增AJAX存储session:通过专门的AJAX动作将用户勾选的字段状态存储到WooCommerce Session,确保折扣逻辑能正确获取状态。
- 实现会员选项互斥:前端JS给两个会员选项添加统一类名,勾选时自动取消另一个选项的勾选状态。
- 修正折扣计算:遍历购物车商品数量,计算总折扣金额(每件200瑞典克朗)。
- 完善字段验证:前端设置必填属性,后端添加会员号格式验证,以及两个会员选项的互斥验证。
- 修复Nonce逻辑:添加统一的Nonce字段,确保AJAX请求的安全性。
- 调整Session重置时机:仅在订单完成后重置session,避免用户勾选状态丢失。
内容的提问来源于stack exchange,提问作者Inger
相关产品推荐
相关产品推荐

