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

WooCommerce自定义结账字段交互及折扣功能修复请求

问题分析与修复方案

你的代码存在几个核心问题导致会员相关折扣不生效,以下是问题点和完整修复代码:

核心问题点

  1. POST数据获取逻辑错误:WooCommerce结账AJAX更新时,自定义字段不会直接通过$_POST传递,依赖session存储的状态才是正确方式。
  2. 会员折扣计算错误:需求是每件商品减200瑞典克朗,但原代码只减了固定200,未乘以商品数量。
  3. 会员选项未实现互斥:原JS没有处理两个会员选项的互斥逻辑,勾选一个时未自动取消另一个。
  4. 字段缺少必填验证:会员号输入框未设置必填属性,且前端/后端验证不完整。
  5. Nonce验证无效:原代码只做了验证,但没有输出对应的nonce字段,导致验证永远失败,阻断会员逻辑。
  6. 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);
}

修复说明

  1. 新增AJAX存储session:通过专门的AJAX动作将用户勾选的字段状态存储到WooCommerce Session,确保折扣逻辑能正确获取状态。
  2. 实现会员选项互斥:前端JS给两个会员选项添加统一类名,勾选时自动取消另一个选项的勾选状态。
  3. 修正折扣计算:遍历购物车商品数量,计算总折扣金额(每件200瑞典克朗)。
  4. 完善字段验证:前端设置必填属性,后端添加会员号格式验证,以及两个会员选项的互斥验证。
  5. 修复Nonce逻辑:添加统一的Nonce字段,确保AJAX请求的安全性。
  6. 调整Session重置时机:仅在订单完成后重置session,避免用户勾选状态丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:47:02