Search code examples
phpwoocommercemetadatacheckoutorders

Display WooCommerce custom checkout field values on orders and email notifications


I have some custom WooCommerce chekcout fields in the form of dropdown menus. I want whichever options are selected in these dropdowns to be displayed in the order details on the admin side, as well as in the order emails. However, I can't get this to work. Whatever is selected in the dropdowns is not displayed in the emails or on the admin side in the order details, which is what I want.

I want to save the information from the dropdowns in the order metadeta, and this is what I want to accomplish with my code. Creating the dropdowns works, as well as changing the total order amount based on whether or not the Delivery Service is selected. If Pickup is selected, a second dropdown appears which allows the client to select a pickup location. Information from both these dropdowns should appear in the order details on the admin side, as well as in any order emails that display the order details.

I tried to use woocommerce_checkout_create_order to save _delivery_or_pickup and _pickup_location directly to the order meta at order creation time, then use woocommerce_admin_order_data_after_order_details for the most consistent display in the WooCommerce admin panel. The woocommerce_email_order_meta hook is meant to display the custom fields in both the admin and customer emails.

My code is as follows:

// Add custom fields to the checkout page
add_action('woocommerce_before_order_notes', 'add_delivery_or_pickup_fields');

function add_delivery_or_pickup_fields($checkout) {
    // First dropdown for Delivery or Pickup
    woocommerce_form_field('delivery_or_pickup', array(
        'type' => 'select',
        'class' => array('form-row-wide'),
        'label' => __('Delivery or Pickup?', 'woocommerce'),
        'required' => true,
        'options' => array(
            '' => __('Select an option', 'woocommerce'),
            'delivery' => __('Delivery Service', 'woocommerce'),
            'pickup' => __('Pickup', 'woocommerce')
        ),
    ), $checkout->get_value('delivery_or_pickup'));

    // Second dropdown for pickup location (initially hidden, shown via JavaScript)
    woocommerce_form_field('pickup_location', array(
        'type' => 'select',
        'class' => array('form-row-wide', 'pickup-location-field'), // 'pickup-location-field' class used to control visibility
        'label' => __('Select pickup location:', 'woocommerce'),
        'options' => array(
            '' => __('Select a location', 'woocommerce'),
            'main_office' => __('Pavia 20F - Main Office', 'woocommerce'),
            'kaminis_kitchen' => __('Kamini\'s Kitchen (Baby Beach)', 'woocommerce')
        ),
    ), $checkout->get_value('pickup_location'));

    // JavaScript to toggle visibility of the pickup location dropdown and update delivery fee
    ?>
    <script type="text/javascript">
        jQuery(document).ready(function($) {
            $('.pickup-location-field').hide();

            $('#delivery_or_pickup').change(function() {
                if ($(this).val() === 'pickup') {
                    $('.pickup-location-field').show();
                } else {
                    $('.pickup-location-field').hide();
                }
                
                var deliveryOrPickup = $(this).val();
                $.ajax({
                    type: 'POST',
                    url: wc_checkout_params.ajax_url,
                    data: {
                        action: 'save_delivery_or_pickup_to_session',
                        delivery_or_pickup: deliveryOrPickup,
                    },
                    success: function() {
                        $('body').trigger('update_checkout');
                    }
                });
            }).change();
        });
    </script>
    <?php
}

// 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_fee_based_on_session');

function add_delivery_fee_based_on_session($cart) {
    if (is_admin() && !defined('DOING_AJAX')) return;

    $delivery_or_pickup = WC()->session->get('delivery_or_pickup');

    if ($delivery_or_pickup === 'delivery') {
        $delivery_fee = 10;
        $cart->add_fee(__('Delivery Fee', 'woocommerce'), $delivery_fee);
    }
}

// Save custom fields to order meta on order creation
add_action('woocommerce_checkout_create_order', 'save_delivery_or_pickup_to_order', 10, 2);

function save_delivery_or_pickup_to_order($order, $data) {
    if (!empty($_POST['delivery_or_pickup'])) {
        $order->update_meta_data('_delivery_or_pickup', sanitize_text_field($_POST['delivery_or_pickup']));
    }
    if (!empty($_POST['pickup_location'])) {
        $order->update_meta_data('_pickup_location', sanitize_text_field($_POST['pickup_location']));
    }
}

// Display custom fields in the admin panel under order details
add_action('woocommerce_admin_order_data_after_order_details', 'display_delivery_or_pickup_in_admin_order_meta', 10, 1);

function display_delivery_or_pickup_in_admin_order_meta($order) {
    $delivery_or_pickup = $order->get_meta('_delivery_or_pickup');
    $pickup_location = $order->get_meta('_pickup_location');

    if ($delivery_or_pickup) {
        echo '<p><strong>' . __('Delivery or Pickup') . ':</strong> ' . ($delivery_or_pickup === 'delivery' ? 'Delivery Service' : 'Pickup') . '</p>';
    }
    if ($pickup_location) {
        echo '<p><strong>' . __('Pickup Location') . ':</strong> ' . $pickup_location . '</p>';
    }
}

// Add custom fields to customer and admin emails
add_action('woocommerce_email_order_meta', 'add_delivery_or_pickup_to_emails', 10, 3);

function add_delivery_or_pickup_to_emails($order, $sent_to_admin, $plain_text) {
    $delivery_or_pickup = $order->get_meta('_delivery_or_pickup');
    $pickup_location = $order->get_meta('_pickup_location');

    if ($delivery_or_pickup || $pickup_location) {
        echo '<h2>' . __('Order Details') . '</h2>';
        if ($delivery_or_pickup) {
            echo '<p><strong>' . __('Delivery or Pickup') . ':</strong> ' . ($delivery_or_pickup === 'delivery' ? 'Delivery Service' : 'Pickup') . '</p>';
        }
        if ($pickup_location) {
            echo '<p><strong>' . __('Pickup Location') . ':</strong> ' . $pickup_location . '</p>';
        }
    }
}

Solution

  • They are some mistakes and missing things. I have completely revised your code.

    I made some changes and:

    • I have included, for those custom fields, the validation process.
    • The jQuery code is now enqueued in the right way to the footer.
    • Those custom fields values are now also saved as user metadata, which is useful to get the chosen options from the previous order.
    • The "Delivery Fee" is only displayed in checkout (to avoid issues).

    In admin order pages, display your custom shipping details below the shipping address.

    text 1

    For customer orders and email notifications, I display those custom shipping details in order details total rows table.

    text 2

    Try the following code:

    // 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 custom fields to the checkout page
    add_action( 'woocommerce_before_order_notes', 'add_delivery_or_pickup_fields', 10 );
    function add_delivery_or_pickup_fields( $checkout ) {
        // 1st dropdown for Delivery or Pickup
        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') );
    
        // 2nd dropdown for pickup location (initially hidden, shown via JavaScript)
        woocommerce_form_field('pickup_location', array(
            'type' => 'select',
            'class' => array('form-row-wide'), // <= Removed additional class
            'label' => esc_html__( 'Select pickup location:', 'woocommerce' ),
            'required' => true, // <== Needed as it's a required field when visible
            'options' => get_pickup_location_options(),
        ), $checkout->get_value('pickup_location'));
    
        // Define the WC Session variable from the previous order (custom user metadata)
        if ( $shipping_option = $checkout->get_value('delivery_or_pickup') ) {
            WC()->session->set('delivery_or_pickup', $shipping_option );
        } 
        // Otherwise, remove the WC Session Variable, if it exists, on checkout page load
        elseif (  WC()->session->__isset('delivery_or_pickup') ) {
            WC()->session->__unset('delivery_or_pickup');
        }
    }
    
    // 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 jQuery code
        wc_enqueue_js("const pickupField = $('#pickup_location_field');
        // On start
        if ( $('#delivery_or_pickup').val() !== 'pickup' ) {
            pickupField.hide(); // Hide pickup field if hasn't been set on a previous order
        }
        // On change
        $(document.body).on('change', '#delivery_or_pickup', function() { 
            if ( typeof wc_checkout_params === 'undefined' ) {
                return false;
            }
            const deliveryOrPickup = $(this).val();
            deliveryOrPickup === 'pickup' ? pickupField.show() :  pickupField.hide();
    
            $.ajax({
                type: 'POST',
                url: wc_checkout_params.ajax_url,
                data: {
                    action: 'save_delivery_or_pickup_to_session',
                    delivery_or_pickup: deliveryOrPickup,
                },
                success: function() {
                    $(document.body).trigger('update_checkout');
                }
            });
        });");
    }
    
    // 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 );
        }
    }
    
    // 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');
        }
    }
    
    // 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],
                    );
                }
    
            }
        }
        return $new_total_rows;
    }
    

    Code goes in functions.php file of your child theme (or in a plugin).