Search code examples
phpwordpresswoocommercecustom-fieldsorders

WooCommerce : Add custom Metabox to admin order page


I am currently successfully adding a field to my WooCommerce product pages which is showing the value:

  • in the cart (front end),
  • on checkout page (front end),
  • on order page (front end),
  • and in admin individual order page (back end).

The problem: It isn't showing as a custom field in the admin order "custom fields" Metabox with the value inside it, but just as a text in the order page.

Here is my working code:

// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_checkout_field');

function my_custom_checkout_field() {
    echo '<div id="my_custom_checkout_field"><h3>'.__('My Field').'</h3>';
    echo  '<label>fill in this field</label> <input type="text" name="my_field_name">';
    echo '</div>';
}

// Store custom field
function save_my_custom_checkout_field( $cart_item_data, $product_id ) {
    if( isset( $_REQUEST['my_field_name'] ) ) {
        $cart_item_data[ 'my_field_name' ] = $_REQUEST['my_field_name'];
        /* below statement make sure every add to cart action as unique line item */
        $cart_item_data['unique_key'] = md5( microtime().rand() );
    }
    return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_my_custom_checkout_field', 10, 2 );

// Render meta on cart and checkout
function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
    $custom_items = array();
    /* Woo 2.4.2 updates */
    if( !empty( $cart_data ) ) {
        $custom_items = $cart_data;
    }
    if( isset( $cart_item['my_field_name'] ) ) {
        $custom_items[] = array( "name" => 'My Field', "value" => $cart_item['my_field_name'] );
    }
    return $custom_items;
}
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );

// This is what I think needs changing?

function subscription_order_meta_handler( $item_id, $values, $cart_item_key ) {
    if( isset( $values['my_field_name'] ) ) {
        wc_add_order_item_meta( $item_id, "My Field", $values['my_field_name'] );
    }
}
add_action( 'woocommerce_add_order_item_meta', 'subscription_order_meta_handler', 1, 3 );

I think it is this last bit of the code that needs changing. It currently shows the text under the order item, so perhaps I need to adjust wc_add_order_item_meta to something else?

I've tried everything but it doesn't seem to work. I can get it to work when my field is on the checkout page but not when I pull it from the product page.

Perhaps I am missing a checkout process snippet?


Solution

  • UPDATE 2023 (Still works in Woocommerce 3+)

    Firstly, I have gotten everything working as expected, except getting the value for my_field_slug in the back end "Custom fields" Metabox within Order pages.

    Then after a real nightmare, I have found a pretty nice working solution, better than before. In the back end you have now a Custom Metabox with the custom field my_field_slug displaying the right value, like in this screenshot:

    enter image description here


    My code is divided into 2 parts.

    1. The backend Metabox in Order pages, with an editable field showing the correct value coming from a custom field on the product pages (in the front end):
    // Adding Meta container admin shop_order pages
    add_action( 'add_meta_boxes', 'add_shop_order_meta_boxes' );
    if ( ! function_exists( 'add_shop_order_meta_boxes' ) )
    {
        function add_shop_order_meta_boxes()
        {
            add_meta_box( 'custom_other_field', __('My Field','woocommerce'), 'add_custom_other_field_content', 'shop_order', 'side', 'core' );
        }
    }
    
    // Adding Meta field in the meta container on admin shop_order pages
    if ( ! function_exists( 'add_custom_other_field_content' ) )
    {
        function add_custom_other_field_content( $post )
        {
            $order = wc_get_order($post->ID); // Get the WC_Order object
    
            printf('<p style="border-bottom:solid 1px #eee;padding-bottom:13px;">
                <input type="hidden" name="custom_other_field_nonce" value="%s">
                <input type="text" style="width:250px;" name="my_field_slug" placeholder="%s" value="%s">
                </p>',
                wp_create_nonce(),
                __('some placeholder text', 'woocommerce'),
                $order->get_meta('_my_field_slug')
            );
    
        }
    }
    // Save the Meta field value
    add_action( 'woocommerce_process_shop_order_meta', 'save_custom_metabox_field_value', 10, 2 );
    if ( ! function_exists( 'save_custom_metabox_field_value' ) )
    {
        function save_custom_metabox_field_value( $order_id, $post) {
            // Check if our nonce is set.
            if ( ! isset($_REQUEST['custom_other_field_nonce']) && ! wp_verify_nonce($_REQUEST['custom_other_field_nonce']) ) {
                return $order_id;
            }
    
            $order = wc_get_order($order_id); // Get the WC_Order object
    
            // Sanitize text input value and update the meta field in the database.
            $order->update_meta_data('_my_field_slug', sanitize_text_field($_POST['my_field_slug']));
            $order->save();
        }
    }
    

    1. Frontend / Backend:

    Note: We allow only one item in cart, as the product field value populates the order Metabox field.

    • The product page custom field (front end).
    • Displaying this data on cart, checkout and orders (front end). On email notifications too.
    • Displaying data on the order page (back end)

    // Display a text field on single product pages
    add_action('woocommerce_before_add_to_cart_button', 'display_custom_field_on_single_product');
    function display_custom_field_on_single_product() {
        echo '<div id="my_custom_field">
            <label>' . __( 'My Field') . ' </label>
            <input type="text" name="my_field_slug" value="">
        </div><br>';
    }
    
    // Allow only one item in cart
    add_filter( 'woocommerce_add_to_cart_validation', 'allow_only_one_cart_item', 10, 3 );
    function allow_only_one_cart_item( $passed, $product_id, $quantity ) {
        if( ! WC()->cart->is_empty() ) {
            WC()->cart->empty_cart();
        }
        return $passed;
    }
    
    // Store custom field value
    add_filter( 'woocommerce_add_cart_item_data', 'add_custom_field_as_custom_cart_item_data', 10, 2 );
    function add_custom_field_as_custom_cart_item_data( $cart_item_data, $product_id ) {
        if( isset( $_REQUEST['my_field_slug'] ) ) {
            $cart_item_data[ 'my_field_slug' ] = sanitize_text_field($_REQUEST['my_field_slug']);
            // below statement make sure every add to cart action as unique line item
            $cart_item_data['unique_key'] = md5( microtime().rand() );
        }
        return $cart_item_data;
    }
    
    // Render the field value on cart and checkout
    add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
    function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
        $custom_items = array();
    
        if( !empty( $cart_data ) ) {
            $custom_items = $cart_data;
        }
    
        if( isset( $cart_item['my_field_slug'] ) ) {
            $custom_items[] = array( 
                'name'  => __('My Field'), 
                'value' => $cart_item['my_field_slug'] 
            );
        }
        return $custom_items;
    }
    
    // Save the the custom field value as order custom meta
    add_action( 'woocommerce_checkout_create_order', 'save_custom_field_as_order_meta', 10, 2 );
    function save_custom_field_as_order_meta( $order, $data ) {
        $items = WC()->cart->get_cart();
        $item  = reset($items);
    
        if ( isset($item['my_field_slug']) ) {
            $order->update_meta_data( '_my_field_slug', $item['my_field_slug'] );
        }
    }
    
    // Save the the custom field value as order item custom meta
    add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_field_as_order_item_meta', 10, 4 );
    function save_custom_field_as_order_item_meta( $item, $cart_item_key, $values, $order ) {
        if ( isset($values['my_field_slug']) ) {
            $item->update_meta_data( 'my_field_slug', $values['my_field_slug'] );
        }
    }
    
     // Add readable "meta key" label name replacement
     add_filter('woocommerce_order_item_display_meta_key', 'filter_wc_order_item_display_meta_key', 10, 3 );
     function filter_wc_order_item_display_meta_key( $display_key, $meta, $item ) {
         if( $display_key === 'my_field_slug' ) {
             $display_key = __('My field', 'woocommerce');
         }
         return $display_key;
     }
    

    Everything is working as expected now.