Hiding Shipping and Payment Options in the Cart and Checkout Blocks

Update

On 15th March, 2024, we updated this guide to show that you can filter payment methods using PHP only. We left the guide as is to still show how you can do it from JavaScript as well.

A while ago we made our new, block-based checkout available for testing in the WooCommerce Blocks plugin. The new Cart and Checkout blocks were built from the ground up to deliver a conversion-optimized checkout experience that is fully customizable in the WordPress Block Editor.  

The new blocks are powered by the Store API, which follows many of the patterns used in the WordPress REST API, with one key difference: It allows unauthenticated access (cookie-based) to product and cart data, which makes it possible to build custom front-ends that work outside the WordPress hosting environment.

One of the most interesting aspects of developing the Cart and Checkout blocks has been their extensibility. Originally, WooCommerce — just like WordPress — was designed to be extended using PHP action and filter hooks. Despite their simplicity, PHP hooks make it difficult to provide guidance around what should be possible or expected — and in which context. 

With the new Cart and Checkout blocks, we provide a more opinionated path for extension developers who wish to extend the WooCommerce checkout. Our aim was to provide enough flexibility to developers who wish to extend the functionality of the new checkout, while preserving the integrity of the checkout process.

In the last few months, we have been validating the extensibility of the new block-based checkout by making our own extensions compatible with it. Many extensions are now fully integrated with the Cart and Checkout blocks, including WooCommerce Subscriptions, Product Add-Ons, and more recently Product Bundles and Gift Cards. Along the way, our team has added new extensibility points that you can utilize to integrate your plugins.

After taking a deep dive into the available extensibility interfaces, we recently introduced you to the Store API — the driving force behind the Cart and Checkout blocks. Now, it’s time to put that knowledge to practice with an integration example.

In this guide, we’ll show you how to conditionally hide shipping and payment methods in the new block-based checkout. 

Getting Started

If you haven’t done so already, we highly recommend adding this guide to your reading list, as it provides a great high-level overview of modern WooCommerce development.

The new Cart and Checkout blocks are essentially React components that rely on the Store API to provide cart and checkout functionality. To extend them, you can:

Store API routes are responsible for mapping requests to endpoints. The wc/store/cart/ route, for example, is mapping GET requests to an endpoint that returns a representation of the customer’s cart. Each route/endpoint requires a particular structure of input data and returns information for a specific resource using a defined and predictable structure. On the WooCommerce Store API documentation page you can find some great information about the available routes/endpoints, HTTP methods, and resources.

Note that some endpoints are designed around specific operations for convenience. For example, the wc/store/cart/add-item endpoint accepts a quantity and product ID, but returns a full cart object, rather than just an updated list of items.

The Store API is written entirely in PHP, and reuses a lot of the existing cart/checkout logic (and filter hooks). As a result, many plugins that were originally built for the shortcode-based checkout will continue to work with the Store API — and with the Cart and Checkout blocks that consume it.

On the other hand, the Cart and Checkout blocks are written entirely in JavaScript, and have moved away from the PHP templating engine that powered the shortcode-based checkout. If your plugin is making heavy use of PHP templates, or utilizes hooks included in core templates, you will probably need to write some React to make it work with the block-based checkout.

This guide will not teach you how to develop blocks with Gutenberg — however, it will give you a head start at customizing and extending the Cart and Checkout blocks.

Case #1: Hiding Shipping Methods

In this example, our goal is to hide shipping options conditionally. Here’s one way to tackle this:

add_filter( 'woocommerce_package_rates', function( $rates, $package ) {
	foreach ( $rates as $rate_id => $rate ) {
		if ( ! my_rate_is_visible( $rate->get_method_id() ) ) {
			unset( $rates[ $rate_id ] );
		}
	}
	return $rates;
}, 10, 2 );

This approach has always worked with the cart checkout shortcodes — and still works with the new, block-based checkout.

Every time you make a change in the Cart or Checkout block to modify the cart state, a request is made via the wc/store/cart/ route. Assuming that the request is valid:

  1. The Store API cart controller updates your cart and recalculates shipping rates in the back end.
  2. A response is sent with the updated cart contents and shipping rates.
  3. The Cart or Checkout block receives the response and updates the displayed shipping options.
BeforeAfter

In the next section, we will take a look at another case that requires a bit more effort to work with the block-based checkout.

Case #2: Hiding Payment Gateways

This time, our goal is to hide payment options conditionally. Here’s an approach that works well with the shortcode-based checkout:

add_filter( 'woocommerce_available_payment_gateways', function( $gateways ) {
	foreach ( $gateways as $gateway_id => $gateway ) {
		if ( ! my_gateway_is_visible( $gateway_id ) ) {
			unset( $gateways[ $gateway_id ] );
		}
	}
	return $gateways;
} );

This approach also worked with the cart checkout shortcodes — for a certain time, it didn’t work with the block-based checkout, this is not the case anymore, it now work with the new, block-based checkout. That’s it, you’re done now.

However, you can also hide payment gateways directly from JavaScript in response to live user actions, the following section will show how to pass information from server to client in each response, as well as hide payments methods from JS.

  1. Extend the Store API to update the payment gateways that we want to hide and make them available to the front end every time the cart state changes.
  2. Write some JavaScript code that consumes this information and hides payment options in the Checkout block.

To give you a more complete picture of the steps involved in this we created a plugin for this guide that comes with all the boilerplate you will need to do both. The plugin also contains the PHP snippet we used to hide shipping options earlier.

Step 1: Extend the Store API

For this example, we will:

Time to get started!

This document from the Store API documentation explains how we can piggyback on the wc/store/cart endpoint. Armed with this knowledge, we have written a class that exposes the visibility status of our payment gateways in cart endpoint responses.

Notes:

To confirm that everything works:

  1. Make sure that the WooCommerce Blocks plugin is installed and activated on your site.
  2. Clone this GitHub repository inside the /wp-content/plugins directory of your WordPress installation.
  3. Log into your WordPress dashboard.
  4. Under the Plugins menu, locate and activate the WooCommerce Checkout Integration Example 1 plugin.

Then:

  1. Add a product to the cart.
  2. Go to the Cart page — note that this should contain the Cart block, not the legacy Cart shortcode!
  3. Open the Chrome Developer Tools, and go to the Network tab.
  4. Change the quantity of the item you just added to the cart. This will trigger a wc/store/v1/cart/update-item route request.
  5. Go to the Preview tab and locate the extensions field.

Examine its contents. If you have activated the Direct Bank Transfer and Cash On Delivery payment gateways, you should see an object like this:

checkout-integration-example: {
	gateway_visibility: [
		{
			gateway: "bacs"
			is_visible: false
		}, 
		{
			gateway: "cod"
			is_visible: true
		}
	]
}

Step 2: Extend the Checkout Block

Now that we have exposed the data we need in cart route responses, it’s time to write some JavaScript code that consumes the exposed data to hide payment options in the front end.

Preparing to Build JavaScript Code

First, we need to prepare our plugin to write and build modern JavaScript code.

If you haven’t heard about node, npm, nvm, or Webpack before, this is a good time for this short introduction. Then, we highly suggest that you take a deep dive into this tutorial, which describes all the steps involved in preparing a plugin to successfully build modern JavaScript.

It’s now time to build the plugin you just cloned and activated:

  1. Make sure that you are using at least node version 14.18 with npm version 6.14.
  2. Run npm install followed by npm run build.

Note: The default WordPress Webpack configuration is set up to look for a file at src/index.js. In this case, we configured Webpack to:

  • Look for our source code in resources/js/frontend.
  • Build it in assets/js/frontend.

Note: If you are already using a task runner to build your plugin’s assets, you will probably want to build upon what you already have, instead of creating a new build process from scratch. Here you can find an example that illustrates how you can use wp-scripts to build modern JavaScript/React code as part of an existing build process based on Grunt.

Note: In some cases, it might be possible to integrate your plugins without writing modern JavaScript or React. When this is an option, you can continue to build your JavaScript code as you always have. 

Now that we have everything we need to build our new JavaScript code, let’s take a closer look at how to enqueue it.

Enqueuing Our Integration Script

To enqueue scripts, styles, and data, you can use the IntegrationRegistry to register an object that implements IntegrationInterface. For more details on how to do it, and which methods to implement in your class, check out this document. Then, take a closer look at the implementation we included in our plugin. To register our instance, we:

  • Assigned a callback to the woocommerce_blocks_checkout_block_registration hook.
  • Called the register method on the IntegrationRegistry object passed into the callback.

Note that we only registered an IntegrationInterface object for the Checkout block. However, if you need to integrate with the Cart or Mini Cart blocks, you may register a different object for each block — or even use the same one with all of them. This document explains which hook to use in each case.

In addition to registering and enqueuing our integration script, we can also use this object to make custom data available to the front end. A closer look at our implementation of get_script_data reveals that we used it to expose the ids of the payment gateways enabled in our store.

Hiding Payment Options in the Checkout Block

So far, we’ve covered all the steps necessary to build and enqueue the custom code we wrote to conditionally hide payment options in the Checkout block. It’s now time to dive deeper into the code that takes care of this! Remember that our goal is to conditionally hide the Direct Bank Transfer gateway when choosing the Free Shipping option.

The Checkout block provides a function called registerPaymentMethodExtensionCallbacks, which allows developers to register callbacks that control the visibility of specific payment methods. Note that each callback receives an argument that provides access to lots of useful information that you can use in your logic, including the cart object.

If you take a closer look at our JavaScript code, you may notice that our callback functions utilize the gateway visibility data that we exposed in cart route responses. This data is updated dynamically every time a change is made in the Cart or Checkout block, which means that you can use the same approach to implement any type of conditional logic that relies on the cart state.

At this point, it should also be clearer to you why we used get_script_data to pass the ids of all payment gateways to the front end: To utilize registerPaymentMethodExtensionCallbacks correctly, one must define a callback function for each payment gateway. By passing a list of payment gateway ids to our script, we made sure that it works in any scenario — and on any site. Want to define another payment option to hide, or change the conditions? Go ahead and edit this method!

BeforeAfter

Note:

If your integration does not require you to write ES6 or React, you could replace the following:

import { getSetting } from '@woocommerce/settings';
import { registerPaymentMethodExtensionCallbacks } from '@woocommerce/blocks-registry';

with:

var registerPaymentMethodExtensionCallbacks = wc.wcBlocksRegistry.registerPaymentMethodExtensionCallbacks;
var getSetting = wc.wcSettings.getSetting;

This approach will simplify the process of building your code quite a bit.

Next Steps

In the future, store owners using WooCommerce for the first time will have the block-based checkout installed as part of our guided store setup experience. If you haven’t done so already, this is a great time to start integrating your plugins with the Cart and Checkout blocks.

Already started, but are having trouble integrating your plugin? Our engineers are available for technical guidance in the WooCommerce Blocks repository. Feel free to open an issue to ask for assistance, or share your findings and feedback.

Need help with another area of extensibility? Please let us know in the comments section below!


Keep yourself in the loop!

Sign up for the WooCommerce developer newsletter:
This field is hidden when viewing the form
This field is hidden when viewing the form
This field is hidden when viewing the form


7 responses to “Hiding Shipping and Payment Options in the Cart and Checkout Blocks”

  1. WTF, we could literally do this with 8 lines of code, anyone could do it, but now look at this mess! What have you done?!

    1. Nadir Seghir Avatar
      Nadir Seghir

      Hey Yoan! Thank you for feedback 😀

      This is no longer the case, both flows can be achieved with PHP only, we will update the article.

      1. Hi Nadir, thanks a lot that would be great!

    2. shanibanerjee Avatar
      shanibanerjee

      Hey Yoan, disagreements about code happen to the best of us – but please keep in mind that this is a community built on helping one another. Feedback is always welcome, but rudeness is not. Please consider this on future comments.

      1. Hi Shanibanerjee, sorry I didn’t meant to be rude, I am not English native and I tried to convey my own surprise, lack of understanding, and helplessness in a funny / friendly way. That’s a miss ^^.

        I truly think that the code is made by experts and couldn’t be more perfectly structured. But, that went to the point that it is now inaccessible to mear humans like me.

        As a developer, coding is harder, debugging is harder, because WC code is becoming too abstract for me. My customers already found it hard and expensive to make custom code for WC, now this article confirmed in my mind that it is the beggining of the end of custom code for most companies ^^’.

        1. Nadir Seghir Avatar
          Nadir Seghir

          Our goal isn’t to make customization harder, but to make it safer and scalable, it’s an unintended reality that extensibility is now harder, and we’re trying to fix that. Feedback is always welcomed, and whatever you run into struggle, do let us know.

          We intend to tackle this in 2 different ways:
          – Provide simpler APIs, or support existing ones when possible.
          – Double down on tutorials and guides.

          The code examples above are the building primitives we’re starting with, extending Store API, hooking into existing integration interfaces, registering callbacks, those are the safe primitives we’re starting with, and they allow a variety of use cases, our next steps would be to build easier abstraction on top of them.

          1. Thank you, that’s great news!

            Actually, what this article highlight is more generalized in my experience: everytime I try to code for WC lately, I feel like I need to pass a trial from the god of developers, where it only took few minutes before.

            Four days ago,
            I needed to prevent cart item from being changed under certain conditions, I needed to learn how WP REST API is working for that (that’s fine), to finally realize that the Store API is using totally different ways (double work and maintenance), and that the QuantityLimits::validate_cart_item_quantity doesn’t have any filters (I have made a PR on github) where the old API offered two: “woocommerce_stock_amount_cart_item” and “woocommerce_update_cart_validation”. I ended up bypassing WC with “rest_pre_dispatch” filter.

            Two days ago,
            I needed to add a custom payment gateway, and allow / disallow it according to user permissions, and found this article.

            Today
            I simply need to display a above the cart under certain conditions with PHP, how do I do?
            Before: I opened the browser inspector on cart page, I saw that the cart has the “woocommerce-cart-form” class. I searched that class in WC’s code, found it easily, and conveniently found out that the line before is the “woocommerce_before_cart” action. Job was done.
            Now: I open the browser inspector on cart page, the cart has “wp-block-woocommerce-cart” and “wc-block-cart” class, or “woocommerce/cart” block-name attribute, but searching them in code doesn’t lead anywhere near to the answer.

            I would like to have the answer, but I would love even more having a guidance how to debug / read / plug into this new code in order not to be stuck everytime I try to do something 🙂

            Again, I do realize that the code looks better now, and I thank you for your comments that are very encouraging as you acknoledge the issues and have plans to fix them, thanks a lot!

Leave a Reply

Your email address will not be published. Required fields are marked *