I wrote a WooCommerce plugin to add SF Express service points to the Checkout Block page. The dropdown appears correctly, but the selected option is not passed to PHP, so the order notes do not reflect the chosen service point. Below are the relevant snippets of my PHP and JS code.I’ve tried logging the POST data and it seems the selected option is not being passed to the PHP function correctly. Any insights or suggestions on what might be wrong?
PHP Code:
<?php
if (!defined('ABSPATH')) {
exit;
}
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
function sf_express_shipping_method_init() {
if (!class_exists('WC_Shipping_SF_Express')) {
class WC_Shipping_SF_Express extends WC_Shipping_Method {
public function __construct() {
$this->id = 'sf_express';
$this->method_title = __('SF Express Service Points', 'sf_express');
$this->method_description = __('Allows customers to pick up parcels from SF Express Service Points.', 'sf_express');
$this->init();
}
function init() {
$this->init_form_fields();
$this->init_settings();
$this->enabled = $this->get_option('enabled');
$this->title = $this->get_option('title');
add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
}
function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __('Enable/Disable', 'sf_express'),
'type' => 'checkbox',
'label' => __('Enable SF Express Service Points', 'sf_express'),
'default' => 'yes'
),
'title' => array(
'title' => __('Title', 'sf_express'),
'type' => 'text',
'description' => __('This controls the title which the user sees during checkout.', 'sf_express'),
'default' => __('SF Express Service Points', 'sf_express'),
'desc_tip' => true,
),
'service_points_csv' => array(
'title' => __('Service Points CSV', 'sf_express'),
'type' => 'textarea',
'description' => __('Paste the contents of your service points CSV file here.', 'sf_express'),
'default' => '',
),
);
}
public function calculate_shipping($package = array()) {
$rate = array(
'id' => $this->id,
'label' => $this->title,
'cost' => '0',
'calc_tax' => 'per_order'
);
$this->add_rate($rate);
}
}
}
}
add_action('woocommerce_shipping_init', 'sf_express_shipping_method_init');
add_filter('woocommerce_shipping_methods', function($methods) {
$methods['sf_express'] = 'WC_Shipping_SF_Express';
return $methods;
});
add_action('wp_enqueue_scripts', function() {
wp_enqueue_script('sf_express', plugins_url('/sf-express.js', __FILE__), array('jquery'), '1.0', true);
wp_localize_script('sf_express', 'sf_express_params', array(
'ajax_url' => admin_url('admin-ajax.php')
));
});
// AJAX Handler for fetching service points
add_action('wp_ajax_fetch_sf_express_service_points', 'fetch_sf_express_service_points');
add_action('wp_ajax_nopriv_fetch_sf_express_service_points', 'fetch_sf_express_service_points');
function fetch_sf_express_service_points() {
$options = get_option('woocommerce_sf_express_settings');
$csv_data = $options['service_points_csv'];
$lines = explode("n", $csv_data);
$html = '';
foreach ($lines as $line) {
$parts = explode(',', trim($line));
if (count($parts) >= 2) {
$html .= '<option value="' . esc_attr(trim($parts[0])) . '">' . esc_html(trim($parts[1])) . '</option>';
}
}
echo $html;
wp_die();
}
add_action('woocommerce_checkout_create_order', function($order, $data) {
// Log the posted service point value
error_log('Selected SF Express Service Point: ' . print_r($_POST['sf_express_service_point'], true));
if (!empty($_POST['sf_express_service_point'])) {
$service_point = sanitize_text_field($_POST['sf_express_service_point']);
$order->update_meta_data('sf_express_service_point', $service_point);
$note = 'SF Express Service Point: ' . $service_point;
$order->add_order_note($note);
}
}, 10, 2);
function debugging( $order_id ) {
$order = wc_get_order( $order_id );
// Log the POST data for debugging
error_log('POST Data: ' . print_r($_POST, true));
if (isset($_POST['sf_express_service_point']) && !empty($_POST['sf_express_service_point'])) {
$service_point = sanitize_text_field($_POST['sf_express_service_point']);
error_log('Service Point: ' . $service_point); // Debug logging
$order->update_meta_data('sf_express_service_point', $service_point);
$note = 'Testing: ' . $service_point;
$order->add_order_note($note);
$order->save();
} else {
$note = 'Testing: Service point not set';
$order->add_order_note($note);
$order->save();
}
}
add_action('woocommerce_thankyou', 'debugging', 10, 1);
add_action('woocommerce_checkout_process', function() {
if (empty($_POST['sf_express_service_point']) && isset($_POST['shipping_method'][0]) && $_POST['shipping_method'][0] === 'sf_express') {
wc_add_notice(__('Please select an SF Express Service Point.', 'sf_express'), 'error');
}
});
add_action('woocommerce_admin_order_data_after_billing_address', function($order) {
$service_point = $order->get_meta('sf_express_service_point');
if ($service_point) {
echo '<p><strong>' . __('SF Express Service Point', 'sf_express') . ':</strong> ' . esc_html($service_point) . '</p>';
}
});
add_filter('woocommerce_email_order_meta_fields', function($fields, $sent_to_admin, $order) {
$service_point = $order->get_meta('sf_express_service_point');
if ($service_point) {
$fields['sf_express_service_point'] = array(
'label' => __('SF Express Service Point', 'sf_express'),
'value' => esc_html($service_point),
);
}
return $fields;
}, 10, 3);
add_action('woocommerce_order_details_after_order_table', function($order) {
$service_point = $order->get_meta('sf_express_service_point');
if ($service_point) {
echo '<p><strong>' . __('SF Express Service Point', 'sf_express') . ':</strong> ' . esc_html($service_point) . '</p>';
}
});
}
JS Code:
jQuery(document).ready(function($) {
console.log('SF Express script loaded');
function addServicePointField() {
console.log('Checking if service point field needs to be added');
if ($('#sf_express_service_point').length === 0) {
console.log('Adding service point field');
var servicePointFieldHtml = '<div class="form-row form-row-wide">' +
'<label for="sf_express_service_point">' +
'SF Express Service Point' +
'</label>' +
'<select id="sf_express_service_point" name="sf_express_service_point" class="select">' +
'<option value="">Select a service point</option>' +
'</select>' +
'</div>';
// Append to the shipping method container
$('.wc-block-components-shipping-rates-control__package').append(servicePointFieldHtml);
updateServicePointField(); // Fetch and update the dropdown
}
}
function removeServicePointField() {
console.log('Removing service point field if it exists');
$('#sf_express_service_point').parent().remove();
}
function updateServicePointField() {
console.log('Fetching updated service points');
$.ajax({
url: sf_express_params.ajax_url,
type: 'POST',
data: {
action: 'fetch_sf_express_service_points'
},
success: function(response) {
console.log('Service points fetched successfully');
$('#sf_express_service_point').html(response);
}
});
}
// Initial call with a delay to ensure all dynamic elements are rendered
setTimeout(addServicePointField, 1000);
// Re-add the service point field when the shipping method changes
$(document).on('change', 'input[name="shipping_method[0]"]', function() {
var shippingMethod = $(this).val();
if (shippingMethod === 'sf_express') {
addServicePointField();
} else {
removeServicePointField();
}
});
// Ensure the field is included in the form submission
$('form.checkout').on('checkout_place_order', function() {
var servicePoint = $('#sf_express_service_point').val();
console.log('Selected service point: ' + servicePoint); // Debugging
if (servicePoint) {
$('<input>').attr({
type: 'hidden',
name: 'sf_express_service_point',
value: servicePoint
}).appendTo('form.checkout');
} else {
console.log('No service point selected');
}
});
// Trigger the addServicePointField function when the page loads and shipping method is already selected
if ($('input[name="shipping_method[0]"]:checked').val() === 'sf_express') {
addServicePointField();
}
});
The service point selector is displayed, but the selected option is not being saved with the order. I’ve tried logging the POST data and it seems the selected option is not being passed to the PHP function correctly. Any insights or suggestions on what might be wrong?
2
Answers
First, as you are using WooCommerce Checkout Block, note that it allows very few customizations.
You will not be able to get the selected "Service Point" to save it as order metadata, and either you will not be able to validate the field if no value has been selected.
Also, there are multiple mistakes in your code.
Solution: Using
WC_Session
variables.What you can do, instead, is to save the selected "Service point" in a session variable.
As you can’t validate this custom field, the only way is to remove the empty option, this way you always have a value.
Also, instead of displaying the options via Ajax, you can display the complete select field with all options from start as hidden. Then, we show or hide the field, depending on the selected shipping method.
If customer chose "SF Express" as shipping method, we save the selected "Service Point" via Ajax in a WC_Session variable.
Then when submitting the data, if "SF Express" is the chosen shipping method, we can get the selected "Service Point" from the WC_Session variable.
Note that the following hooks doesn’t seems to work with Checkout blocks:
woocommerce_checkout_create_order
,woocommerce_checkout_update_order_meta
,woocommerce_checkout_order_created
.But
woocommerce_checkout_create_order_shipping_item
hook works, so that’s why we use it, to save the selected "Service point".Your
class WC_Shipping_SF_Express
method code stays unchanged. We change everything else.Note that the following code is only to be used with WooCommerce Checkout Block:
Code goes in functions.php file of your child theme (or in a plugin). Tested and works.
On customer orders:
On email notifications:
On admin Order:
I’ve followed your steps but when user is not logged in I’m not able to get WC()->session value. Do you have any idea how can I get it for non-logged in users?