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>';
}
}
}
They are some mistakes and missing things. I have completely revised your code.
I made some changes and:
In admin order pages, display your custom shipping details below the shipping address.
For customer orders and email notifications, I display those custom shipping details in order details total rows table.
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).