Within our series on building a WooCommerce extension, we’ve talked about using classes while building a widget and while creating a complex shortcode.
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.
The documentation available on the payment gateway API is pretty helpful, and addresses many of these steps.
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
ortest
)$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
orfalse
(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).
$this->init_form_fields(); $this->init_settings();
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 enabled
, title
, 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.
- 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.
- 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.
- Remove the cart with
WC()->cart->empty_cart();
. Now that the order is placed, we should empty out the cart. - 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 $order->reduce_order_stock(); // Remove cart WC()->cart->empty_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.
[…] interested in learning about WooCommerce can check out this tutorial on building a simple payment gateway from […]
Hi!)
I have a custom gateway. After ordering , it goes to the payment . How to automatically change the status: “processing”
code:
/**
* Process the payment and return the result
**/
function process_payment($order_id){
$order = new WC_Order($order_id);
return array(‘result’ => ‘success’, ‘redirect’ => $order->get_checkout_payment_url( true ));
}
PS. the full code of the gateway:)
https://github.com/makeroff/yadex-gateway/blob/master/.gitignore
Thanks for the guide!
By the way, how can I add a custom field in this new created payment gateway? I want to add a file upload function where customer need to upload their proof of bank transfer via cash deposit machine first before they can checkout, like in this screenshot – http://prntscr.com/db4tov
Very nice tutorials,
Hello sir, how do i redirect payment gateway page, could you give example.
Thanks
Great Great Stuff. I’m running a marketplace website with several buyers. At the moment all the money from the buyers gets into my bank account and I have to transfer the money manually to the vendors. Is it possible to write a custom gateway for marketplaces. I’m using woocommerce and WC Vendors. If It’s possible please show me the way.
Hey Trone, I’d recommend using Product Vendors with PayPal Mass Pay or PayPal Adaptive Payments. Having this sort of automated transfer is possible, but really complicated to achieve, not something simple and straight-forward like this tutorial 🙂
Thanks Beka. Unfortunately we do not have pay pal in my country.
Hi Beka
Great guide, glad i found it.
What might be the best approach to adding in custom fields for the buyer to complete at checkout when the new off-line gateway is used (e.g. radio button, select, text field) and then show that info within an order on the WP Dashboard.
A good example would be Direct Debit, or similar. So that those details can be processed off-line before manually pushing the order to complete.
Many thanks
Great tutorials,
but can you guide me how to redirect my custom bank payment page link.
Thanks in advance…
Great Tutorial !!
Is there a possibility to validate the Input of the setting fields ?
Regards
Ralf
Excellent tutorial. Thanks for sharing this amazing guidelines to create my own payment gateway. I think woocommerce is best.
[…] Create your own payment gateway and integrate it with a payment processor for additional control. […]
Thank you for a great tutorial Beka. I’m rebuilding an existing payment gateway and this helps me to understand what everything means. Could you suggest any resources for adding support for Subscriptions?
Hey Nate, I’d check out the gateway integration guide from the Subscriptions team for this.