How to Create a Simple WooCommerce Payment Gateway

Today we’ll take a look at building a simple WooCommerce payment gateway integration, as this will give us an opportunity to extend a WooCommerce class. WooCommerce gives you several core classes, such as payment gateway or email classes, that can be extended to add your own functionality, saving you time on development and ensuring your plugin works in a standard way.

Background: The WC_Payment_Gateway Class

This is a bit of pluginception going on, but the WC_Payment_Gateway class is actually an extension of the WC_Settings_API class.

The WC_Settings_API class gives us the basic structure for all settings used throughout WooCommerce, such as the ability to create form fields and validate them.

The WC_Payment_Gateway class extends this to give us structure and functions that will be specific to payment methods, such as the ability to get order totals or the thank you page URL. By extending this class to create our own payment gateway, we can benefit from the structure already present. This handles various functions for us, such as getting the title and description and displaying it on the checkout page.

When you extend a class within your own plugin, you gain the functionality associated with that class, and you can add your own or make changes with overrides. Another place this is helpful is when adding your own emails or widgets, as you can extend WooCommerce core classes to benefit from the basic structure present.

All payment gateways in WooCommerce will begin by extending the WC_Payment_Gateway class.

1. Check that WooCommerce is active

Because we’ll be extending a class in WooCommerce core, we’ll need to be sure it’s active so that class is present to avoid fatal errors. We can’t extend a class that doesn’t exist 🙂 .

The easiest way to do so is to add a check for WooCommerce within the active plugins, and to return if it’s not present.

// Make sure WooCommerce is active
if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) return;

2. Begin to Build Your Child Class

I’m going to wrap this in an init function and hook into plugins_loaded later than the default priority. This ensures that if WooCommerce is active (which we’ve just checked for), we load our class after WooCommerce core (making this a secondary check against fatal errors). This ensures that, not only is WooCommerce active, but we’re loading after it so the WC_Payment_Gateway class is available.

To extend a class, you’ll follow the format:

class My_Child_Class extends The_Parent_Class { }

Here’s what this will look like in our plugin:

 * Offline Payment Gateway
 * Provides an Offline Payment Gateway; mainly for testing purposes.
 * We load it later to ensure WC is loaded first since we're extending it.
 * @class       WC_Gateway_Offline
 * @extends     WC_Payment_Gateway
 * @version     1.0.0
 * @package     WooCommerce/Classes/Payment
 * @author      SkyVerge
add_action( 'plugins_loaded', 'wc_offline_gateway_init', 11 );

function wc_offline_gateway_init() {

    class WC_Gateway_Offline extends WC_Payment_Gateway {

        // The meat and potatoes of our gateway will go here

    } // end \WC_Gateway_Offline class

3. Construct the Gateway

The first thing we’ll do in our class is build our __construct() function. This will load in the variables that we need to include in our class.

There are five required variables that we’ll have to set in this function:

  • $this->id = the unique ID for this gateway (i.e., offline or test)
  • $this->icon = the link to the image displayed next to the method’s title on the checkout page — this is optional and doesn’t need to be set.
  • $this->has_fields = true or false (bool). This should be false for our simple gateway, but can be set to true if you create a direct payment gateway that will have fields, such as credit card fields.
  • $this->method_title = the title of the payment method for the admin page (i.e., “Cheque”)
  • $this->method_description = the description for the payment method shown to the admins

Once we’ve set these variables, the constructor will need a few other functions. We’ll have to initialize the form fields and settings.

Form fields will be set in the init_form_fields() function, which adds all of the settings fields (such as enabling the gateway and adding a title to be displayed to customers).


We’ll also add our actions to our constructor, which we’ll get to next.

4. Init the Form Fields

We’ll need to create an init_form_fields() function to set up the form fields for our payment gateway. A stub for this function is included in the main settings class, which we’re extending (by proxy), so this is an example of a method in a child class overriding the method in its parent class. While the function does nothing in the parent class (aside from make sure there are no fatal errors if you don’t override it), we’ll give it some functionality in our child class.

The basic fields that we should include are enabledtitle, and description.

  • Enabled will be a checkbox that allows the user to enable or disable the gateway on the checkout page.
  • Title is different from the title we set in the constructor; this will be the title shown to customers at checkout and can be different from the admin title
  • Description will be shown under the title when the gateway is selected at checkout, and tells customers what to do next

Since I’m creating an offline gateway, I’m also going to create an “Instructions” field, which I’ll add to emails and the “Thank you” page to remind customers of how to remit payment. You can create these fields in the same way that you create any WooCommerce setting (as we’ve already shown the payment gateway class extends the settings API to let us do this).

 * Initialize Gateway Settings Form Fields
public function init_form_fields() {
    $this->form_fields = apply_filters( 'wc_offline_form_fields', array(
        'enabled' => array(
            'title'   => __( 'Enable/Disable', 'wc-gateway-offline' ),
            'type'    => 'checkbox',
            'label'   => __( 'Enable Offline Payment', 'wc-gateway-offline' ),
            'default' => 'yes'

        'title' => array(
            'title'       => __( 'Title', 'wc-gateway-offline' ),
            'type'        => 'text',
            'description' => __( 'This controls the title for the payment method the customer sees during checkout.', 'wc-gateway-offline' ),
            'default'     => __( 'Offline Payment', 'wc-gateway-offline' ),
            'desc_tip'    => true,

        'description' => array(
            'title'       => __( 'Description', 'wc-gateway-offline' ),
            'type'        => 'textarea',
            'description' => __( 'Payment method description that the customer will see on your checkout.', 'wc-gateway-offline' ),
            'default'     => __( 'Please remit payment to Store Name upon pickup or delivery.', 'wc-gateway-offline' ),
            'desc_tip'    => true,

        'instructions' => array(
            'title'       => __( 'Instructions', 'wc-gateway-offline' ),
            'type'        => 'textarea',
            'description' => __( 'Instructions that will be added to the thank you page and emails.', 'wc-gateway-offline' ),
            'default'     => '',
            'desc_tip'    => true,
    ) );

Now we have basics about our gateway, such as its name and description, along with fields that merchants will fill in within its settings. Since we’re extending WooCommerce core classes, WC will handle rendering these at checkout for us when our gateway is enabled, so now we just need to worry about how the gateway should act at checkout.

5. Process the Payment

This is the most important part of the payment gateway.

There are a few pieces to this function.

  1. Set the correct status for the order. In this case, we want to mark orders as “on-hold” since the payment hasn’t been received.
  2. Reduce Stock (optional). You probably want to reduce inventory once the order is processed to reserve the stock. However, you can leave this out if the order won’t reduce stock until the admin manually reduces it (not recommended), or if you’ll be adding your own code to reduce it when the status is changed from on-hold to processing.
  3. Remove the cart with WC()->cart->empty_cart();. Now that the order is placed, we should empty out the cart.
  4. Redirect to the “Thank you” page. We’ll want to display the “Success” result since the order was placed, and redirect the return URL for that order (so we display the correct information).
public function process_payment( $order_id ) {
    $order = wc_get_order( $order_id );
    // Mark as on-hold (we're awaiting the payment)
    $order->update_status( 'on-hold', __( 'Awaiting offline payment', 'wc-gateway-offline' ) );
    // Reduce stock levels
    // Remove cart
    // Return thankyou redirect
    return array(
        'result'    => 'success',
        'redirect'  => $this->get_return_url( $order )

This function handles the processing of the order, telling WooCommerce what status is should have and where customers go after it’s used.

6. Add Payment Gateway Info to Order Received and Emails

Now we’ve got some final polish steps to take care of. Since this gateway requires further instructions to complete the payment, we want to be sure its instructions are shown on both the thank you page and order emails, which is what the thankyou_page() and email_instructions() stub methods are for.

 * Output for the order received page.
public function thankyou_page() {
    if ( $this->instructions ) {
        echo wpautop( wptexturize( $this->instructions ) );
 * Add content to the WC emails.
 * @access public
 * @param WC_Order $order
 * @param bool $sent_to_admin
 * @param bool $plain_text
public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
    if ( $this->instructions && ! $sent_to_admin && 'offline' === $order->payment_method && $order->has_status( 'on-hold' ) ) {
        echo wpautop( wptexturize( $this->instructions ) ) . PHP_EOL;

Instructions field displayed to customers on the thank you page / emails: check.

7. Add to WooCommerce Payment Gateways

The last thing we’ll have to do is add this to the available WooCommerce gateways under WooCommerce > Settings > Checkout. There’s a filter we can easily use to add this gateway to the list of available payment gateways: woocommerce_payment_gateways

This filter gives us the array of all available gateways, so we’ll add our gateway into this array, then return the array with our gateway added.

function wc_offline_add_to_gateways( $gateways ) {
    $gateways[] = 'WC_Gateway_Offline';
    return $gateways;
add_filter( 'woocommerce_payment_gateways', 'wc_offline_add_to_gateways' );

Taking it Further

This is a very simple example of how to add a payment gateway to WooCommerce, which is essentially a clone of the “Check Payments” gateway. If you’re integrating with a payment processor, you’ll need to incorporate posting and receiving information from the payment processor, and this will get more complex (you may even need to include tokenization / saving cards to customer accounts). This will vary with each payment processor and the API the processor provides.

However, this sample includes the basics that will need to be handled from the WooCommerce side of things, such as the settings, setting the initial order status, redirecting to the thank you page, and including any needed info in emails.

WooCommerce Payment Gateway Sample: Offline Gateway

We’ve got a working sample of an offline gateway that basically clones the functionality of the “Cheque” gateway. You could use this to accept another manual payment method, such as “Invoice Later”, or use this as a “Test” gateway if you’ve already allocated the Cheque gateway to something else.

Anjali Punjab

Anjali Punjab is a freelance writer, blogger, and ghostwriter who develops high-quality content for businesses. She is also a HubSpot Inbound Marketing Certified and Google Analytics Qualified Professional.