Even for developers, WordPress blocks can be confusing at the best of times. Throw WooCommerce into the mix, and things get even trickier. I’ve been hanging out in the WooCommerce Community Slack lately, trying to help out where I can, and this question kept cropping up.
How do I integrate my existing payment gateway with the block checkout? I don’t know where to begin.
It’s not a question easily answered over messenger (as the length of this guide goes to show!), so I wanted to lay out the steps I would take — from start to finish — to add block support to an existing payment gateway plugin.
So, rather than do this in a purely hypothetical approach, I thought it would be cool to show some actual practice with a real plugin. If you like, you can follow along with the steps in your own testbed and see how the pieces fit together!
Before we begin, a few caveats to mention.
-
I’ve picked a plugin mostly at random from the WordPress.org repository. Based on its description it seemed simple enough to meet the needs for the tutorial, but I haven’t ever used the plugin, and therefore this isn’t a recommendation to use this plugin. It just suited the purposes of creating some simple blocks to hopefully show you more about the process than the plugin itself.
-
As a quick note, I highly recommend using TypeScript for your frontend code. You’ll have a much better time working with data that has types, avoiding tricky hidden errors, not to mention the added benefit of editor tooling such as intellisense/autocomplete. I’ve not included it in this tutorial though as it can be scary to some who are just getting started, and WooCommerce haven’t made it easy for us 3pd’s to use TS even if they do :upsidedown_face: (_though I’d still recommend it, honestly, you don’t have to use much to get the benefits!)
-
Before we crack on, make sure you’ve got Node and pnpm installed. You can of course use your own preferred node package manager, but the commands below will be using pnpm to keep things simple.
Before we begin
Before diving into the code, let’s quickly discuss the WooCommerce checkout block and what we’re aiming to achieve.
The WooCommerce Cart and Checkout block functions quite a bit differently than the WordPress blocks that you may have read about. Newer WooCommerce blocks, such as the ones they’ve built for the product collection, behave a lot more similarly though.
What this means is that when you’re looking to add blocks for the WooCommerce checkout, such as the payment gateway integration, you really need to rely on the WooCommerce documentation specifically, and not the WordPress block editor docs.
The additional TL;DR here is that the WooCommerce Checkout block is rendered in the frontend1, compared to the WordPress blocks which are mostly server side rendered (in PHP) with only the editor side of things rendered in React.
While WooCommerce has tried to add a compatibility layer to existing hooks and filters in PHP-land, some things are just not going to translate when it gets rendered (and re-rendered) in React-land. So, for the most part, you’ll be dealing with JS/TS for your checkout block integrations!
This also means you may need to delve into the Store API (redux store for session data), and probably touch on the WooCommerce Checkout event flows.
For the rest of this tutorial I will be working with the WooCommerce Custom Payment Gateway. I have never used this plugin before (as noted above), only coming across it searching the wp.org directory for “payment gateway” and looking for a simple sounding one.
While the other gateways may be more complicated and closer to a real payment gateway, the API implementation of the gateway isn’t the interesting part for this article, so this fit perfectly. I also noticed that there was at least one support request in the forum asking for block support where the developer indicated is not yet included. So it ticked all the boxes for our purposes!
Initial setup to start working with our block
Let’s kick things off by setting up a basic app skeleton. This will ensure that the PHP side is loading our script correctly on the block checkout pages and give us an entry point for our new shiny frontend block scripts.
The aim here is just to get the scripts being loaded at block checkout so that we have a solid base to work from for the rest of our integration.
In order to complete this step, we need to:
- add the frontend tooling required to bundle the scripts
- get a dev server running in watch mode (so we don’t have to manually refresh like heathens)
- add the PHP hooks and compatibility settings to declare block support for our gateway
Add the frontend tooling
In the WordPress block world, the go-to tool is wp-scripts, which is basically a handy wrapper around webpack.
It comes with some default config that let’s you hit the ground running. I’d recommend looking into the webpack docs for further configuration options as your needs grow. You can still merge the WP defaults and override them with anything specific to your use case.
Alright, cracks knuckles, let’s get to it!
First up we’ll:
- install the gateway plugin and pop open the directory in our terminal
- run
pnpm init
to create a fresh package.json, since our downloaded gateway didn’t come with one - add the
pnpm install -D @wordpress/scripts @woocommerce/dependency-extraction-webpack-plugin
packages too - add some other WordPress dependencies we’ll need:
pnpm install @wordpress/element @wordpress/html-entities @wordpress/i18n
- we can add some other quality of life scripts that we’ll be using too as part
of the
scripts
property - store our frontend code in the client directory by running
mkdir client/src/
and creating ourtouch client/src/app.jsx
file
Once all this is done, your package.json should look like:
Your packageManager line may differ, depending on the version installed at the time you run the command.
Don’t worry about it - you can use whatever pnpm version you like. Once you’ve got that installed and setup, you’re good to go!
Get a dev server running in watch mode
Next, we need to decide what webpack config you need. I would recommend setting up a webpack.config.js file, even if you plan on using the WP defaults, as this lets you override settings later down the line if you need to. It also lets you configure the entry and output paths which makes things easier and allows you to match your own plugin layout.
Will your development environment have SSL certificates enabled? Some gateways may require you to be running under SSL in order to load their SDK scripts, and by default wp-scripts will run in plain non-SSL mode.
It doesn’t matter so much for our purposes here, so the choice is up to you. A lot of free local WP environments provide free self signed certificates you can use though, so it might be good practice to set it up anyway. I’ll provide both example configs, you’ll need to tweak them for your own circumstances.
I’ll be using SSL as my local WP instance is running with self-signed certs, but it shouldn’t matter for the rest of this article. So choose Option A for non-SSL sites, or Option B for the full-kitchen-sink config that supports both 😎
(if you’re unsure here, go with option A, you should only need option B if your gateway or existing site has ssl requirements!)
-
Without needing to take into account SSL, your webpack.config.js can be simplified:
Specifically, we’re relying on most of the default WordPress config, but making key modifications like:
- filtering out the WordPress extraction plugin so that we can replace it with the WooCommerce version (L55-58)
- setting the entry points for our script and the output path and file name (L64-75)
-
First we need to install the dotenv dependency as we’re going to read in some local variables. We can use these in our config to allow us to keep it a little dynamic without needing to change the config each time in each project we end up using it in.
We’ll also add the clean-webpack-plugin whilst we’re at it.
You should make absolutely sure when using .env files that you are not comitting them into version control.
Most template .gitignore files will ignore them by default, but definitely, definitely, do not publish API keys stored in .env files into public repositories unless you want to have a really bad day (or understand why you need them there).
You can always commit
.env.sample
files instead that explain what variables need setting and the potential values or safe defaults.Shouldn’t matter for the purposes of how we’re using them in this tutorial, but it’s always worth mentioning!
For our purposes let’s fill the .env file with:
Next up, we can use it with a custom webpack.config.js:
Don’t worry if this looks complicated — it’s mostly boilerplate. Once set up, you can reuse this across projects. 😅
All’s we’re doing here is importing the default WordPress config and spreading it onto our own config object. This let’s us override only the bits we need to, and the rest is taken from WordPress defaults.
Here’s what we’re doing:
- supporting both SSL and non-SSL within the same config, allowing us to turn it
on and off via an
.env
file (L31 and L40) - adding additional config for the
devServer
so that we provide CORS headers (L14-20) - also adding an
allowedHost
property that matches our WP instance (L21-28) - and adjusting the websocketURL depending on if we’re running in SSL mode or not (L30-32)
- telling webpack where to find our SSL cert files. You will need to look up your own certs and provide them in the .env (L40-49)
Finally, in the last secton we:
- filter out the WordPress extraction plugin so that we can replace it with the WooCommerce version (L55-58)
- add the clean webpack plugin to keep our output dir clean when running the dev server for long periods of time (it generates numerous .hot-update. files as you change files while it is running) (L60-62)
- set the entry points for our script and the output path and file name (L64-75)
It feels like a lot, but once you have this down, you can re-use this in all your other projects and just change the entry and output paths!
SSL is a bit tricky, but this setup ensures you’re covered whether your site uses SSL or not.
- supporting both SSL and non-SSL within the same config, allowing us to turn it
on and off via an
Add the PHP hooks and compatibility settings to declare block support
First things first, let’s tell WooCommerce that we support the cart checkout
block. This can be done by hooking into the before_woocommerce_init
action.
Luckily, our plugin is already declaring some compatibility on L56 of the
woocommerce-other-payment-gateway class, so let’s add it in there:
We also need to register our integration with the WooCommerce block registry. To
do that we need to hook into the woocommerce_blocks_loaded
action which we can
do the same file. We’ll add it just below the existing plugins_loaded
hooks:
The eagle eyed amongst you probably noticed we’re creating a new instance of a
WC_Other_Payment_Gateway_Blocks_Integration
class in the above code, but that
class doesn’t exist in our plugin! So let’s create a new file and get that class
created:
Hopefully the annotations in the code make it self explanatory, but to recap quickly what this class is doing.
- We’re creating a new block integration class which is going to be registered to the WooCommerce registry
- It extends the Blocks
AbstractPaymentMethodType
from WooCommerce - We added the abstract methods from the extended class with our own ID’s and data
- When the
initialize()
function is called, we register the scripts and styles for our block integration based on the output of webpack that we configured in the previous step - Finally adding a little protection for customers to hopefully avoid some unnecessary loading
A quick note about coding standards.
I’ve purposefully left the code here as simple as possible and tried to match the style of the existing plugin so as not to look completely out of place.
It’s up to you to make sure you align with coding best practices and standards of your company or follow WordPress/PSR/Other standard. The code above could certainly be tidier and use utility functions to avoid repetition, so if you spot a point of improvement, go for it!
A quick recap so far
In this section we’ve setup the basic frontend tooling that let’s us start with the frontend coding. Node and pnpm should be up and running, and we’ve initalized a project package.json file to take care of installing our node dependencies. We also discussed the difference between SSL and non-SSL configuration when it comes to running the block scripts in your local WP instance.
You should be able to run pnpm dev
now and see a similar output to:
Getting an error that your app.jsx file is not found?
Double check your output paths in your webpack config and make sure they match
your project layout. Did you remember to mkdir
the client/src directory with
the app.jsx file inside?
We then switched back to our PHP backend, and added the initial template to get the block scripts registered and initalized there too. If you’ve got everything running correctly, you should be able to navigate to Checkout under a FSE/block theme and check the network tab to make sure the scripts are being loaded!
You will probably see a failure for the .css file as we have not yet added any styling. We will tackle that in the next steps.
So long as you’re not seeing any errors with webpack running (even if it isn’t doing anything!), and you see the scripts being loaded at checkout, then you’re good to move on. If not, pause here and do some Googling (or Chat Gippity) and get those resolved first.
You’re doing great, let’s keep going!
Let the frontend fun begin
Now we can get cracking on the fun part, messing around with our frontend code. What I like to do when adding an integration to an existing plugin is to have both the shortcode checkout and block checkout available in separate tabs so I can always go back and forth.
This just means, depending on when you created your local WordPress instance with WooCommerce, you might need to create a new page using either the legacy WooCommerce checkout shortcode or the WooCommerce checkout block.
In either case, make sure you are able to navigate to both in your browser, and we can get started on the next piece.
Here we have our other gateway plugin showing on the shortcode based checkout. This is the UI we’re going to want to replicate in React/blocks. Fortunately for us, this is fairly simple and consists of only a single textarea and label.
Registering the payment gateway
So while we’ve already registered the gateway under PHP and triggered the loading of the scripts, because WooCommerce renders the blocks at checkout via the frontend, we need to register the payment gateway on the frontend also.
We can add this to our app.jsx file we created earlier.
If you’re brand new to React/JS then some of this might look a little strange, but hopefully it’s not completely unreadable. There’s a few things happening here in order to set up the gateway.
Firstly, we need to use the WooCommerce functions registerPaymentMethod
and getPaymentMethodData
. These are available globally on the window
object as part of the wcBlocksRegistry and wcSettings objects, respectively.
The WooCommerce dependency extraction plugin we added searches through our code for any import statement targeting an @woocommerce
module and adds it to the PHP script dependencies. Essentially, the import acts as an alias. If you want to import directly, or aren’t using the Webpack plugin, you can access the functionality via the global window
object instead.
We’re also importing a component to be used for our edit and save components (explained below), but we haven’t created this yet. Go ahead and create a new components directory with the payment-method.jsx file. We’ll add the contents for this in a little bit.
We then use the getPaymentMethodData
function to grab the current gateway
settings. (hint: these are the values we passed through from the backend in the
previous step!). We export this constant so we can reuse it in other files without needing to re-request it from the function. Ultimately, it loads from a script tag on the page.
So retriggering the function isn’t going to cause performance degradation either, I just find it easier to do the import.
Finally, we set a default gateway name that we can fall back on in case the backend doesn’t provide it (which it currently doesn’t, but we can fix that later!).
Now we get to the main part of the app file, where we call the
registerPaymentMethod
function to
register the block gateway
with WooCommerce. These values are described on the WooCommerce docs, but the
interesting ones to mention here are the content, edit, and label
properties.
The label property takes a React element and is rendered next to the radio button at block checkout. We’re just wrapping the settings title, or default title, in a span, which itself is wrapped in a div with the appropriate classes from WooCommerce.
What’s interesting though is that when WooCommerce renders this component, it
passes through some additional props, one of which is the components
object which contains some handy components you could use instead. Specifically,
as you can read in the docs, the PaymentMethodLabel
and PaymentMethodIcons
components that you could use here instead.
If you wanted to get fancy, you could pause here and create your own React component that uses these components which would allow you to pass through a list of cardIcons, icon, and text. You could then import this in your app file to use for the label property. You can also reuse this custom component for future projects.
For the content and edit props we’re using the same component,
<PaymentMethod />
, just for ease of use. content would be what gets
rendered for the customer to see at checkout, and edit is what’s
shown in the admin site editor when editing the checkout page.
Since there’s no difference between the two in our case, we can use the same
component for both.
Importing plugin settings
So now we have our app running, we need to add some of the plugin settings so that we can control the block UI in the same way the shortcode checkout works.
Specifically, we’re looking to grab the merchant configured payment gateway name, the message, whether the text is required, or if the text field should show at all. (we’re not concerned about the order status, as this will still be handled by the backend.)
Remember when we setup the get_payment_method_data()
function earlier? We need to go back there and
pass over these plugin settings now.
Here we’re passing over the plugin settings that are relevent for the frontend. As our
previous implementation of the AbstractPaymentMethodType
class initialized our settings
for us (by calling get_option()
in the initialize()
function), we can call get_setting()
in order to grab the settings from the database option.
You can test whther this is working before changing any frontend code by running a console.log statement in your browser’s console window:
We can now use these settings together with our frontend UI!
Updating the frontend ui
We’ve already pre-emptively added our title and description, so there’s no need to update that in our code. It should now work as expected rather than falling back to the defaults we provided.
Go ahead! Try changing the settings in the plugin payment settings page in wp-admin and reload the block checkout.
We can now add the rest of the components to match the shortcode checkout. Reviewing the shortcode checkout in our other browser tab, we need to add the description, and the input box that is only shown when hide_text_box is “no”. So let’s add that quickly to our payment-method component we added earlier:
We’re setting up state to store the note for later use and importing WooCommerce’s Textarea component, using the alias that the webpack extract plugin handles for us.
Next, we use the description from the settings and conditionally render the textarea based on whether it’s enabled in the plugin settings (i.e., when it’s not hidden).
At this point, you should have the UI in place!
But we’re not done yet! Unfortunately, the Textarea component doesn’t support required, so we’re going to have to add that feature another way and we also need to handle the payment processing with our gateway too.
While we’re here, we can also add CSS support to our plugin. While this plugin won’t need any real styling, feel free to go as nuts as you like with your own. We’ll set up the files just to show how it’s done.
Back in our app.jsx file, add a new import for your stylesheet:
And make sure to create the style directory with the style.css file inside. We can add some sample CSS to it just so webpack generates us a file, and we solve the 404 error we were seeing earlier:
Give that a try! Restart your webpack server if required, and give the block checkout a reload.
Handling the hooks
Hooks are a React feature for composing reusable logic. React comes with many built-in hooks, some of which you’ve already seen in our code for storing state, while others trigger when certain state changes.
Our payment gateway logic is no different. WooCommerce provides us some hooks we can use in order to register our gateway for certain events, as described in their event workflows.
Since our requirements are simple, we’ll focus on using onPaymentSetup()
- which triggers after the customer
selects the Place Order button. (while there other events are fired first, we don’t need to handle them.)
Let’s take a look at the code that works first, and then we can step through it.
First, we’re importing the internationalization function, __()
, which
should be familiar if you’ve worked with WordPress before. All of WordPress’ usual i18n
functions are available in this package too.
Next, we need to destructure the eventRegistration and emitResponse from the component’s props.
By default, WooCommerce passes these props to your components when you register your
payment gateway. There’s a whole host of other properties you can use too! Why don’t you try
replacing the destructured props with just the word props
and console.log them to see
what’s available?
The eventRegistration prop gives us access to the WooCommerce events we mentioned earlier.
Specifically, on line 9, we destructure the onPaymentSetup()
registration function for our
gateway to use.
The emitResponse prop gives us acces to a few different constants that we can use when building our reply, whether a success or error message, and where the error should be displayed.
From lines 12-21, we’re adding the functionality for our required note on the textarea. If the plugin settings enforce a note here, we check if the stored state (paymentNote which is updated when the user types into the field) is empty, and if so we immediately return an error with an error message and messageContext.
The error message uses the i18n function (__()
), so when the plugin is built, the message will
be included in the generated .pot file. The messageContext prop lets us tell WooCommerce where
to put the error message, in this case, above the payment form closer to where our input is. Otherwise
it appears at the top of the checkout page.
This can be a nicer UX for the customers, as they know to correct the field immediately below it.
One limitation of this basic implementation is that it doesn’t indicate which field is causing the error. A more polished solution would involve adding error styling to the textarea and using relevant ARIA labels for accessibility.
Why not give it a try? You could store an error state and reset it after the user starts typing.
Finally, on lines 23-30, we send back a success response, allowing the checkout process continue. We also include the customer note in the payment data, which can be processed by the backend.
It’s important to note that we need to use the same object property that the shortcode checkout would use, so the existing
process_payment()
function still works accordingly.
If you’re unsure what this is from reading the PHP code, you can open up the network page on the shortcode checkout.
Make sure that preserve log is enabled, and then submit a successful order.
You will see a request to ?wc-ajax=checkout
that has the payload that lists all the fields that the gateway is
sending to the backend that you can then replicate for your block support.
At this point, if you try and place the order via blocks checkout, you should be blocked from continuing if the note field is empty and the plugin has it required, and the order should be placed successfully and the note added to the order viewable in admin otherwise!
Congrats! 🎉 If you made it this far, give yourself a pat on the back.
Wrapping up
Building with WooCommerce blocks comes with its own set of challenges, especially since WooCommerce handles cart and checkout pages differently than the traditional shortcode approach. If you’re coming from a PHP background, the shift to JavaScript (and all the associated tooling) may feel steep. However, as we’ve seen in this tutorial, the code itself isn’t overly complicated—the main challenge lies in figuring out what functions are available and how to access them.
For you TypeScript developers out there, welcome to chaos. Unfortunately a lot of the types you need to access are not exported from any package, and working with the webpack extraction plugin and alias imports makes it a real pain. Even for those types you do have, you might find they aren’t tight enough to be worth while (lots of any’s!).
All that said though, there’s plenty of helpful folks in the WooCommerce community Slack (I’ll be there, come say hi! 👋), and if you Google hard enough you’ll likely find what you need on Github.
Good luck!