WooCommerce Tax Not Calculating in Checkout Block Fix

WooCommerce tax is not calculating in the Checkout Block after WooCommerce 10.7? Fix the address sync, tax class mapping, and Store API cache properly.
WooCommerceWordPressCheckout Block
May 7, 20265 min read996 words

The Problem

I got a panicked Slack from a B2C client on Wednesday: their WooCommerce store had updated to 10.7 overnight, and the Checkout Block was showing 0.00 for tax on every order, regardless of country. The classic shortcode checkout still calculated correctly, the cart totals on the product page were fine, but the moment a customer hit /checkout, tax disappeared. Stripe captured the wrong amounts for two hours before they paused payments.

If you opened the Checkout Block this week and saw the Tax line missing or stuck at zero while the same products tax correctly elsewhere, you have hit the address-sync bug introduced by the Store API changes in WooCommerce Blocks 11.x (which ships with WooCommerce 10.7). It is not your tax rates. It is the block not telling the server which address to tax against until the customer manually edits a field.

Why It Happens

The Checkout Block calculates tax through the Store API's /wc/store/v1/checkout endpoint, and that endpoint relies on WC()->customer having a billing or shipping address set. In WooCommerce Blocks 11.x the team moved address hydration from a synchronous PHP step to an asynchronous client dispatch. The block now waits for the wc/store/cart action to fire before pushing the address to the server.

There are three places that breaks on real client sites:

  1. Empty initial address. If the customer has no saved address, WC_Customer::get_taxable_address() returns the store base, but only when the request is treated as taxable. The block sends an early update_customer request with an empty country, the tax rules match nothing, and the Store API caches the result for the rest of the page lifecycle.
  2. Tax class mismatch on virtual products. WooCommerce 10.7 changed the default tax class lookup for virtual and downloadable products to fall back to the parent variation. If your variations were imported from CSV with an empty tax_class field, the block reads it as the literal string "parent" instead of inheriting, and WC_Tax::find_rates() returns an empty array.
  3. Persisted Store API response. WooCommerce Blocks added a 60-second nonce-keyed cache to the cart response. If the first page load has zero tax, every subsequent load for that session also has zero tax until the nonce expires or the customer changes a quantity.

The shortcode checkout does not hit these paths because it ran a synchronous wc_setup_loaded_cart() call that the block deliberately removed.

The Fix

Step 1: Force the customer address before the block hydrates. Add this to a small mu-plugin or your theme's functions.php. It runs on the Store API request itself, so it does not affect the classic checkout:

add_action( 'woocommerce_store_api_checkout_update_customer_from_request', function ( $customer, $request ) {
    if ( ! $customer instanceof WC_Customer ) {
        return;
    }

    if ( ! $customer->get_billing_country() ) {
        $base = wc_get_base_location();
        $customer->set_billing_country( $base['country'] );
        $customer->set_billing_state( $base['state'] );
    }

    if ( ! $customer->get_shipping_country() ) {
        $customer->set_shipping_country( $customer->get_billing_country() );
        $customer->set_shipping_state( $customer->get_billing_state() );
    }

    $customer->save();
}, 10, 2 );

This guarantees get_taxable_address() returns a real country on the very first request, so WC_Tax::find_rates() actually finds something.

Step 2: Repair the tax class on virtual variations. Run this once via WP-CLI to clear the literal "parent" string and let WooCommerce inherit properly:

wp eval '
  global $wpdb;
  $rows = $wpdb->query(
      "UPDATE {$wpdb->prefix}wc_product_meta_lookup l
       JOIN {$wpdb->prefix}posts p ON p.ID = l.product_id
       SET l.tax_class = \"\"
       WHERE p.post_type = \"product_variation\" AND l.tax_class = \"parent\""
  );
  wc_delete_product_transients();
  echo "Cleaned $rows variations\n";
'

After running it, hit WooCommerce → Status → Tools → Regenerate the product lookup table so the meta lookup matches the post meta. Skip the regenerate step and the block keeps reading the cached lookup row.

Step 3: Bust the Store API cart cache when a customer logs in or arrives. The block's cache is keyed by the cart hash, so we just bump the hash on first hit:

add_filter( 'woocommerce_store_api_cart_item_data', function ( $data, $cart_item ) {
    if ( ! WC()->session ) {
        return $data;
    }

    if ( ! WC()->session->get( 'qc_cart_warmed' ) ) {
        WC()->cart->calculate_totals();
        WC()->session->set( 'qc_cart_warmed', 1 );
    }

    return $data;
}, 10, 2 );

Forcing calculate_totals() once per session warms the tax calculation against the real address from Step 1 and writes a cache entry the block can actually use.

Step 4: Verify with a real Store API call. Do not trust the UI here, the block re-renders the previous response on hydration. Curl the endpoint and look at the totals.total_tax field directly:

curl -s 'https://yourstore.com/wp-json/wc/store/v1/cart' \
  -H 'Cookie: woocommerce_cart_hash=...; wp_woocommerce_session_...=...' \
  | jq '.totals.total_tax, .items[].totals.total_tax'

If total_tax is non-zero on the API and zero in the block, you still have a stale client cache, hard refresh and try again. If it is zero on the API, your fix did not land. The Store API reference covers the endpoint shape if you want to dig further.

The Lesson

Missing tax in the Checkout Block almost never means broken tax rates. It means the Store API ran its first calculation before the customer had an address, cached the empty result, and served it for the rest of the session. Force the address early, fix any imported variations with literal "parent" tax classes, warm the cart once per session, and verify with a direct API call rather than trusting the UI.

If your store is silently undercharging tax because of a Blocks upgrade, this is the kind of WooCommerce stabilisation work I do — see my services. For another silent-failure pattern in the same checkout flow, look at WooCommerce checkout shipping rates not showing.

Back to blogStart a project