I have written custom zip code validation that calls Google Maps API to set the user’s city and state as well as zip code in the user meta when only the zip code input is enabled on the cart page’s shipping calculator. The code works.
However, I am struggling with what hook to add the action. I have used several hooks, including my own custom hook, and I get the following behaviors:
This is what the screen looks like before trying to change the zip code:
This is what the screen looks like entering the new zip code:
If the action is added to the woocommerce_before_shipping_calculator
hook (which is triggered in shipping-calculator.php
):
After clicking update, the post data in the request with the user-entered zip code is correct, the Google data is retrieved and the meta is updated. BUT the cart’s shipping calculator shows as if no zip code is in the user meta. Here is the image:
BUT we know the meta data is there, because when I force a refresh of the page in the browser right after, the correct zip code from the user meta set in my functions.php
function attached to the hook shows up:
This makes some sense to me because the formatted destination is written in cart-shipping.php
before the calculator is loaded. So I believe I need to run my custom validation code BEFORE shipping-calculator.php
is loaded.
If the action is added to the woocommerce_before_cart
hook (which is triggered in cart.php
) or the woocommerce_cart_totals_before_shipping
hook (which is triggered in cart-totals.php
):
I was sure woocommerce_cart_totals_before_shipping
was the right hook since it is called right before wc_cart_totals_shipping_html()
in cart-totals.php
, and that function is where the packages are loaded.
BUT after clicking update in both of these cases, the post data from the request is empty (surprise!), so my function can’t call the Google API (since the entered zip code isn’t passed), and therefore, the user meta does not get changed. More confusingly, the resulting screen shows as if there is no zip code entered, but a refresh of the screen reverts to the original zip code:
If I add a custom action to hook onto at the top of cart-shipping.php
Theoretically, my function will be called and should set the user meta right before the formatted destination is loaded for display on the screen, which I believe is what determines if the screen shows a shipping destination or is awaiting a zip code. (See cart-shipping.php
code included at the end of this post so you can see where I put the custom action and the logic of the formatted destination for yourself. Note this is my overridden version of the template because I have other logic in there to show a notice to users who may be from out of town but are visiting and could pickup in store.)
In this case, the post data is also empty, with the same behavior as the woocommerce_before_cart
hook.
My bottom line confusion
I can’t seem to find where I should include my custom action and/or hook into an existing action so that I can get the correct post data from the shipping update but before the shipping information is loaded. I am contemplating just forcing a refresh of the screen at the end of my function (if I can do that?) but this seems like a terrible way to solve this problem if it is even possible and doesn’t cause an infinite loop.
I am pretty new to woocommerce and wordpress, so please forgive me for my feeble attempt to explain the problem or if I just missed something obvious. Thanks to anyone who has followed this question to this point and can help suggest how to get my code to run in just the right place so the input can be captured and the user meta can be updated before the shipping section on the cart screen is loaded. Thanks!
cart-shipping.php
defined( 'ABSPATH' ) || exit;
do_action('my_custom_action');
$formatted_destination = isset( $formatted_destination ) ? $formatted_destination : WC()->countries->get_formatted_address( $package['destination'], ', ' );
$has_calculated_shipping = ! empty( $has_calculated_shipping );
$show_shipping_calculator = ! empty( $show_shipping_calculator );
$calculator_text = '';
?>
<tr class="woocommerce-shipping-totals shipping">
<th><?php echo wp_kses_post( $package_name ); ?></th>
<td data-title="<?php echo esc_attr( $package_name ); ?>">
<?php if ( ! empty( $available_methods ) && is_array( $available_methods ) ) : ?>
<ul id="shipping_method" class="woocommerce-shipping-methods">
<?php $visitor = true; ?>
<?php foreach ( $available_methods as $method ) : ?>
<li>
<?php
if (str_contains($method->id, 'local_pickup')) :
$visitor = false;
endif;
if ( 1 < count( $available_methods ) ) {
printf( '<input type="radio" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" %4$s />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ), checked( $method->id, $chosen_method, false ) ); // WPCS: XSS ok.
} else {
printf( '<input type="hidden" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ) ); // WPCS: XSS ok.
}
printf( '<label for="shipping_method_%1$s_%2$s">%3$s</label>', $index, esc_attr( sanitize_title( $method->id ) ), wc_cart_totals_shipping_method_label( $method ) ); // WPCS: XSS ok.
do_action( 'woocommerce_after_shipping_rate', $method, $index );
?>
</li>
<?php endforeach; ?>
<?php if ($visitor) : ?>
<li>
<?php do_action('fabrix_visitor_bopis'); ?>
</li>
<?php endif; ?>
</ul>
<?php if ( is_cart() ) : ?>
<p class="woocommerce-shipping-destination">
<?php
if ( $formatted_destination ) {
// Translators: $s shipping destination.
printf( esc_html__( 'Shipping to %s.', 'woocommerce' ) . ' ', '<strong>' . esc_html( $formatted_destination ) . '</strong>' );
$calculator_text = esc_html__( 'Change destination', 'woocommerce' );
} else {
echo wp_kses_post( apply_filters( 'woocommerce_shipping_estimate_html', __( 'Shipping options will be updated during checkout.', 'woocommerce' ) ) );
}
?>
</p>
<?php endif; ?>
<?php
elseif ( ! $has_calculated_shipping || ! $formatted_destination ) :
if ( is_cart() && 'no' === get_option( 'woocommerce_enable_shipping_calc' ) ) {
echo wp_kses_post( apply_filters( 'woocommerce_shipping_not_enabled_on_cart_html', __( 'Shipping costs are calculated during checkout.', 'woocommerce' ) ) );
} else {
echo wp_kses_post( apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'woocommerce' ) ) );
}
elseif ( ! is_cart() ) :
echo wp_kses_post( apply_filters( 'woocommerce_no_shipping_available_html', __( 'There are no shipping options available. Please ensure that your address has been entered correctly, or contact us if you need any help.', 'woocommerce' ) ) );
else :
echo wp_kses_post(
/**
* Provides a means of overriding the default 'no shipping available' HTML string.
*
* @since 3.0.0
*
* @param string $html HTML message.
* @param string $formatted_destination The formatted shipping destination.
*/
apply_filters(
'woocommerce_cart_no_shipping_available_html',
// Translators: $s shipping destination.
sprintf( esc_html__( 'No shipping options were found for %s.', 'woocommerce' ) . ' ', '<strong>' . esc_html( $formatted_destination ) . '</strong>' ),
$formatted_destination
)
);
$calculator_text = esc_html__( 'Enter a different destination', 'woocommerce' );
endif;
?>
<?php if ( $show_package_details ) : ?>
<?php echo '<p class="woocommerce-shipping-contents"><small>' . esc_html( $package_details ) . '</small></p>'; ?>
<?php endif; ?>
<?php if ( $show_shipping_calculator ) : ?>
<?php woocommerce_shipping_calculator( $calculator_text ); ?>
<?php endif; ?>
</td>
</tr>