There are lot’s of payment processor who provide there API for transaction but no ready plugin for woocommerce wordpress platform. For this case we need to create woocommerce custom payment gateway plugin. Let see how to create a woocommerce custom payment gateway plugin step by step.

Before start this tutorial , if you need any custom woocommerce plugin development or any woocommerce customization please contact me. I’ll be happy to help you.

Checkout All Steps Here –

  1. How to create a custom plugin
  2. Check that WooCommerce is active
  3. WC_Payment_Gateway class skeleton
  4. Add plugin option & init the form fields
  5. Credit/Debit card form setup on checkout page with validation
  6. Process payments
  7. Payment Gateway response show on order Received page and Email
  8. Payment Gateway Callback -Instant Payment Notifications , Automate order status update
  9. How to activate plugin & admin end, front end display

1st Step – How to create a custom plugin

1st we need to create a folder and name must be related with payment processor . Here I use woocommerce-a2zwp-payment-gateway . Under this folder I create a php file with same naming convention so the php file name is woocommerce-a2zwp-payment-gateway.php Here is the code snippet.

<?php
/**
 * Plugin Name:       WooCommerce A2zwp Payment Gateway
 * Plugin URI:        https://www.a2zwp.net/
 * Description:       Take card payments on your store
 * Version:           1.0.0
 * Author:            Arghya
 * Author URI:        https://www.a2zwp.net/
 */

After add this the plugin will appear in your admin area! And you can activate it.

2nd Step -Check WooCommerce is active or not

We need to check is there woocommerce plugin already active or not , because we are going to use woocommerce payment class so if there is no woocommerce than that class also missing, so error will come also can down your site so it’s very much need to check it before use anything woo extension. Check out the below code.

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

This is the most easiest way to check woocommerce plugin active or not .

3rd Step – WC_Payment_Gateway class skeleton

Here we need to create a custom PHP Class to extend WooCommerce WC_Payment_Gateway class.

1st we need to use a filter hook which add our custom gateway class with woocommerce gateway list for this we can use this woocommerce_payment_gateways filter .

add_filter( 'woocommerce_payment_gateways', 'a2zwp_add_gateway_class' );
function a2zwp_add_gateway_class( $gateways ) {
	$gateways[] = 'WC_a2zwp_Gateway'; // your class name is here
	return $gateways;
}

This hook plugins_loaded is called once any activated plugins have been loaded. So we need to load this plugin by using this action hook.

add_action( 'plugins_loaded', 'a2zwp_init_gateway_class' );

So a2zwp_init_gateway_class this is our plugin load function and we need to add our gateway class inside this function.

function a2zwp_init_gateway_class() {
    class WC_a2zwp_Gateway extends WC_Payment_Gateway {
         // Here will be all public function like
         // __construct(), init_form_fields(), payment_fields(), 
         //   payment_scripts(), process_payment( $order_id ) etc
    }
}

4th Step – Add plugin option & init the form fields

Most methods are inherited from the WC_Payment_Gateway class, but some are required in your custom gateway.

public function __construct() {
 
	$this->id = 'a2zwp_gateway'; // the unique ID for this gateway
	$this->icon = ''; // the link to the image displayed next to the method’s title on the checkout page — it's optional 
	$this->has_fields = true; // in case you need a custom credit card form, if simple offline gateway then should be false so the values are  true/false (bool).
	$this->method_title = 'A22zwp Gateway'; //the title of the payment method for the admin page
	$this->method_description = 'Description of A2zwp 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' ) );
 }

Init the Form Fields

We’ll need to create an init_form_fields() function to set up the form fields for our payment gateway.

public function init_form_fields(){
			$this->form_fields = array(
				'enabled' => array(
					'title' => 'Enable/Disable',
					'type' => 'checkbox',
					'label' =>'Enable Card Payment',
					'default' => 'no'
				),
				'title' => array(
					'title' => 'Title',
					'type' => 'text',
					'description' =>'This controls the title which the user sees during checkout.',
					'default' => 'Credit Card Payment'
				),
				'description' => array(
					'title' => 'Payment Description',
					'type' => 'textarea',
					'default' => 'Pay with credit card'
				),
				'instructions' => array(
					'title'       => 'Instructions',
					'type'        => 'textarea',
					'description' => 'Instructions that will be added to the thank you page and emails.',
					'default'     => '',
					'desc_tip'    => true,
				),
				'merNo' => array(
					'title' => 'Merchent No.',
					'type' => 'text',
					'description' =>'Please enter the merchant No.',
					'css' => 'width:400px'
				),
				'gatewayNo' => array(
					'title' => 'Gateway No.',
					'type' => 'text',
					'description' => 'Please enter the gateway No.',
					'css' => 'width:400px'
				),
				'secure_key' => array(
					'title' => 'Security Key',
					'type' => 'text',
					'description' => 'Please enter the security key',
					'description' => 'Please enter the security key',
					'css' => 'width:400px'
				),
				'url' => array(
					'title' => 'Submit Url',
					'type' => 'text',
					'label' => 'Submit Url.',
					'description' => 'Please enter the Submit Url.',
					'default' => ''
				),
				'return_url' => array(
					'title' => 'Return Url',
					'type' => 'text',
					'label' => 'Return Url.',
					'description' => 'Please enter the Return Url.',
					'default' => ''
				),
				'notify_url' => array(
					'title' => 'Notify Url',
					'type' => 'text',
					'label' => 'Neturn Url.',
					'description' =>'Please enter the Notify Url.',
					'default' =>''
				),

			);
        }
		

5th Step – Credit/Debit card form setup on checkout page with validation

Before start this part please check if you need this section or not , here is the list of payment gateway type –

  1. Form based – This is where the user must click a button on a form that then redirects them to the payment processor on the gateway’s own website.
  2. iFrame based – This is when the gateway payment system is loaded inside an iframe on your store.
  3. Direct – This is when the payment fields are shown directly on the checkout page and the payment is made when ‘place order’ is pressed. 
  4. Offline – No online payment is made. Like Cheque, Bank Transfer payment.

We need this section only for 3rd type that is Direct so the payment fields are shown directly on the checkout page .

public function payment_fields() {
    if ( $this->description ) {
      echo wpautop( wp_kses_post( $this->description ) );
    }
    $uniqid = uniqid();
    //If we need to pass any uniqid value to payment processor.
     ?>
    <input type="hidden" name="uniqueId" value="<?php echo $uniqid; ?>" />
    <p class="form-row form-row-first">
        <label for="a2zwp_card_number"><?php _e("Credit Card number", 'woothemes') ?> <span class="required">*</span></label>
	<input type="text" class="input-text" class="input-text wc-credit-card-form-card-number" placeholder="Card Number" name="a2zwp_card_number" maxlength="16" autocomplete="off" />
    </p>
    <p class="form-row form-row-first">
	<label for="cc-expire-month"><?php _e("Expiration date", 'woothemes') ?> <span class="required">*</span></label>
	<select class="input-text" name="a2zwp_card_expiration_month" id="cc-expire-month">
<?php
     for ($i = 1; $i < 13; $i++) {
	$MonthOp .="<option value='".sprintf('%02d',$i)."'>".sprintf('%02d', $i)."</option>";
     }
?>
	<option value=""> Month </option>
	<?php echo $MonthOp; ?>
	</select>
	<select class="input-text" name="a2zwp_card_expiration_year" id="cc-expire-year" c style="width:190px;padding-left:5px;">
<?php
      for ($y = 2020; $y < 2040; $y++) {
          $YearOp .="<option value='".sprintf('%02d', $y)."'>".sprintf('%02d', $y)."</option>";
      }
?>
	<option value=""> Year </option>
	<?php echo $YearOp; ?>
	</select>
  </p>
  <p class="form-row form-row-first">
	<label for="a2zwp_card_csc"><?php _e("Card security code", 'woothemes') ?><span class="required">*</span></label>
	<input type="password" maxlength="4" class="input-text wc-credit-card-form-card-cvc" id="a2zwp_card_csc" name="a2zwp_card_csc" style="width:190px" placeholder="CVC" autocomplete="off"/>
   </p>
<?php
}

This is for form validation , check how this custom card form validate

public function validate_fields(){
			global $woocommerce;

			$CardNo          =$_REQUEST['a2zwp_card_number'];
			$cvv		 =$_REQUEST['a2zwp_card_csc'];
			$expiresMonth  	 =$_REQUEST['a2zwp_card_expiration_month'];
			$expiresYear     =$_REQUEST['a2zwp_card_expiration_year'];
			$errorMsg = $this->validateCardInfo($CardNo,$cvv,$expiresYear,$expiresMonth);

			if(!empty($errorMsg) && strlen($errorMsg) > 1) {

				//$woocommerce->add_error($errorMsg);
				wc_add_notice( __($errorMsg, 'woocommerce' ), 'error' );
				return false;
			}
}

Here I use validateCardInfo as a sub function so now check this one

function validateCardInfo($cardNum,$cvv,$year,$month) {
			$errorMsg = "";
			$errorMsg = $this->validateCardNum($cardNum);

			if(!empty($errorMsg) && strlen($errorMsg)>1) {
				return $errorMsg;
			}

			$errorMsg = $this->validateCVV($cvv);
			if(!empty($errorMsg) && strlen($errorMsg)>1) {
				return $errorMsg;
			}

			$errorMsg = $this->validateExpiresDate($year,$month);
			if(!empty($errorMsg) && strlen($errorMsg)>1) {
				return $errorMsg;
			}
			return $errorMsg;
		}

There are 3 more sub function for this validation validateCardNum, validateCVV, validateExpiresDate

function validateCardNum($cardNum) {
			$msg = "";
			if(empty($cardNum) || !is_numeric($cardNum) || strlen($cardNum)<15 || strlen($cardNum)>16 ||
				!$this->card_check_by_luhn($cardNum)) {
				$msg = 'The <strong>credit card number</strong> is incorrect !';
			}
			return $msg;
		}
function card_check_by_luhn($cardNum){
			$str = '';
			foreach(array_reverse(str_split($cardNum)) as $i => $c) $str .= ($i % 2 ? $c * 2 : $c);
			return array_sum(str_split($str)) % 10 == 0;
		}
function validateCVV($cvv) {
			$msg = "";
			if(empty($cvv) || !is_numeric($cvv) || strlen($cvv)<3 || strlen($cvv)>4) {

				$msg = '<strong>CVV/CSC</strong> Code is incorrect !';
			}
			return $msg;
		}
function validateExpiresDate($year,$month) {
			$msg = "";
			if(empty($year) || !is_numeric($year) || strlen($year) !=4) {
				$msg = 'The <strong>year</strong> of expiry date is incorrect !';
			} else if(empty($month) || !is_numeric($month) || strlen($month) !=2 || $month < 1 || $month>12) {
				$msg = 'The <strong>month</strong> of expiry date is incorrect !';
			} else {
				$currentDate  = new DateTime(date("Y-m",time()));
				$inputDate    = new DateTime($year."-".$month);
				if($year<date("Y",time()) || $inputDate->format('U') < $currentDate->format('U')) {
					$msg = 'The <strong>expire date</strong> is expired!';
				}
			}
			return $msg;
		}

6th Step – Process payments

This is the one of the important part of the payment gateway because in this section we grab the order data and send them to payment processor endpoint. API interaction could be built with wp_remote_post().

public function process_payment( $order_id ) {
 
	global $woocommerce;
 
	// Grab all order data
	$order = wc_get_order( $order_id );
 
 
	/*
 	 * Array of payment info as per 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, Thank you! for order', 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;
	}
 
}

7th Step – Payment Gateway response show on order Received page and Email

Add payment gateway info to order received page and Emails

/**
 * Output for the order received page.
 */
public function thankyou_page() {
    if ( $this->instructions ) {
        echo wpautop( wptexturize( $this->instructions ) );
    }
}
/**
 * Add content to the WC emails.
 */
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;
    }
}

8th Step – Payment Gateway Callback -Instant Payment Notifications , Automate order status update

Some of the payment processor may not has any instant payment response method for that the order status is by default pending-payment we need to change it by custom notification url. So just add this notify URL to merchant account and they will hit the url when payment done.

The webshook URLs (callback URLs) in Woo look like this: https://a2zwp.net/wc-api/{webhook name}/

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);
}

9th Step – How to activate plugin & admin end, front end display

Just upload the plugin zip file and activate it .

plugin-list
woo-settings
custom-plugin
checkout-form

After all setup admin and checkout page card form looks like this , hope this tutorial will helps you create custom payment plugin . If anything is not clear or you are facing any issue please discuss in comment section.

To Download the whole code snippet as a plugin Click Here. Just download and install all setup automatically done thanks.

2 thoughts on “How to create a woocommerce custom payment gateway plugin

  1. Hi! Thank you very much for the article.
    I ask you, when an order placed and then for some reason is canceled, maybe for the customer ó admin, does this form of payment allow the stock to be returned automatically?

Leave a Reply

Your email address will not be published. Required fields are marked *