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:
- add your own custom blocks to visualize data, or collect user input in the front-end;
- modify the behavior or appearance of the Cart and Checkout blocks;
- register custom callbacks to process input from your blocks and update the cart state;
- expose custom data in the responses of existing Store API routes; or
- change the behavior of the Store API with WordPress action and filter hooks.
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:
- The Store API cart controller updates your cart and recalculates shipping rates in the back end.
- A response is sent with the updated cart contents and shipping rates.
- The Cart or Checkout block receives the response and updates the displayed shipping options.
Before | After |
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.
- 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.
- 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:
- Enable the Direct Bank Transfer and Cash on Delivery payment gateways.
- Hide the Direct Bank Transfer option when Free Shipping is selected.
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:
- In order to register our schema and data callback functions, we used the global helper function
woocommerce_store_api_register_endpoint_data
. - Our data is namespaced using an identifier that is unique to our plugin.
- We made sure that the
woocommerce_blocks_loaded
action has fired before initializing this integration.
To confirm that everything works:
- Make sure that the WooCommerce Blocks plugin is installed and activated on your site.
- Clone this GitHub repository inside the
/wp-content/plugins
directory of your WordPress installation. - Log into your WordPress dashboard.
- Under the Plugins menu, locate and activate the WooCommerce Checkout Integration Example 1 plugin.
Then:
- Add a product to the cart.
- Go to the Cart page — note that this should contain the Cart block, not the legacy Cart shortcode!
- Open the Chrome Developer Tools, and go to the Network tab.
- Change the quantity of the item you just added to the cart. This will trigger a wc/store/v1/cart/update-item route request.
- 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:
- Make sure that you are using at least node version 14.18 with npm version 6.14.
- Run
npm install
followed bynpm 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!
Before | After |
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!
Leave a Reply