Need help with custom WooCommerce payment plugin development? Let’s talk →
Create a Payment Gateway Plugin for WooCommerce
I started this tutorial back there when I was visiting Stockholm but I am always trying to keep it up to to date with the latest WooCommerce versions. As well as a lot of custom payments gateways have been developed for the clients by me and my team since then and I am doing my best to reflect the gained experience in this guide.
Below you can find a super-detailed step by step for WooCommerce custom payment gateway development, so you can create your own.
On the other hand, if you face any difficulties in the process or you’re just looking for someone to develop a payment plugin for you, feel free to contact us.
Step 1. We begin with creating a plugin
In case you don’t know, custom payment methods are just plugins. So we have to create one.
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 Custom Payment Gateway
* Plugin URI: https://rudrastyh.com/woocommerce/payment-gateway-plugin.html
* Description: Take credit card payments on your store.
* Author: Misha Rudrastyh
* 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 which extend WC_Payment_Gateway, here is the class base
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’d 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 look like this:

If you need WooCommerce payment gateway development (integrating your payment processor API with WooCommerce), just let us know, me and my team are here to help 🙂
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', 'some payment processor site/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:
const successCallback = function( data ) {
const checkoutForm = $( 'form.woocommerce-checkout' )
// add a token to our hidden input field
// console.log(data) to find the token
checkoutForm.find( '#misha_token' ).val( data.token )
// deactivate the tokenRequest function event
checkoutForm.off( 'checkout_place_order', tokenRequest )
// submit the form now
checkoutForm.submit()
}
const errorCallback = function( data ) {
console.log( data )
}
const 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( $ ){
const checkoutForm = $( 'form.woocommerce-checkout' )
checkoutForm.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="#">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 for 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 ) {
// we need it to get any order detailes
$order = wc_get_order( $order_id );
/*
* Array with parameters for API interaction
*/
$args = array(
...
);
/*
* Your API integration can be built with wp_remote_post()
*/
$response = wp_remote_post( '{payment processor endpoint}', $args );
if( 200 === wp_remote_retrieve_response_code( $response ) ) {
$body = json_decode( wp_remote_retrieve_body( $response ), true );
// it could be different depending on your payment processor
if( 'APPROVED' === $body[ 'response' ][ 'responseCode' ] ) {
// 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
WC()->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.

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
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 integrate their API yet, 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
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,
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 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 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.
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?
Nice Content and clear explanation like poetry.
Thanks
Thanks for explaining it so simply
Merci Misha pour ce excellent document
Hello Misha, thanks for the great explanation and tutorial for WooCommerce Payment Gateway integration.
What about the refunds?
Couldn’t find a good example about this when I googled?
Any source link or any idea or any examples?
Many thanks.
Hi Fatih!
Sorry for a late reply.
First of all you have to specify the refunds support.
After that you can use a specific method:
Hi Misha,
This tutorial very very helpful. Thanks for that. I am creating token via javascript via payment processor sdk. Here i am facing the form get submitted before token updated in the form input. Is there way to delay and stop form submit in checkout in on submit event.
Hi,
event.preventDefault()
at the beginning of the event should help.Hi. Thanks for this fantastic article. It’s far better than any of the official documentation!
Say I have an old school sort of payment provider where I need to redirect the user rather than showing an iframe or capturing card details directly.
– Right now, I redirect the user to payment gateway
– Once payment is done, the payment gateway will redirect the user back to the Woocommerce checkout payment (with transaction id). How can I query the order in the page directly? The payment gateway will eventually trigger the webhook which is fine, but for better user experience, I’d like to query the order straight after the redirect. Any ideas?
Thanks once again!
Hi Misha,
Thank you for sharing the content on the payment plugin is awesome. Do you have any tutorials on payment token API? I am can statically add the content needed to pass the data to add a payment method for customers to save their card info via token but for the life of me can’t seems to figure out how to pass the data using the $token = new WC_Payment_Token_CC(); abstract class.
Many thanks
Hi John, You can create token like below code :
I am trying to integrate Windcave Payment Gateway. I followed your instructions above and it is working. Great article. Thanks.
But Windcave developer team has asked me to give them a notification url while creating a session on click of place order button. I created a notification url by following step 5.3. I am getting a callback from them with sessionId and username that means it is working. But the problem is how can I get the Order Id to complete the order.
Hello Misha, thanks for the great tutorial for WooCommerce Payment Gateway.
What about the subscription payment?
Couldn’t find a good example about this when I googled?
Any source link or any idea or any examples?
Thanks.
Hey,
Do you mean WooCommerce Subscriptions plugin, right?
Yes, Right Misha. Do you have any source link or any idea or any examples?
At this moment I have no tutorial to recommend you on this topic unfortunately. If you really need this functionality I could only suggest you my team, the guys already has experience with subscriptions and can develop it for you. If interested, just leave a message with details via contact form.
Amazing tutorial, thanks for this work
Very helpful tutorial not getting any error during implementation my getaway
Scripts don’t get Enqueued.
Hello ,
Does it have any compatibility issues with the latest woocommerce version ?
We are adding a custom payment gateway with external js sdk using your documentation, mounting the form at payment_fields() function which render the form as iframe.
The form is rendering but jQuery( document ).ready(function() that added inside this function is rendering twice on page load,
could you please assist us to prevent this duplication on triggering, we are not triggering the function from anywhere else, there are no conflict or error in logs too.
Thank you in advance for your valuable help.
Hey,
Everything should work great with the latest WooCommerce versions, I guess it must be something not right in your WordPress theme. Please send a message using the contact form, we’ll try to help you