Create a Payment Gateway Plugin for WooCommerce
Before we begin this tutorial, if you need WooCommerce payment plugin development, please contact me, me and my team will be happy to help 🚀
Step 1. We begin with creating a plugin
When I first heard about plugins for WordPress I thought it is so hard to create one. But actually, to create a plugin you just need to create a file and add a couple lines of code inside it.
So, in the plugins
folder I created misha-gateway.php
file and added the following code there. In case your plugin will have more than one file, place it in the folder with the same name, example: misha-gateway/misha-gateway.php
.
<?php
/*
* Plugin Name: WooCommerce Misha Payment Gateway
* Plugin URI: https://rudrastyh.com/woocommerce/payment-gateway-plugin.html
* Description: Take credit card payments on your store.
* Author: Misha Rudastyh
* Author URI: http://rudrastyh.com
* Version: 1.0.1
*
Once you did it, the plugin will appear in your admin area! And you can even activate it.

Step 2. Payment Gateways are PHP classes. Here is the class skeleton.
So we have to create a custom PHP Class to extend WooCommerce WC_Payment_Gateway class.
Every class method is described below. You can begin with copying and pasting the below code into your main plugin file.
/* * This action hook registers our PHP class as a WooCommerce payment gateway */ add_filter( 'woocommerce_payment_gateways', 'misha_add_gateway_class' ); function misha_add_gateway_class( $gateways ) { $gateways[] = 'WC_Misha_Gateway'; // your class name is here return $gateways; } /* * The class itself, please note that it is inside plugins_loaded action hook */ add_action( 'plugins_loaded', 'misha_init_gateway_class' ); function misha_init_gateway_class() { class WC_Misha_Gateway extends WC_Payment_Gateway { /** * Class constructor, more about it in Step 3 */ public function __construct() { ... } /** * Plugin options, we deal with it in Step 3 too */ public function init_form_fields(){ ... } /** * You will need it if you want your custom credit card form, Step 4 is about it */ public function payment_fields() { ... } /* * Custom CSS and JS, in most cases required only when you decided to go with a custom credit card form */ public function payment_scripts() { ... } /* * Fields validation, more in Step 5 */ public function validate_fields() { ... } /* * We're processing the payments here, everything about it is in Step 5 */ public function process_payment( $order_id ) { ... } /* * In case you need a webhook, like PayPal IPN etc */ public function webhook() { ... } } }
If you inserted the code above “as is” in your plugin file, you get 500 error, because this code just shows the plugin class structure, where each method should be.
Step 3. Payment Gateway Plugin Options
In the class constructor we:
- Define class properties, like gateway ID and name, lines 23-33,
- Initialize the settings, lines 35-39,
- Append options to class properties, lines 40-45,
- Save options, line 48,
- Enqueue custom JavaScript and CSS if needed, line 51.
We can also register payment gateway webhooks in class constructor (example on line 54).
public function __construct() { $this->id = 'misha'; // payment gateway plugin ID $this->icon = ''; // URL of the icon that will be displayed on checkout page near your gateway name $this->has_fields = true; // in case you need a custom credit card form $this->method_title = 'Misha Gateway'; $this->method_description = 'Description of Misha payment gateway'; // will be displayed on the options page // gateways can support subscriptions, refunds, saved payment methods, // but in this tutorial we begin with simple payments $this->supports = array( 'products' ); // Method with all the options fields $this->init_form_fields(); // Load the settings. $this->init_settings(); $this->title = $this->get_option( 'title' ); $this->description = $this->get_option( 'description' ); $this->enabled = $this->get_option( 'enabled' ); $this->testmode = 'yes' === $this->get_option( 'testmode' ); $this->private_key = $this->testmode ? $this->get_option( 'test_private_key' ) : $this->get_option( 'private_key' ); $this->publishable_key = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' ); // This action hook saves the settings add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); // We need custom JavaScript to obtain a token add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) ); // You can also register a webhook here // add_action( 'woocommerce_api_{webhook name}', array( $this, 'webhook' ) ); }
Depending on the payment processor you use, the option fields could be different, but in most cases you will have “Enabled/Disabled”, “Title”, “Description” and “Test mode” options.
public function init_form_fields(){ $this->form_fields = array( 'enabled' => array( 'title' => 'Enable/Disable', 'label' => 'Enable Misha Gateway', 'type' => 'checkbox', 'description' => '', 'default' => 'no' ), 'title' => array( 'title' => 'Title', 'type' => 'text', 'description' => 'This controls the title which the user sees during checkout.', 'default' => 'Credit Card', 'desc_tip' => true, ), 'description' => array( 'title' => 'Description', 'type' => 'textarea', 'description' => 'This controls the description which the user sees during checkout.', 'default' => 'Pay with your credit card via our super-cool payment gateway.', ), 'testmode' => array( 'title' => 'Test mode', 'label' => 'Enable Test Mode', 'type' => 'checkbox', 'description' => 'Place the payment gateway in test mode using test API keys.', 'default' => 'yes', 'desc_tip' => true, ), 'test_publishable_key' => array( 'title' => 'Test Publishable Key', 'type' => 'text' ), 'test_private_key' => array( 'title' => 'Test Private Key', 'type' => 'password', ), 'publishable_key' => array( 'title' => 'Live Publishable Key', 'type' => 'text' ), 'private_key' => array( 'title' => 'Live Private Key', 'type' => 'password' ) ); }
If you’ve done everything correctly, your options page should like like this:

Step 4. Direct Checkout Form
Before implementing the code below, please read these key points:
- If you’re creating a payment gateway like PayPal, where all the user actions happen on the payment gateway website, you can skip this step, just do not add
payment_fields()
andvalidate_fields()
methods and continue to Step 5. - In this tutorial I assume that you use a payment processor that sends card data with its own AJAX-request and gives you a token that you can use in PHP, so do not (!) add
name
attributes to the card form fields. This is how it works by step:- Customer fills his card data and clicks “Place Order” button.
- We delay the form submission using
checkout_place_order
event in WooCommerce and send AJAX request with card data directly to our payment processor, - If customer details are OK, the processor returns a token and we add it to our form below,
- Now we can submit the form (in JS of course),
- We use the token in PHP to capture a payment via payment processor’s API.
4.1 Enqueue scripts
In step 2 we’ve already added wp_enqueue_scripts
action hook and connect payment_scripts
method to it.
public function payment_scripts() { // we need JavaScript to process a token only on cart/checkout pages, right? if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) { return; } // if our payment gateway is disabled, we do not have to enqueue JS too if ( 'no' === $this->enabled ) { return; } // no reason to enqueue JavaScript if API keys are not set if ( empty( $this->private_key ) || empty( $this->publishable_key ) ) { return; } // do not work with card detailes without SSL unless your website is in a test mode if ( ! $this->testmode && ! is_ssl() ) { return; } // let's suppose it is our payment processor JavaScript that allows to obtain a token wp_enqueue_script( 'misha_js', 'https://www.mishapayments.com/api/token.js' ); // and this is our custom JS in your plugin directory that works with token.js wp_register_script( 'woocommerce_misha', plugins_url( 'misha.js', __FILE__ ), array( 'jquery', 'misha_js' ) ); // in most payment processors you have to use PUBLIC KEY to obtain a token wp_localize_script( 'woocommerce_misha', 'misha_params', array( 'publishableKey' => $this->publishable_key ) ); wp_enqueue_script( 'woocommerce_misha' ); }
4.2 Obtain a token in JavaScript
Before all I want to say that for every payment processor this code could be different, but the main idea is the same. Here is the content for your misha.js
file:
var successCallback = function(data) { var checkout_form = $( 'form.woocommerce-checkout' ); // add a token to our hidden input field // console.log(data) to find the token checkout_form.find('#misha_token').val(data.token); // deactivate the tokenRequest function event checkout_form.off( 'checkout_place_order', tokenRequest ); // submit the form now checkout_form.submit(); }; var errorCallback = function(data) { console.log(data); }; var tokenRequest = function() { // here will be a payment gateway function that process all the card data from your form, // maybe it will need your Publishable API key which is misha_params.publishableKey // and fires successCallback() on success and errorCallback on failure return false; }; jQuery(function($){ var checkout_form = $( 'form.woocommerce-checkout' ); checkout_form.on( 'checkout_place_order', tokenRequest ); });
4.3 Form with card data
With payment_fields()
class method you can create a payment form with card fields like this:

Below is the code:
public function payment_fields() { // ok, let's display some description before the payment form if ( $this->description ) { // you can instructions for test mode, I mean test card numbers etc. if ( $this->testmode ) { $this->description .= ' TEST MODE ENABLED. In test mode, you can use the card numbers listed in <a href="#" target="_blank" rel="noopener noreferrer">documentation</a>.'; $this->description = trim( $this->description ); } // display the description with <p> tags etc. echo wpautop( wp_kses_post( $this->description ) ); } // I will echo() the form, but you can close PHP tags and print it directly in HTML echo '<fieldset id="wc-' . esc_attr( $this->id ) . '-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">'; // Add this action hook if you want your custom payment gateway to support it do_action( 'woocommerce_credit_card_form_start', $this->id ); // I recommend to use inique IDs, because other gateways could already use #ccNo, #expdate, #cvc echo '<div class="form-row form-row-wide"><label>Card Number <span class="required">*</span></label> <input id="misha_ccNo" type="text" autocomplete="off"> </div> <div class="form-row form-row-first"> <label>Expiry Date <span class="required">*</span></label> <input id="misha_expdate" type="text" autocomplete="off" placeholder="MM / YY"> </div> <div class="form-row form-row-last"> <label>Card Code (CVC) <span class="required">*</span></label> <input id="misha_cvv" type="password" autocomplete="off" placeholder="CVC"> </div> <div class="clear"></div>'; do_action( 'woocommerce_credit_card_form_end', $this->id ); echo '<div class="clear"></div></fieldset>'; }
Step 5. Process payments
5.1 Validate fields
I know that checkout fields like First name should be validated earlier, but this is just an example:
public function validate_fields(){ if( empty( $_POST[ 'billing_first_name' ]) ) { wc_add_notice( 'First name is required!', 'error' ); return false; } return true; }
5.2 Capture payments with API and set the order status
Prepare to a lot of text 🙃
- Once you get the order object with
wc_get_order()
function, you can use its methods likeget_billing_first_name()
,get_billing_country()
,get_billing_address_1()
etc to get the customer billing and shipping details (by the way you can find all the methods inincludes/class-wc-order.php
which is in WooCommerce plugin folder). You can also get the billing details from$_POST
array, at the moment of writing this tutorial I’m not sure what is better. - You can add notes to the order with
$order->add_order_note()
method, it can be notes to customer (will be displayed in member’s area) and private notes (only on edit order pages).
Order notes on Edit Order pages - In this tutorial we consider using direct payments without going to gateway websites. But if for your purposes customers must go to a payment gateway website to complete their payment, you have to skip Step 4 and in this step instead capturing the payments with
wp_remote_post()
, useadd_query_arg()
to build a correct redirect URL to your payment gateway checkout page. - Use
$order->get_total()
to receive the order amount. get_woocommerce_currency()
should help you to get current shop currency.- If your payment gateway asks to list all the products in a order, use
$order->get_items()
, some examples you can find here.
public function process_payment( $order_id ) { global $woocommerce; // we need it to get any order detailes $order = wc_get_order( $order_id ); /* * Array with parameters for API interaction */ $args = array( ... ); /* * Your API interaction could be built with wp_remote_post() */ $response = wp_remote_post( '{payment processor endpoint}', $args ); if( !is_wp_error( $response ) ) { $body = json_decode( $response['body'], true ); // it could be different depending on your payment processor if ( $body['response']['responseCode'] == 'APPROVED' ) { // we received the payment $order->payment_complete(); $order->reduce_order_stock(); // some notes to customer (replace true with false to make it private) $order->add_order_note( 'Hey, your order is paid! Thank you!', true ); // Empty cart $woocommerce->cart->empty_cart(); // Redirect to the thank you page return array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ) ); } else { wc_add_notice( 'Please try again.', 'error' ); return; } } else { wc_add_notice( 'Connection error.', 'error' ); return; } }
5.3 In case you need a Payment Gateway Callback (Instant Payment Notifications, Webhooks etc)
Let’s say that our custom payment gateway doesn’t have its own form for the card data, and once the customer completed his billing details (like name, address etc) he will be redirected to a payment gateway website.
How can we check if payment is completed and display it in our store?
Manually? Huh, serious? 😐
Many payment gateways have payment notifications and this is how it works — once a customer completed an order on a payment gateway website, the gateway sends a request with $_GET parameters to a specific URL of our website that we set on the gateway’s settings page. And WooCommerce allows to process these requests.
The webshook URLs (callback URLs) in Woo look like this: http://rudrastyh.com/wc-api/{webhook name}/
, the hook name could be near anything, example paypal-payment-complete
or just misha
. It absolutely doesn’t mean what you will use here, the main requirement is that {webhook name} in the URL and {webhook name} in the filter (Step 3, line 54) must match.
add_action( 'woocommerce_api_{webhook name}', array( $this, 'webhook' ) );
And webhook()
is the function (class method) that can do anything with $_GET parameters received.
public function webhook() { $order = wc_get_order( $_GET['id'] ); $order->payment_complete(); $order->reduce_order_stock(); update_option('webhook_debug', $_GET); }
You do not have to add exit;
at the end because it exits WordPress anyway once the code inside it is fired.
That’s all for this tutorial.
- Have a question? – Welcome to comments.
- Need WooCommerce payment plugin development? – Contact me.
Comments — 53
Really helpful. Thanks
Great Stuff!!!
I have one question, When completing the payment on a custom payment website (POST data have been sent to the webhook function), how to redirect the user to the thank you page ?
Hey Joe,
Webhooks are sent independently from users, you have to redirect your users just after the completed payment.
It can be done in step 5.2, but if you use a payment gateway with its own checkout page, you have to send a specific redirect-to parameter to it.
Hi, can this be used to build one for World First? Thanks
Hi,
I didn’t have a chance to work with their API, but I think everything should be ok.
Thank you!
That really help me out!
Hi
Can it made to be base on country or currency so that it will only appear on the checkout in a particular country or currency.
Hi,
I think you could try to activate/deactivate specific payment gateways depending on a country selected on the checkout page. Example:
A little bit more about
woocommerce_available_payment_gateways
you read in this tutorial.thank you so much
Thanks for the guide, very helpful.
Quick question, Im trying to setup an action to receive webhook replies from my payment vendor (it happens at a random time later) so that i can go and update the orders.
Ive put in the action to the class construct :
I then have a function named the same:
When i call the url (site/payment_update) it calls the function successfully (i.e. i can echo things etc)….i can even retrieve GET data.
But if i POST to it…. the function receives nothing. var_dump($_POST) brings back array(0) { }.
Any idea how to tackle this? Is the post data being removed before perhaps?
Many Thanks.
Hey,
I’m not sure why it doesn’t work, but you can try
init
ortemplate_redirect
hooks.Hi Misha, thanks for the great guide!
However I’m not sure how to retrieve the CC details. I’m using direct payment without going to gateway websites.
I’ve checked the POST var in both validate_fields & process_payments, but only found [payment_method], but none of the CC details.
Thanks again!
Hi Ricky,
You can not retrieve card details, only a token.
Hey Misha,
In that case, how should I process the card details? The payment gateway I’m using requires the system to encrypt the card number, get months/year and store them into a csv file. These should be done in php.
This is out of the norm because the gateway allows flexible subscription periods, ie: 1st Jan, 5th Feb, 9th Mar and so on.
Hey,
Of course you have to discuss this stuff with your gateway support. If you need card details, and you are sure about that, just add
name
attributes to the form fields (step 4.3).Hey,
For 5.1 Validate Fields, why are we returning false if the field is not empty? Shouldn’t it be
instead?
Thanks,
Shaun.
Hey,
Yes, you are right. Thank you for your comment.
Hi,very nice tutorial by the way.
I am looking to build a Mobile Payment Woocommerce Extension plugin but can’t figure out how to replace the Credit card form with a simple form that will take a Phone number (number to be used for payment) and then accessing this number in the $args array for wp_remote_post().
Hi Boris,
Did you try Step 4?
Yeah, I did. Finally figured it out. Thanks
Hi Misha, your tutorial was very helpful.
But as a beginner with woocommerce and php i’m stuck, I created a simple offline payment gateway like COD and I added a field for change if the customer need, but i don’t know how to get the input data of this field.
Hi Matheus,
Where did you add that field?
Right here.
Did you try to get it like this
$_POST['troco']
inpayment_complete
hook?I did like this
and I tried to echo at thankyou_page as a test
but nothing happened
woocommerce_api_
is for webhooks, so you are using it incorrectly, just tryprocess_payment()
method which is part of the payment gateway class.I appreciate your help but I think its too much for me, I can’t figure out how to use process_payment() and this woocommerce/wp code.
Thank you so much anyway.
Hi
what about the security of your customized gateway, i mean if you start using your payment gateway for international payments on your website so how can you assure its save and the customer’s information will not be leaked?
Hey Zabi,
1. Use https://
2. Do not pass your customers card data to your server, only tokens
Everything else depends on your actual website security.
Hi,
Nice tutorial,
I’d like to check fields values in Admin gateway settings.
How to check fields values and return possible errors after saving the form ?
Thank you
Hey Dimitri,
Thank you!
You should create just one more payment class method:
Should be OK, but I didn’t test it though. So, please let me know if it works 🙃
Hi, it return correctly errors but the settings are never saved even if no errors are returned.
Ok, just need to add the following line before the conditions:
Thank you for your comment!
Hi Misha,
I am using your guide in order to add an iframe payment gateway.
I have completed step 3 and saw and the start of step 4 there should be a token.
I didn’t fully understand what this token is and where do I get it.
I’ll appreciate it if you could elaborate on it.
I’ll probably have some more questions afterwards, if its fine with you.
Thanks!
Hi Omer,
Token is necessary for a complete integration, when customers input card details directly on your website and that’s all.
Should your payment gateway work like this?
Hi Misha,
Thanks for this tutorial. How to add field type “image upload”? Instead of text & textarea, the customer as well can upload any attachment before clicking the “confirm order” button.
Hi,
Hmmm… It looks like a good idea for a new tutorial 🙃
Great! I’ll be the first to read hehe!
Hi !
Where I can send the payment process, to a Webcheckout Platform extern? For example for PayU latam the process is: Customer click to End Order in the Store, then this is redirection to page Payment Service Provider and then when the customer end the payment, the customer return to the store.
Thanks for the help !!
Hi Jaime,
I didn’t work with this platform.
Hi Misha!
Thank you very much for this great tutorial!
Thanks to you I’ve build my custom payment plugin using redirect to my custom payment gateway site (using
wp_remote_post()
first to send the order’s details).But instead of using redirect or just displaying a form for the user to enter
her credit card details I want to display a modal displaying content from my custom
payment gateway site in order to let the user select some options (a lot actually) in a better way by staying in the eshop’s checkout page. Can show a modal (with my gateway’s content either via ajax or iframe) after the user presses the button?
Best,
Spyros
Hi Spyros,
I didn’t try that out, but I think it is possible.
Hi Misha, I am also trying to show a modal with my gateways’ form instead of in the given box, May I also know how is it possible. I have been trying to figure it out too.
I am trying to test that my callback function works by visiting the callback URL in my browser http://localhost:90/wordpress-payment-demo/wc-api/callback_handler/ but I keep getting -1. Here is my callback function.
I also tried posting to the URL with Postman and I get a status code of 400 bad request.
Hey,
Is your
add_action
inside the constructor, right?Hi Misha,
Thank you for this great tutorial i’m using this as the payment gateway sends me a callback with both Paid/Failed Payments. The paid callback works fine and changes the order status to complete but the failed callback don’t know how to make it update my order status too to canceled when its being received from their server.
Hi,
I recommend you to debug it step by step, check every
if
condition.Thanks a lot for this tutorial. You just changed my life and that of my colleagues. We followed this and build a mobile money payment pligin for woocommerce. Works like charm. Your are best!
Maybe you should include how to add settings shortcut link on plugin that will appear beside deactivate.
Thanks!!!!!
You’re welcome, Lesly! 🙃
Here it is.
Really helpful. Thanks
Hi Misha really helpful tutorial. Thank you!
Hello Misha, can you add the boilerplate to download?
Comments are closed.