phpjquerywordpresswoocommercesession-variables

Dynamic fees calculated from custom checkout fields in WooCommerce


I have a WooCommerce store specialized in renting out beach equipment. I added a dropdown menu to the checkout page where a customer can select for the order to be delivered or that they can pick it up themselves. If they select the delivery option, a $10 fee is added to the cart total, listed as Delivery Fee. If they select pickup, a second dropdown appears where they can select a pickup location. I want to add the option for clients to purchase cold drinks from the checkout page if they select a specific pickup location (Kamini's Kitchen), the list of which should pop up when they select this one pickup location. I wrote it so that checkboxes appear for each drink and a quantity that can be selected. When a drink is added, it should be added to the cart under its proper name and the listed price should be added to the total (times the multiplier). Lastly, the selected drink options should appear in the order e-mails and on the admin backend.

Here is my code attempt based on this answer thread:

// Utility function: Get Delivery or Pickup options
function get_delivery_or_pickup_options() {
    return array(
        ''          => esc_html__( 'Select an option', 'woocommerce' ),
        'delivery'  => esc_html__( 'Delivery Service', 'woocommerce' ),
        'pickup'    => esc_html__( 'Pickup', 'woocommerce' )
    );
}

// Utility function: Get pickup location options
function get_pickup_location_options() {
    return array(
        ''                  => esc_html__( 'Select an option', 'woocommerce' ),
        'main_office'       => esc_html__( 'Pavia 20F - Main Office', 'woocommerce' ),
        'kaminis_kitchen'   => esc_html__( "Kamini's Kitchen (Baby Beach)", 'woocommerce' )
    );
}

add_action( 'woocommerce_before_order_notes', 'add_delivery_or_pickup_fields', 10 );
function add_delivery_or_pickup_fields( $checkout ) {
    // Existing dropdowns...
    woocommerce_form_field( 'delivery_or_pickup', array(
        'type'      => 'select',
        'class'     => array('form-row-wide'),
        'label'     => esc_html__( 'Delivery or Pickup?', 'woocommerce' ),
        'required'  => true,
        'options'   => get_delivery_or_pickup_options(),
    ), $checkout->get_value('delivery_or_pickup') );

    woocommerce_form_field('pickup_location', array(
        'type' => 'select',
        'class' => array('form-row-wide'),
        'label' => esc_html__( 'Select pickup location:', 'woocommerce' ),
        'required' => true,
        'options' => get_pickup_location_options(),
    ), $checkout->get_value('pickup_location'));

    // Drinks Section - Hidden by default, displayed by JS if pickup_location == kaminis_kitchen
    echo '<div id="drink_options_section" style="display:none;">';
    echo '<h3>' . esc_html__('If you’re renting a beach cooler, you have the option to pre-purchase drinks here and have the cooler loaded up with ice and drinks when picking it up', 'woocommerce') . '</h3>';

    $drink_items = array(
        'bag_of_ice'       => array('Bag of Ice', 4.00),
        'chill'            => array('Chill', 5.00),
        'balashi'          => array('Balashi', 5.00),
        'magic_mango'      => array('Magic Mango', 5.00),
        'amstel_bright'    => array('Amstel Bright', 6.00),
        'budweiser'        => array('Budweiser', 5.00),
        'bud_light'        => array('Bud Light', 5.00),
        'corona'           => array('Corona', 6.00),
        'heineken'         => array('Heineken', 5.00),
        'presidente'       => array('Presidente', 5.00),
        'modelo'           => array('Modelo', 5.00),
        'polar'            => array('Polar', 4.00),
        'guinness'         => array('Guinness', 7.00),
        'coke'             => array('Coke', 3.00),
        'coke_zero'        => array('Coke Zero', 3.00),
        'sprite'           => array('Sprite', 3.00),
        'sprite_zero'      => array('Sprite Zero', 3.00),
        'gingerale'        => array('Gingerale', 3.00),
        'fuze_tea'         => array('Fuze Tea', 3.00),
        'club_soda'        => array('Club Soda', 3.00),
        'tonic_water'      => array('Tonic Water', 3.00),
        'fanta_cherry'     => array('Fanta Cherry', 3.00),
        'fanta_grape'      => array('Fanta Grape', 3.00),
        'fanta_orange'     => array('Fanta Orange', 3.00),
        'bottled_water'    => array('Bottled Water', 2.57),
    );

    foreach ( $drink_items as $key => $item ) {
        echo '<p><label><input type="checkbox" name="drinks[' . esc_attr($key) . ']" value="1"> ' . esc_html($item[0]) . ' ($' . number_format($item[1], 2) . ')</label> ';
        echo '<input type="number" name="drink_qty[' . esc_attr($key) . ']" min="1" max="99" value="1" style="width:60px;margin-left:10px;" /></p>';
    }

    echo '</div>';
}


// JavaScript to toggle visibility of the pickup location dropdown and update delivery fee
add_action( 'woocommerce_checkout_init', 'add_delivery_or_pickup_js_script' );
function add_delivery_or_pickup_js_script() {
    wc_enqueue_js("

    const pickupField = $('#pickup_location_field');
    const drinkOptions = $('#drink_options_section');

    function toggleDrinkOptions() {
        if ($('#pickup_location').val() === 'kaminis_kitchen') {
            drinkOptions.show();
        } else {
            drinkOptions.hide();
        }
    }

    if ($('#delivery_or_pickup').val() !== 'pickup') {
        pickupField.hide();
        drinkOptions.hide();
    }

    $(document.body).on('change', '#delivery_or_pickup', function() { 
        const val = $(this).val();
        val === 'pickup' ? pickupField.show() : pickupField.hide();
        $.post(wc_checkout_params.ajax_url, {
            action: 'save_delivery_or_pickup_to_session',
            delivery_or_pickup: val
        }, function() {
            $(document.body).trigger('update_checkout');
        });
    });

    $(document.body).on('change', '#pickup_location', function() {
        toggleDrinkOptions();
        $(document.body).trigger('update_checkout');
    });

    // Send drink selections via AJAX
    function updateDrinkSessionData() {
        const selectedDrinks = {};
        $('#drink_options_section input[type=checkbox]:checked').each(function() {
            const key = $(this).attr('name').replace('drinks[', '').replace(']', '');
            const qty = $('#drink_qty_' + key).val() || 1;
            selectedDrinks[key] = qty;
        });

        $.post(wc_checkout_params.ajax_url, {
            action: 'save_drink_options_to_session',
            drinks: selectedDrinks
        }, function() {
            $(document.body).trigger('update_checkout');
        });
    }

    $(document.body).on('change', '#drink_options_section input', updateDrinkSessionData);

    toggleDrinkOptions();
");
}


// Ajax request: Save selection to WooCommerce session
add_action('wp_ajax_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
add_action('wp_ajax_nopriv_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
function save_delivery_or_pickup_to_session() {
    if ( isset($_POST['delivery_or_pickup']) ) {
        WC()->session->set('delivery_or_pickup', sanitize_text_field($_POST['delivery_or_pickup']));
    }
    wp_die();
}

// Add delivery fee if "Delivery Service" is selected
add_action( 'woocommerce_cart_calculate_fees', 'add_delivery_option_fee_based_on_session' );
function add_delivery_option_fee_based_on_session( $cart ) {
    if (is_admin() && !defined('DOING_AJAX')) return;

    if ( is_checkout() && WC()->session->get('delivery_or_pickup') === 'delivery' ) {
        $delivery_fee = 10;
        $cart->add_fee( esc_html__( 'Delivery Fee', 'woocommerce' ), $delivery_fee );
    }
}

// Save drink selection to WooCommerce session
add_filter('woocommerce_checkout_posted_data', 'capture_drink_data_during_ajax');
function capture_drink_data_during_ajax( $data ) {
    if ( isset($_POST['drinks']) && isset($_POST['drink_qty']) ) {
        $data['drinks'] = $_POST['drinks'];
        $data['drink_qty'] = $_POST['drink_qty'];
    }
    return $data;
}

add_action('wp_ajax_save_drink_options_to_session', 'save_drink_options_to_session');
add_action('wp_ajax_nopriv_save_drink_options_to_session', 'save_drink_options_to_session');
function save_drink_options_to_session() {
    $drinks = isset($_POST['drinks']) ? $_POST['drinks'] : array();
    $sanitized = array();

    foreach ($drinks as $drink => $qty) {
        $drink = sanitize_key($drink);
        $qty = max(1, intval($qty));
        $sanitized[$drink] = $qty;
    }
    
    WC()->session->set('drink_data', $sanitized);
    wp_die();
}


add_action('woocommerce_cart_calculate_fees', 'add_drink_fee_via_ajax', 20, 1);
function add_drink_fee_via_ajax($cart) {
    if (is_admin() && !defined('DOING_AJAX')) return;

    $pickup_location = isset($_POST['pickup_location']) ? sanitize_text_field($_POST['pickup_location']) : WC()->session->get('pickup_location');
    if ($pickup_location !== 'kaminis_kitchen') return;

    $drink_data = WC()->session->get('drink_data', []);
    if (empty($drink_data)) return;

    $prices = array(
        'bag_of_ice' => 4.00, 'chill' => 5.00, 'balashi' => 5.00, 'magic_mango' => 5.00,
        'amstel_bright' => 6.00, 'budweiser' => 5.00, 'bud_light' => 5.00, 'corona' => 6.00,
        'heineken' => 5.00, 'presidente' => 5.00, 'modelo' => 5.00, 'polar' => 4.00, 'guinness' => 7.00,
        'coke' => 3.00, 'coke_zero' => 3.00, 'sprite' => 3.00, 'sprite_zero' => 3.00,
        'gingerale' => 3.00, 'fuze_tea' => 3.00, 'club_soda' => 3.00, 'tonic_water' => 3.00,
        'fanta_cherry' => 3.00, 'fanta_grape' => 3.00, 'fanta_orange' => 3.00, 'bottled_water' => 2.57,
    );

    $total = 0;
    foreach ($drink_data as $drink => $qty) {
        if (isset($prices[$drink])) {
            $total += $prices[$drink] * $qty;
        }
    }

    if ($total > 0) {
        $cart->add_fee(__('Drink Options', 'woocommerce'), $total);
    }
}


add_action('woocommerce_checkout_order_processed', function () {
    WC()->session->__unset('drink_data');
}, 10, 1);

// Validate custom shipping options
add_action( 'woocommerce_after_checkout_validation', 'delivery_or_pickup_validation', 20, 2 );
function delivery_or_pickup_validation( $data, $errors ) {
    // Delivery or Pickup validation
    if ( isset($_POST['delivery_or_pickup']) && empty($_POST['delivery_or_pickup']) ) {
        $errors->add( 'delivery_or_pickup', esc_html__( 'You must choose between "Delivery" or "Pickup" option.', 'woocommerce' ), 'error' );
    }
    // Pickup location validation
    elseif ( isset($_POST['delivery_or_pickup']) && $_POST['delivery_or_pickup'] === 'pickup' 
    && isset($_POST['pickup_location']) && empty($_POST['pickup_location']) ) {
        $errors->add( 'pickup_location', esc_html__( 'You must choose a pickup location.', 'woocommerce' ), 'error' );
    }
}

// Save custom chosen shipping option details as order metadata
add_action( 'woocommerce_checkout_create_order', 'save_delivery_or_pickup_as_order_metadata', 10 );
function save_delivery_or_pickup_as_order_metadata( $order ) {
    if ( isset($_POST['delivery_or_pickup']) && !empty($_POST['delivery_or_pickup']) ) {
        $order->add_meta_data('_delivery_or_pickup', esc_attr($_POST['delivery_or_pickup']), true );
    }
    if ( isset($_POST['pickup_location']) && !empty($_POST['pickup_location']) ) {
        $order->add_meta_data('_pickup_location', esc_attr($_POST['pickup_location']), true);
    }
    // Remove the WC Session Variable
    if (  WC()->session->__isset('delivery_or_pickup') ) {
        WC()->session->__unset('delivery_or_pickup');
    }
}

add_action( 'woocommerce_checkout_create_order', 'save_drinks_to_order_meta', 20, 1 );
function save_drinks_to_order_meta( $order ) {
    if ( isset($_POST['drinks']) ) {
        $items = array(
            'bag_of_ice' => 'Bag of Ice', 'chill' => 'Chill', 'balashi' => 'Balashi', 'magic_mango' => 'Magic Mango',
            'amstel_bright' => 'Amstel Bright', 'budweiser' => 'Budweiser', 'bud_light' => 'Bud Light',
            'corona' => 'Corona', 'heineken' => 'Heineken', 'presidente' => 'Presidente',
            'modelo' => 'Modelo', 'polar' => 'Polar', 'guinness' => 'Guinness', 'coke' => 'Coke',
            'coke_zero' => 'Coke Zero', 'sprite' => 'Sprite', 'sprite_zero' => 'Sprite Zero',
            'gingerale' => 'Gingerale', 'fuze_tea' => 'Fuze Tea', 'club_soda' => 'Club Soda',
            'tonic_water' => 'Tonic Water', 'fanta_cherry' => 'Fanta Cherry', 'fanta_grape' => 'Fanta Grape',
            'fanta_orange' => 'Fanta Orange', 'bottled_water' => 'Bottled Water'
        );

        $drinks_ordered = array();
        foreach ($_POST['drinks'] as $key => $val) {
            $qty = intval($_POST['drink_qty'][$key] ?? 1);
            if ($qty > 0 && isset($items[$key])) {
                $drinks_ordered[] = $items[$key] . ' x' . $qty;
            }
        }

        if ( !empty($drinks_ordered) ) {
            $order->add_meta_data('_drink_options', implode(', ', $drinks_ordered));
        }
    }
}


// Update custom chosen shipping option details as USER metadata (useful for next checkout)
add_action( 'woocommerce_checkout_update_customer', 'save_delivery_or_pickup_as_user_metadata', 10 );
function save_delivery_or_pickup_as_user_metadata( $customer ) {
    if ( isset($_POST['delivery_or_pickup']) && !empty($_POST['delivery_or_pickup']) ) {
        $customer->update_meta_data('delivery_or_pickup', esc_attr($_POST['delivery_or_pickup']));
    }
    if ( isset($_POST['pickup_location']) && !empty($_POST['pickup_location']) ) {
        $customer->update_meta_data('pickup_location', esc_attr($_POST['pickup_location']));
    }
}

// Display chosen custom shipping option details in the admin panel under shipping address
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'display_delivery_or_pickup_meta_in_admin_order', 10 );
function display_delivery_or_pickup_meta_in_admin_order( $order ) {
    // Delivery or Pickup
    if ( $value = $order->get_meta('_delivery_or_pickup') ) {
        $options = get_delivery_or_pickup_options();
        printf( '<p><strong>%s:</strong> %s', esc_html__('Shipping', 'woocommerce' ), $options[$value] );

        // Pickup location
        if ( $value = $order->get_meta('_pickup_location') ) {
            $options = get_pickup_location_options();
            printf( '<br><strong>%s:</strong> %s', esc_html__('Location', 'woocommerce' ), $options[$value] );
        }
        echo '</p>';
    }
}

// Display chosen custom shipping option details in order total lines (customer orders and email notifications)
add_filter( 'woocommerce_get_order_item_totals', 'insert_custom_line_order_item_totals', 10, 3 );
function insert_custom_line_order_item_totals( $total_rows, $order, $tax_display ){
    $shipping = $order->get_meta('_delivery_or_pickup');
    $options1  = get_delivery_or_pickup_options();
    $location = $order->get_meta('_pickup_location');
    $options2  = get_pickup_location_options();
    $key_target = array_key_exists('discount', $total_rows) ? 'discount' : 'cart_subtotal';
    $new_total_rows = array();

    // Loop through total rows
    foreach( $total_rows as $key => $value ){
        $new_total_rows[$key] = $total_rows[$key];

        if( 'cart_subtotal' === $key ) {
            $new_total_rows['shipping2'] = array(
                'label' => esc_html__('Shipping option:'),
                'value' => $options1[$shipping],
            );

            if ( $location ) {
                $new_total_rows['location'] = array(
                    'label' => esc_html__('Pickup location:'),
                    'value' => $options2[$location],
                );
                $new_total_rows['drink_options'] = array(
    'label' => esc_html__('Drink Options:'),
    'value' => esc_html($order->get_meta('_drink_options')),
);
            }

        }
    }
    return $new_total_rows;
}

But when customer select an optional drink from the list, the additional related fee get not added: The checkout get refreshed but nothing gets added. Here the "drinks" part is not working as intended. In my code, I have defined an array of all drinks with the label name and the cost for each option, and this drinks options list appear conditionally based on the selected pickup location via JavaScript. Here, I'm stuck with the part where the selected options are supposed to be added to the cart dynamically via AJAX.


Solution

  • Ther are multiple mistakes, unnecessary and missing things in your PHP and jQuery code.

    As you are storing the selected fields values via Ajax in WC Session variables, don't use PHP $_POST to retreive the data outside PHP Ajax receiver functions. Use the WC Sessions variables to get the selected values for the dynamic calculated fee, and to save those selected values as custom order metadata.

    Try the following instead:

    // Utility function: Remove custom WC Session Variables
    function remove_custom_wc_session_variables() {
        if (  WC()->session->__isset('delivery_or_pickup') ) {
            WC()->session->__unset('delivery_or_pickup');
        }
        if (  WC()->session->__isset('pickup_location') ) {
            WC()->session->__unset('pickup_location');
        }
        if (  WC()->session->__isset('drink_options') ) {
            WC()->session->__unset('drink_options');
        }
    }
    
    // Utility function: Get Delivery or Pickup options
    function get_delivery_or_pickup_options() {
        return array(
            ''          => esc_html__( 'Select an option', 'woocommerce' ),
            'delivery'  => esc_html__( 'Delivery Service', 'woocommerce' ),
            'pickup'    => esc_html__( 'Pickup', 'woocommerce' )
        );
    }
    
    // Utility function: Get pickup location options
    function get_pickup_location_options() {
        return array(
            ''                  => esc_html__( 'Select an option', 'woocommerce' ),
            'main_office'       => esc_html__( 'Pavia 20F - Main Office', 'woocommerce' ),
            'kaminis_kitchen'   => esc_html__( "Kamini's Kitchen (Baby Beach)", 'woocommerce' )
        );
    }
    
    // Utility function: Get pickup drinks options
    function get_drink_items_options() {
        return array(
            'bag_of_ice'       => array('Bag of Ice', 4.00),
            'chill'            => array('Chill', 5.00),
            'balashi'          => array('Balashi', 5.00),
            'magic_mango'      => array('Magic Mango', 5.00),
            'amstel_bright'    => array('Amstel Bright', 6.00),
            'budweiser'        => array('Budweiser', 5.00),
            'bud_light'        => array('Bud Light', 5.00),
            'corona'           => array('Corona', 6.00),
            'heineken'         => array('Heineken', 5.00),
            'presidente'       => array('Presidente', 5.00),
            'modelo'           => array('Modelo', 5.00),
            'polar'            => array('Polar', 4.00),
            'guinness'         => array('Guinness', 7.00),
            'coke'             => array('Coke', 3.00),
            'coke_zero'        => array('Coke Zero', 3.00),
            'sprite'           => array('Sprite', 3.00),
            'sprite_zero'      => array('Sprite Zero', 3.00),
            'gingerale'        => array('Gingerale', 3.00),
            'fuze_tea'         => array('Fuze Tea', 3.00),
            'club_soda'        => array('Club Soda', 3.00),
            'tonic_water'      => array('Tonic Water', 3.00),
            'fanta_cherry'     => array('Fanta Cherry', 3.00),
            'fanta_grape'      => array('Fanta Grape', 3.00),
            'fanta_orange'     => array('Fanta Orange', 3.00),
            'bottled_water'    => array('Bottled Water', 2.57),
        );
    }
    
    add_action( 'woocommerce_before_order_notes', 'add_delivery_or_pickup_fields', 10 );
    function add_delivery_or_pickup_fields( $checkout ) {
    
        woocommerce_form_field( 'delivery_or_pickup', array(
            'type'      => 'select',
            'class'     => array('form-row-wide'),
            'label'     => esc_html__( 'Delivery or Pickup?', 'woocommerce' ),
            'required'  => true,
            'options'   => get_delivery_or_pickup_options(),
        ), '');
    
        woocommerce_form_field('pickup_location', array(
            'type'      => 'select',
            'class'     => array('form-row-wide'),
            'label'     => esc_html__( 'Select pickup location:', 'woocommerce' ),
            'required'  => true,
            'options'   => get_pickup_location_options(),
        ), '');
    
        // Drinks Section - Hidden by default, displayed by JS if pickup_location == kaminis_kitchen
        echo '<div id="drink_options_section" style="display:none;">';
        echo '<h3>' . esc_html__('If you’re renting a beach cooler, you have the option to pre-purchase drinks here and have the cooler loaded up with ice and drinks when picking it up', 'woocommerce') . '</h3>';
    
        foreach ( get_drink_items_options() as $key => $item ) {
            printf('<p><label><input type="checkbox" name="drinks[%s]" value="1" /> %s (%s)</label> 
            <input type="number" name="qty-%s" min="1" max="99" value="1" style="width:60px;margin-left:10px;" /></p>',
            esc_attr($key), esc_attr($item[0]), wp_strip_all_tags( wc_price($item[1]) ), esc_attr($key) );
        }
        echo '</div>';
    
        // Remove custom WC Session Variables on checkout load
        remove_custom_wc_session_variables();
    }
    
    
    // JavaScript to toggle visibility of the pickup location dropdown and update delivery fee
    add_action( 'woocommerce_checkout_init', 'add_delivery_or_pickup_js_script' );
    function add_delivery_or_pickup_js_script() {
        // Enqueue Javascript for checkout
        wc_enqueue_js("const pickupField = $('#pickup_location_field'),
        drinkOptions = $('#drink_options_section');
    
        var deliveryOrPickupVal = '';
    
        // Reset drinks
        function resetDrinks(){
            $('#drink_options_section input[type=checkbox]').each(function() {
                if ( $(this).is(':checked') ) {
                    $(this).prop('checked', false);
                }
            });
            $('#drink_options_section input[type=number]').each(function() {
                if ( $(this).val() != 1 ) {
                    $(this).val(1);
                }
            });
        }
    
        function triggerDeliveryOrPickupAjax( location ) {
            $.ajax({
                url: wc_checkout_params.ajax_url,
                type: 'POST',
                data: {
                    action: 'save_delivery_or_pickup_to_session',
                    'delivery_or_pickup': deliveryOrPickupVal,
                    'pickup_location': location
                },
                success: function (response) {
                    $(document.body).trigger('update_checkout');
                }
            });
        }
    
        pickupField.hide();
        drinkOptions.hide();
    
        // Send delivery or pickup selection via AJAX
        $(document.body).on('change', '#delivery_or_pickup', function() { 
            if ( $(this).val() === 'pickup' ) {
                pickupField.show();
            } else {
                pickupField.hide(); 
                $('#pickup_location').val('');
                drinkOptions.hide();
                resetDrinks();
            }
            deliveryOrPickupVal = $(this).val();
            triggerDeliveryOrPickupAjax( '' );
        });
    
        // Send pickup location choice via AJAX
        $(document.body).on('change', '#pickup_location', function() {
            if ( $(this).val() === 'kaminis_kitchen' ) {
                drinkOptions.show();
            } else {
                drinkOptions.hide();
                resetDrinks();
            }
            triggerDeliveryOrPickupAjax( $(this).val() );
        });
        
        // Send drink selected options via AJAX
        $(document.body).on('change', '#drink_options_section input', function() {
            var selectedDrinks = {};
    
            $('#drink_options_section input[type=checkbox]:checked').each(function() {
                const key = $(this).attr('name').replace('drinks[', '').replace(']', ''),
                      qty = $('[name=qty-'+key+']').val() || 1;
    
                selectedDrinks[key] = qty;
            });
    
            $.ajax({
                url: wc_checkout_params.ajax_url,
                type: 'POST',
                data: {
                    'action': 'save_drink_options_to_session',
                    'drink_options': selectedDrinks
                },
                success: function (response) {
                    $(document.body).trigger('update_checkout');
                }
            });
        });");
    }
    
    
    // Ajax request: Save delivery_or_pickup to WooCommerce session
    add_action('wp_ajax_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
    add_action('wp_ajax_nopriv_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
    function save_delivery_or_pickup_to_session() {
        if ( isset($_POST['delivery_or_pickup']) ) {
            $delivery_or_pickup = esc_attr($_POST['delivery_or_pickup']);
    
            WC()->session->set('delivery_or_pickup', $delivery_or_pickup);
    
            // Handle pickup location to WC Session
            if ( isset($_POST['pickup_location']) ) {
                WC()->session->set('pickup_location', esc_attr($_POST['pickup_location']));
            }
        }
        wp_die();
    }
    
    // Ajax request: Save drink_options to WooCommerce session
    add_action('wp_ajax_save_drink_options_to_session', 'save_drink_options_to_session');
    add_action('wp_ajax_nopriv_save_drink_options_to_session', 'save_drink_options_to_session');
    function save_drink_options_to_session() {
        $drink_options = isset($_POST['drink_options']) ? $_POST['drink_options'] : array();
        
        $sanitized_options = array();
    
        foreach ($drink_options as $drink => $qty) {
            $sanitized_options[sanitize_key($drink)] = max(1, intval($qty));
        }
        
        WC()->session->set('drink_options', $sanitized_options); 
        wp_die(print_r($sanitized_options, true));
    }
    
    // Add delivery fee if "Delivery Service" is selected
    add_action( 'woocommerce_cart_calculate_fees', 'add_custom_fees_based_on_wc_session_variables' );
    function add_custom_fees_based_on_wc_session_variables( $cart ) {
        if (is_admin() && !defined('DOING_AJAX')) return;
    
        $delivery_or_pickup = WC()->session->get('delivery_or_pickup');
    
        if ( is_checkout() && $delivery_or_pickup === 'delivery' ) {
            $delivery_fee = 10;
    
            // Delivery fee
            $cart->add_fee( esc_html__( 'Delivery Fee', 'woocommerce' ), $delivery_fee );
        
            if ( WC()->session->get('pickup_location') !== 'kaminis_kitchen' ) return;
    
        } elseif( $delivery_or_pickup === 'pickup' && WC()->session->get('pickup_location') === 'kaminis_kitchen' ) {
        
            $drink_options = (array) WC()->session->get('drink_options');
            
            if ( empty($drink_options) ) return;
        
            $drink_items = get_drink_items_options();
        
            $total = 0;
        
            foreach ($drink_options as $drink => $qty) {
                if (isset($drink_items[$drink][1])) {
                    $total += $drink_items[$drink][1] * $qty;
                }
            }
            // Drink options fee
            if ( $total ) {
                $cart->add_fee( esc_html__('Drink Options', 'woocommerce'), $total );
            }
        }
    }
    
    
    // Validate custom shipping options
    add_action( 'woocommerce_after_checkout_validation', 'delivery_or_pickup_validation', 20, 2 );
    function delivery_or_pickup_validation( $data, $errors ) {
        $delivery_or_pickup = WC()->session->get('delivery_or_pickup');
    
        // Delivery or Pickup validation
        if ( ! $delivery_or_pickup ) {
            $errors->add( 'delivery_or_pickup', esc_html__( 'You must choose between "Delivery" or "Pickup" options.', 'woocommerce' ), 'error' );
        }
        // Pickup location validation
        elseif ( $delivery_or_pickup === 'pickup' && ! WC()->session->get('pickup_location') ) {
            $errors->add( 'pickup_location', esc_html__( 'You must choose a pickup location.', 'woocommerce' ), 'error' );
        }
    }
    
    // Save custom chosen shipping option details as order metadata
    add_action( 'woocommerce_checkout_create_order', 'save_delivery_or_pickup_as_order_metadata', 10 );
    function save_delivery_or_pickup_as_order_metadata( $order ) {
        if ( $delivery_or_pickup = WC()->session->get('delivery_or_pickup') ) {
            $order->add_meta_data('_delivery_or_pickup', $delivery_or_pickup, true );
    
            if ( $delivery_or_pickup === 'pickup' && WC()->session->__isset('pickup_location') ) {
                $order->add_meta_data('_pickup_location', WC()->session->get('pickup_location'), true);
            }
        }
    
        if ( $drink_options = WC()->session->get('drink_options') ) { 
            $drink_items    = get_drink_items_options();
            $drinks_ordered = array();
    
            foreach ( $drink_options as $option => $qty ) {
                if ( isset($drink_items[$option][0]) ) {
                    $drinks_ordered[] = $drink_items[$option][0] . ' x' . $qty;
                }
            }
    
            if ( !empty($drinks_ordered) ) {
                $order->add_meta_data('_drink_options', implode(', ', $drinks_ordered));
            }
        }
        remove_custom_wc_session_variables();
    
    }
    
    // Display chosen custom shipping option details in the admin panel under shipping address
    add_action( 'woocommerce_admin_order_data_after_shipping_address', 'display_delivery_or_pickup_meta_in_admin_order', 10 );
    function display_delivery_or_pickup_meta_in_admin_order( $order ) {
        // Delivery or Pickup
        if ( $value = $order->get_meta('_delivery_or_pickup') ) {
            $options = get_delivery_or_pickup_options();
            printf( '<p><strong>%s:</strong> %s', esc_html__('Shipping', 'woocommerce' ), $options[$value] );
    
            // Pickup location
            if ( $value = $order->get_meta('_pickup_location') ) {
                $options = get_pickup_location_options();
                printf( '<br><strong>%s:</strong> %s', esc_html__('Location', 'woocommerce' ), $options[$value] );
            }
            if ( $drink_options = $order->get_meta('_drink_options') ) {
                printf( '<br><strong>%s:</strong> %s', esc_html__('Drinks', 'woocommerce' ), $drink_options );
            }
            echo '</p>';
        }
    }
    
    // Display chosen custom shipping option details in order total lines (customer orders and email notifications)
    add_filter( 'woocommerce_get_order_item_totals', 'insert_custom_line_order_item_totals', 10, 3 );
    function insert_custom_line_order_item_totals( $total_rows, $order, $tax_display ){
        $shipping = $order->get_meta('_delivery_or_pickup');
        $options1  = get_delivery_or_pickup_options();
        $location = $order->get_meta('_pickup_location');
        $options2  = get_pickup_location_options();
        $key_target = array_key_exists('discount', $total_rows) ? 'discount' : 'cart_subtotal';
        $new_total_rows = array();
    
        // Loop through total rows
        foreach( $total_rows as $key => $value ){
            $new_total_rows[$key] = $total_rows[$key];
    
            if( 'cart_subtotal' === $key ) {
                $new_total_rows['shipping2'] = array(
                    'label' => esc_html__('Shipping option:'),
                    'value' => $options1[$shipping]
                );
    
                if ( $location ) {
                    $new_total_rows['location'] = array(
                        'label' => esc_html__('Pickup location:'),
                        'value' => $options2[$location]
                    );
    
                    if ( $drink_options = $order->get_meta('_drink_options') ) {
                        $new_total_rows['drink_options'] = array(
                            'label' => esc_html__('Drink details:'),
                            'value' => $drink_options
                        );
                    }
                }
            }
        }
        return $new_total_rows;
    }
    

    It should better work now.