WordPress
Article

Building a WooCommerce Payment Extension

By Yojance Rabelo

Building a WooCommerce payment extension is a straightforward process for the most part. The hardest problem that you’ll probably encounter is learning how to interact with the merchant’s API to submit a payment and parse the response back. In this article, we are going to build the real thing: a working WooCommerce payment extension. Then, we’ll even connect it to a real payment processor, which we are going to submit payments to and parse the response.

WooCommerce Authorize.net

The Different Flavors of WooCommerce Payment Gateways

There are four different types of WooCommerce payment gateways which we can build to process payments on your store. Let’s begin by taking a look at the one we are going to be using in this article; the Direct method.

  • Direct — this payment method allows the payment fields to be shown directly on the checkout page, and the payment happens as soon as the ‘Place Order’ button is pressed. This type of gateway requires you to install an SSL certificate on your site and might also require you to be PCI compliant. Being PCI compliant is something your Merchant evaluates for you. A good example of this type of gateway is the Authorize.net AIM, which we will use later on, when we build our WooCommerce payment extension.

  • Form Based — in this method, the user must click a button on a form and then is redirected to the actual payment process website. Payment is then captured there; think PayPal Standard.

  • iFrame Based — through this method, the entire gateway payment system is loaded for you inside an iFrame on your store, kind of how PayPal Advanced works.

  • Offline – last but not least, the offline method is where no online payments are made at all. Cheque or Bank Transfers are good examples of this type of payment.

Why Use the Direct Method

The direct method allows for a greater level of customization since the payment fields are handled by you and, most importantly, the customer never leaves your website. The customer will greatly enjoy this experience over the other three options.

Methods Used in This Article

Chances are there is more than one way of doing this and the results might be identical. Any suggestions on how you accomplish this task or further improvements are always welcome and received well. Just be polite and informative in your response.

The following code has been tested in the latest versions available for WordPress (4.0) and WooCommerce (2.2.2) and using the dummy data provided for WooCommerce when you install the plugin.

Obtaining an Authorize.Net Developer Account

If you don’t already have a developer/test account from Authorize.net, you’ll need one to fully complete and test this plugin. To obtain an Authorize.net developer/test or sandbox account (as they describe it), just go to their Developer Center and fill out the form.

The developer sandbox is a fully functioning Authorize.Net payment gateway supporting the current API methods. The sandbox functions like our production environment. However, there are two key differences:

  1. No actual card processing is performed. The sandbox only simulates connections to the card networks, but no actual card payments are processed.

  2. Developers can trigger specific error conditions and transaction responses to aid in testing.

Planning Our WooCommerce Payment Extension Plugin

In order to make this easier to read and follow, we are going to separate our plugin into two different files. One of the files will register our Payment Gateway and custom action link. The other will be a simple Class, which will contain the methods that are going to be responsible for creating the administration fields, handling the payment processing and field validation, and displaying an error message if you are not forcing SSL on the checkout page. Take a look below at our file structure:

woocommerce-authorizenet-aim-gateway (folder)

-- woocommerce-authorizenet-aim-gateway.php

-- woocommerce-authorizenet-aim.php

Building Our Plugin

Now that we have a clear understanding of how our plugin will be set-up and which files do what, we can begin by writing the bare-bones of our entire plugin; the basic code which we will expand on until the plugin is completed.

<?php
/*
Plugin Name: Authorize.net AIM - WooCommerce Gateway
Plugin URI: http://www.sitepoint.com/
Description: Extends WooCommerce by Adding the Authorize.net AIM Gateway.
Version: 1
Author: Yojance Rabelo, SitePoint
Author URI: http://www.sitepoint.com/
*/

// Include our Gateway Class and Register Payment Gateway with WooCommerce
add_action( 'plugins_loaded', 'spyr_authorizenet_aim_init', 0 );
function spyr_authorizenet_aim_init() {}


// Add custom action links
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'spyr_authorizenet_aim_action_links' );
function spyr_authorizenet_aim_action_links( $links ) {}

We can now see our plugin on the Plugins page but it doesn’t do anything yet. We are not ready to activate it just yet.

Plugin Extension Payment Page

Let’s begin with the first file: include the PHP Class, register this new Payment Gateway with WooCommerce, and add your action links so that the customer can go to the settings page as soon as they activate the plugin.

<?php
/*
Plugin Name: Authorize.net AIM - WooCommerce Gateway
Plugin URI: http://www.sitepoint.com/
Description: Extends WooCommerce by Adding the Authorize.net AIM Gateway.
Version: 1.0
Author: Yojance Rabelo, SitePoint
Author URI: http://www.sitepoint.com/
*/

// Include our Gateway Class and register Payment Gateway with WooCommerce
add_action( 'plugins_loaded', 'spyr_authorizenet_aim_init', 0 );
function spyr_authorizenet_aim_init() {
	// If the parent WC_Payment_Gateway class doesn't exist
	// it means WooCommerce is not installed on the site
	// so do nothing
	if ( ! class_exists( 'WC_Payment_Gateway' ) ) return;
	
	// If we made it this far, then include our Gateway Class
	include_once( 'woocommerce-authorizenet-aim.php' );

	// Now that we have successfully included our class,
	// Lets add it too WooCommerce
	add_filter( 'woocommerce_payment_gateways', 'spyr_add_authorizenet_aim_gateway' );
	function spyr_add_authorizenet_aim_gateway( $methods ) {
		$methods[] = 'SPYR_AuthorizeNet_AIM';
		return $methods;
	}
}

// Add custom action links
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'spyr_authorizenet_aim_action_links' );
function spyr_authorizenet_aim_action_links( $links ) {
	$plugin_links = array(
		'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=checkout' ) . '">' . __( 'Settings', 'spyr-authorizenet-aim' ) . '</a>',
	);

	// Merge our new link with the default ones
	return array_merge( $plugin_links, $links );	
}

Note that the action links, in this case the ‘Settings’ link, will only show when the plugin has been activated. This will lead the customer to the WooCommerce checkout administration page. Let’s continue on so that we are one step closer to activating the plugin, and viewing at least some default fields.

Setting Up Our Class Constructor

Now that we are going to begin working on our actual Payment Class, we have to start by setting some default values for this Payment Gateway. Some of the settings we’ll define:

  • id — The global ID for this Payment method.

  • method_title — The Title shown on the top of the Payment Gateways Page next to all the other Payment Gateways.

  • method_description — The description for this Payment Gateway, shown on the actual Payment options page on the backend.

  • title — The title to be used for the vertical tabs that can be ordered from top to bottom.

  • icon — If you want to show an image next to the gateway’s name on the front-end. URL to an image.

  • has_fields — Bool. Can be set to ‘true’ if you want payment fields to show on the checkout if doing a direct integration, which we are doing in this case.

  • supports — Supports the default credit card form. More on this later on. At this stage, you just need to know that it is super cool.

Expressing this on our constructor is next, along with some other useful functions that we need in order to make everything work in harmony. Comments will help you understand the purpose, and why it’s there.

// Setup our Gateway's id, description and other values
function __construct() {
	
	// The global ID for this Payment method
	$this->id = "spyr_authorizenet_aim";

	// The Title shown on the top of the Payment Gateways Page next to all the other Payment Gateways
	$this->method_title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

	// The description for this Payment Gateway, shown on the actual Payment options page on the backend
	$this->method_description = __( "Authorize.net AIM Payment Gateway Plug-in for WooCommerce", 'spyr-authorizenet-aim' );

	// The title to be used for the vertical tabs that can be ordered top to bottom
	$this->title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

	// If you want to show an image next to the gateway's name on the frontend, enter a URL to an image.
	$this->icon = null;

	// Bool. Can be set to true if you want payment fields to show on the checkout 
	// if doing a direct integration, which we are doing in this case
	$this->has_fields = true;

	// Supports the default credit card form
	$this->supports = array( 'default_credit_card_form' );

	// This basically defines your settings which are then loaded with init_settings()
	$this->init_form_fields();

	// After init_settings() is called, you can get the settings and load them into variables, e.g:
	// $this->title = $this->get_option( 'title' );
	$this->init_settings();
	
	// Turn these settings into variables we can use
	foreach ( $this->settings as $setting_key => $value ) {
		$this->$setting_key = $value;
	}
	
	// Lets check for SSL
	add_action( 'admin_notices', array( $this,	'do_ssl_check' ) );
	
	// Save settings
	if ( is_admin() ) {
		// Versions over 2.0
		// Save our administration options. Since we are not going to be doing anything special
		// we have not defined 'process_admin_options' in this class so the method in the parent
		// class will be used instead
		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
	}		
} // End __construct()

Activate Your Plugin

Go ahead and activate your plugin. Make sure that there are no errors thrown and click on the new ‘Settings’ link. This will take you to the Checkout administration options. You should now see your newly registered payment extension there.

Checkout Page 1

Scroll to the bottom of the page and you’ll be able to see it there, too.

Checkout Page 2

Building the Administration Page

In order to build the administration page, we need to know what fields are required by WooCommerce itself, and what fields are required by the payment processor’s API. In our case, since we are using the Authorize.net AIM, we just need the Authorize.net API login and Transaction Key.

Since we are using our testing account to build the plugin, it’s also a good idea to include an option to set the Gateway in test mode. Regardless of what you are doing, providing a way to easily turn the Gateway into test mode is very useful, and you should consider making it a requirement when you build your gateway. Most gateways will provide you with two different links to which you can send the transactions: one for testing purposes, and the other for a production environment.

After some brainstorming, this is what I came up with for the fields that we are going to include in our administration page:

  • enabled — Enable/Disable this payment gateway.

  • title — Payment title that the customer will see during the checkout process.

  • description — Payment description that the customer will see during the checkout process.

  • api_login — API Login provided by Authorize.net when you signed up for an account.

  • trans_key — Transaction Key provided by Authorize.net when you signed up for an account.

  • environment — Place the payment gateway in test mode or production.

Let’s turn these into code that WooCommerce understands, and will parse into options which will be rendered on the back-end. This is where init_form_fields() comes into play.

// Build the administration fields for this specific Gateway
public function init_form_fields() {
	$this->form_fields = array(
		'enabled' => array(
			'title'		=> __( 'Enable / Disable', 'spyr-authorizenet-aim' ),
			'label'		=> __( 'Enable this payment gateway', 'spyr-authorizenet-aim' ),
			'type'		=> 'checkbox',
			'default'	=> 'no',
		),
		'title' => array(
			'title'		=> __( 'Title', 'spyr-authorizenet-aim' ),
			'type'		=> 'text',
			'desc_tip'	=> __( 'Payment title the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
			'default'	=> __( 'Credit card', 'spyr-authorizenet-aim' ),
		),
		'description' => array(
			'title'		=> __( 'Description', 'spyr-authorizenet-aim' ),
			'type'		=> 'textarea',
			'desc_tip'	=> __( 'Payment description the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
			'default'	=> __( 'Pay securely using your credit card.', 'spyr-authorizenet-aim' ),
			'css'		=> 'max-width:350px;'
		),
		'api_login' => array(
			'title'		=> __( 'Authorize.net API Login', 'spyr-authorizenet-aim' ),
			'type'		=> 'text',
			'desc_tip'	=> __( 'This is the API Login provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
		),
		'trans_key' => array(
			'title'		=> __( 'Authorize.net Transaction Key', 'spyr-authorizenet-aim' ),
			'type'		=> 'password',
			'desc_tip'	=> __( 'This is the Transaction Key provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
		),
		'environment' => array(
			'title'		=> __( 'Authorize.net Test Mode', 'spyr-authorizenet-aim' ),
			'label'		=> __( 'Enable Test Mode', 'spyr-authorizenet-aim' ),
			'type'		=> 'checkbox',
			'description' => __( 'Place the payment gateway in test mode.', 'spyr-authorizenet-aim' ),
			'default'	=> 'no',
		)
	);		
}

If you visit the Authorize.net AIM administration page now, your fields will display using the native WooCommerce template system for display administration options.

Admin Options

Fill Out The Administration Options

Go ahead and fill out the administration option with your credentials. Don’t forget to set the gateway to Test mode so that we can work safely and test all of our code as we go. Press that ‘Save Changes’ button before proceeding.

Processing Payments

The payment processing is done via process_payment($order_id ). It is important to point out that it gets the current order passed to it so we can get the values we need. The credit card fields can be obtained from $_POST.

Many gateway plugins, including ours, use a similar interface for card payment fields in the checkout form so WooCommerce added a form for us to use when needed. This default form has automatic formatting for credit card numbers, expiration dates and it even displays automatically the type of card being used as soon as the customer enters the first few digits of their credit card number. To be able to use this form, make sure your site is running with at least version 2.1 of WooCommerce.

By default, the form will create the following form fields which can be obtained from $_POST:

  • GATEWAYID-card-number

  • GATEWAYID-card-cvc

  • GATEWAYID-card-expiry

Here is an example of how the default form looks without any custom CSS.

Default Credit Card Form

Now that we know this, we can proceed to the next step and build the process_payment() function to handle the transaction.

// Submit payment and handle response
public function process_payment( $order_id ) {
	global $woocommerce;
	
	// Get this Order's information so that we know
	// who to charge and how much
	$customer_order = new WC_Order( $order_id );
	
	// Are we testing right now or is it a real transaction
	$environment = ( $this->environment == "yes" ) ? 'TRUE' : 'FALSE';

	// Decide which URL to post to
	$environment_url = ( "FALSE" == $environment ) 
					   ? 'https://secure.authorize.net/gateway/transact.dll'
					   : 'https://test.authorize.net/gateway/transact.dll';

	// This is where the fun stuff begins
	$payload = array(
		// Authorize.net Credentials and API Info
		"x_tran_key"           	=> $this->trans_key,
		"x_login"              	=> $this->api_login,
		"x_version"            	=> "3.1",
		
		// Order total
		"x_amount"             	=> $customer_order->order_total,
		
		// Credit Card Information
		"x_card_num"           	=> str_replace( array(' ', '-' ), '', $_POST['spyr_authorizenet_aim-card-number'] ),
		"x_card_code"          	=> ( isset( $_POST['spyr_authorizenet_aim-card-cvc'] ) ) ? $_POST['spyr_authorizenet_aim-card-cvc'] : '',
		"x_exp_date"           	=> str_replace( array( '/', ' '), '', $_POST['spyr_authorizenet_aim-card-expiry'] ),
		
		"x_type"               	=> 'AUTH_CAPTURE',
		"x_invoice_num"        	=> str_replace( "#", "", $customer_order->get_order_number() ),
		"x_test_request"       	=> $environment,
		"x_delim_char"         	=> '|',
		"x_encap_char"         	=> '',
		"x_delim_data"         	=> "TRUE",
		"x_relay_response"     	=> "FALSE",
		"x_method"             	=> "CC",
		
		// Billing Information
		"x_first_name"         	=> $customer_order->billing_first_name,
		"x_last_name"          	=> $customer_order->billing_last_name,
		"x_address"            	=> $customer_order->billing_address_1,
		"x_city"              	=> $customer_order->billing_city,
		"x_state"              	=> $customer_order->billing_state,
		"x_zip"                	=> $customer_order->billing_postcode,
		"x_country"            	=> $customer_order->billing_country,
		"x_phone"              	=> $customer_order->billing_phone,
		"x_email"              	=> $customer_order->billing_email,
		
		// Shipping Information
		"x_ship_to_first_name" 	=> $customer_order->shipping_first_name,
		"x_ship_to_last_name"  	=> $customer_order->shipping_last_name,
		"x_ship_to_company"    	=> $customer_order->shipping_company,
		"x_ship_to_address"    	=> $customer_order->shipping_address_1,
		"x_ship_to_city"       	=> $customer_order->shipping_city,
		"x_ship_to_country"    	=> $customer_order->shipping_country,
		"x_ship_to_state"      	=> $customer_order->shipping_state,
		"x_ship_to_zip"        	=> $customer_order->shipping_postcode,
		
		// Some Customer Information
		"x_cust_id"            	=> $customer_order->user_id,
		"x_customer_ip"        	=> $_SERVER['REMOTE_ADDR'],
		
	);

	// Send this payload to Authorize.net for processing
	$response = wp_remote_post( $environment_url, array(
		'method'    => 'POST',
		'body'      => http_build_query( $payload ),
		'timeout'   => 90,
		'sslverify' => false,
	) );

	if ( is_wp_error( $response ) ) 
		throw new Exception( __( 'We are currently experiencing problems trying to connect to this payment gateway. Sorry for the inconvenience.', 'spyr-authorizenet-aim' ) );

	if ( empty( $response['body'] ) )
		throw new Exception( __( 'Authorize.net\'s Response was empty.', 'spyr-authorizenet-aim' ) );
		
	// Retrieve the body's resopnse if no errors found
	$response_body = wp_remote_retrieve_body( $response );

	// Parse the response into something we can read
	foreach ( preg_split( "/\r?\n/", $response_body ) as $line ) {
		$resp = explode( "|", $line );
	}

	// Get the values we need
	$r['response_code']             = $resp[0];
	$r['response_sub_code']         = $resp[1];
	$r['response_reason_code']      = $resp[2];
	$r['response_reason_text']      = $resp[3];

	// Test the code to know if the transaction went through or not.
	// 1 or 4 means the transaction was a success
	if ( ( $r['response_code'] == 1 ) || ( $r['response_code'] == 4 ) ) {
		// Payment has been successful
		$customer_order->add_order_note( __( 'Authorize.net payment completed.', 'spyr-authorizenet-aim' ) );
											 
		// Mark order as Paid
		$customer_order->payment_complete();

		// Empty the cart (Very important step)
		$woocommerce->cart->empty_cart();

		// Redirect to thank you page
		return array(
			'result'   => 'success',
			'redirect' => $this->get_return_url( $customer_order ),
		);
	} else {
		// Transaction was not succesful
		// Add notice to the cart
		wc_add_notice( $r['response_reason_text'], 'error' );
		// Add note to the order for your reference
		$customer_order->add_order_note( 'Error: '. $r['response_reason_text'] );
	}

}

Let’s break down the code even more so that it’s easier to understand.

Lines 5-15

We assign $customer_order a new WC_Order holding all the information from the order for which we are about to process payment. Then we decide whether or not we are in test mode, and finally chose the right URL for the transaction.

Lines 18-66

These are arbitrary fields that we can send Authorize.net so that they can record this information. We build it into an array so that we can build a proper query using http_build_query() later on.

Lines 69-74

Since we are using WordPress, we can utilize all the HTTP API to interact with other sites and, in this case, POST our payload for processing on Authorize.net. Follow this link for more on the HTTP API and wp_remote_post().

Lines 76-80

Two important checks happen here. First we make sure WordPress didn’t encounter any errors while POSTing our payload and secondly; we make sure that we got a response back. At any point, we throw an exception error letting the customer know if there is something wrong with the transaction. Take a look below at an example of one of these errors:

Exception Error

Lines 83-94

If we’ve made it this far without any errors, then we can retrieve the response’s body and parse it into something we can read. The first four values in the response are the only ones we need. We add these four values to an array for easier testing and also to make it much simpler to read.

Lines 98-112

Test the response_code value. Codes 1 or 4 mean that the transaction has been approved. In the event that the transaction is approved, we add a friendly order note for your reference, mark the order as paid, empty the cart’s contents, and finally redirect the customer to the ‘Thank You’ page. If you are curious about seeing how the order notes look after doing these four steps, take a look below:

Success Order Notes

Lines 114-118

In case that the transaction is not successful, we display an error notice to the customer and update the order with the error received from Authorize.net. Here is an example of the order notes on the back-end after the customer has tried a couple of times to complete the transaction without success.

Error Order Notes

That’s it for the payment processing.

Validate Submitted Fields

We are not doing any kind of field validation since we are relying on the default credit card form. Try to enter letters or symbols in any field and you’ll see it’s just not possible. In some cases, I can see how this would be extremely useful, for example; for validating expiration dates which would have to be in a specific format other than the standard we are mostly accustomed to seeing.

For the sake of simplicity in this article, we are just going to return ‘true’ and bypass the field validation.

// Validate fields
public function validate_fields() {
	return true;
}

Do Checkout Pages Use SSL?

Part of being able to provide a seamless checkout experience to the customer and allowing them to checkout on your own site requires the use of an SSL certificate. WooCommerce allows you to quickly enforce the use of SSL only on checkout pages by checking the ‘Force secure checkout’ under WooCommerce -> Settings -> Checkout.

We are just going to check whether or not this has been checked. If it has not been checked, then we are going to display an error on the back-end, warning you that an SSL is required.

// Check if we are forcing SSL on checkout pages
// Custom function not required by the Gateway
public function do_ssl_check() {
	if( $this->enabled == "yes" ) {
		if( get_option( 'woocommerce_force_ssl_checkout' ) == "no" ) {
			echo "<div class=\"error\"><p>". sprintf( __( "<strong>%s</strong> is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are <a href=\"%s\">forcing the checkout pages to be secured.</a>" ), $this->method_title, admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) ."</p></div>";	
		}
	}		
}

Assuming that we are not forcing a secure checkout, this message is displayed in the back-end.

Do SSL Check

If you have made it this far, give yourself a pat on the back: you’ve just built a WooCommerce Payment Gateway by following this guide.

What’s Next?

Challenge yourself. Try to create another payment gateway by following this article and applying the same techniques to your plugin. If I have to make a recommendation, I would recommend trying to build your own extension for Simplify Commerce. This is now bundled into WooCommerce so don’t be afraid to take a peek at that code.

Simplify Commerce has a great API, SDKs for your to use, and is very well documented. Also, signing up for a test account takes just minutes and you can be on your way to writing your next plugin in a heart beat.

Conclusion

As you can see, writing the WooCommerce payment extension was not as hard as you thought it would be. With a little practice, you can write a new one in a matter of hours, just remember to do a lot of testing before you release it to the public, or use it on any production environment. Develop with WP_DEBUG set to true so you can get extra feedback when needed.

The Plugin Code

Below you’ll find the sample code used in this article it the complete form. Please comment below if you have any questions or comments.

WooCommerce Authorize.net AIM

<?php
/* Authorize.net AIM Payment Gateway Class */
class SPYR_AuthorizeNet_AIM extends WC_Payment_Gateway {

	// Setup our Gateway's id, description and other values
	function __construct() {

		// The global ID for this Payment method
		$this->id = "spyr_authorizenet_aim";

		// The Title shown on the top of the Payment Gateways Page next to all the other Payment Gateways
		$this->method_title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

		// The description for this Payment Gateway, shown on the actual Payment options page on the backend
		$this->method_description = __( "Authorize.net AIM Payment Gateway Plug-in for WooCommerce", 'spyr-authorizenet-aim' );

		// The title to be used for the vertical tabs that can be ordered top to bottom
		$this->title = __( "Authorize.net AIM", 'spyr-authorizenet-aim' );

		// If you want to show an image next to the gateway's name on the frontend, enter a URL to an image.
		$this->icon = null;

		// Bool. Can be set to true if you want payment fields to show on the checkout 
		// if doing a direct integration, which we are doing in this case
		$this->has_fields = true;

		// Supports the default credit card form
		$this->supports = array( 'default_credit_card_form' );

		// This basically defines your settings which are then loaded with init_settings()
		$this->init_form_fields();

		// After init_settings() is called, you can get the settings and load them into variables, e.g:
		// $this->title = $this->get_option( 'title' );
		$this->init_settings();
		
		// Turn these settings into variables we can use
		foreach ( $this->settings as $setting_key => $value ) {
			$this->$setting_key = $value;
		}
		
		// Lets check for SSL
		add_action( 'admin_notices', array( $this,	'do_ssl_check' ) );
		
		// Save settings
		if ( is_admin() ) {
			// Versions over 2.0
			// Save our administration options. Since we are not going to be doing anything special
			// we have not defined 'process_admin_options' in this class so the method in the parent
			// class will be used instead
			add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
		}		
	} // End __construct()

	// Build the administration fields for this specific Gateway
	public function init_form_fields() {
		$this->form_fields = array(
			'enabled' => array(
				'title'		=> __( 'Enable / Disable', 'spyr-authorizenet-aim' ),
				'label'		=> __( 'Enable this payment gateway', 'spyr-authorizenet-aim' ),
				'type'		=> 'checkbox',
				'default'	=> 'no',
			),
			'title' => array(
				'title'		=> __( 'Title', 'spyr-authorizenet-aim' ),
				'type'		=> 'text',
				'desc_tip'	=> __( 'Payment title the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
				'default'	=> __( 'Credit card', 'spyr-authorizenet-aim' ),
			),
			'description' => array(
				'title'		=> __( 'Description', 'spyr-authorizenet-aim' ),
				'type'		=> 'textarea',
				'desc_tip'	=> __( 'Payment description the customer will see during the checkout process.', 'spyr-authorizenet-aim' ),
				'default'	=> __( 'Pay securely using your credit card.', 'spyr-authorizenet-aim' ),
				'css'		=> 'max-width:350px;'
			),
			'api_login' => array(
				'title'		=> __( 'Authorize.net API Login', 'spyr-authorizenet-aim' ),
				'type'		=> 'text',
				'desc_tip'	=> __( 'This is the API Login provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
			),
			'trans_key' => array(
				'title'		=> __( 'Authorize.net Transaction Key', 'spyr-authorizenet-aim' ),
				'type'		=> 'password',
				'desc_tip'	=> __( 'This is the Transaction Key provided by Authorize.net when you signed up for an account.', 'spyr-authorizenet-aim' ),
			),
			'environment' => array(
				'title'		=> __( 'Authorize.net Test Mode', 'spyr-authorizenet-aim' ),
				'label'		=> __( 'Enable Test Mode', 'spyr-authorizenet-aim' ),
				'type'		=> 'checkbox',
				'description' => __( 'Place the payment gateway in test mode.', 'spyr-authorizenet-aim' ),
				'default'	=> 'no',
			)
		);		
	}
	
	// Submit payment and handle response
	public function process_payment( $order_id ) {
		global $woocommerce;
		
		// Get this Order's information so that we know
		// who to charge and how much
		$customer_order = new WC_Order( $order_id );
		
		// Are we testing right now or is it a real transaction
		$environment = ( $this->environment == "yes" ) ? 'TRUE' : 'FALSE';

		// Decide which URL to post to
		$environment_url = ( "FALSE" == $environment ) 
						   ? 'https://secure.authorize.net/gateway/transact.dll'
						   : 'https://test.authorize.net/gateway/transact.dll';

		// This is where the fun stuff begins
		$payload = array(
			// Authorize.net Credentials and API Info
			"x_tran_key"           	=> $this->trans_key,
			"x_login"              	=> $this->api_login,
			"x_version"            	=> "3.1",
			
			// Order total
			"x_amount"             	=> $customer_order->order_total,
			
			// Credit Card Information
			"x_card_num"           	=> str_replace( array(' ', '-' ), '', $_POST['spyr_authorizenet_aim-card-number'] ),
			"x_card_code"          	=> ( isset( $_POST['spyr_authorizenet_aim-card-cvc'] ) ) ? $_POST['spyr_authorizenet_aim-card-cvc'] : '',
			"x_exp_date"           	=> str_replace( array( '/', ' '), '', $_POST['spyr_authorizenet_aim-card-expiry'] ),
			
			"x_type"               	=> 'AUTH_CAPTURE',
			"x_invoice_num"        	=> str_replace( "#", "", $customer_order->get_order_number() ),
			"x_test_request"       	=> $environment,
			"x_delim_char"         	=> '|',
			"x_encap_char"         	=> '',
			"x_delim_data"         	=> "TRUE",
			"x_relay_response"     	=> "FALSE",
			"x_method"             	=> "CC",
			
			// Billing Information
			"x_first_name"         	=> $customer_order->billing_first_name,
			"x_last_name"          	=> $customer_order->billing_last_name,
			"x_address"            	=> $customer_order->billing_address_1,
			"x_city"              	=> $customer_order->billing_city,
			"x_state"              	=> $customer_order->billing_state,
			"x_zip"                	=> $customer_order->billing_postcode,
			"x_country"            	=> $customer_order->billing_country,
			"x_phone"              	=> $customer_order->billing_phone,
			"x_email"              	=> $customer_order->billing_email,
			
			// Shipping Information
			"x_ship_to_first_name" 	=> $customer_order->shipping_first_name,
			"x_ship_to_last_name"  	=> $customer_order->shipping_last_name,
			"x_ship_to_company"    	=> $customer_order->shipping_company,
			"x_ship_to_address"    	=> $customer_order->shipping_address_1,
			"x_ship_to_city"       	=> $customer_order->shipping_city,
			"x_ship_to_country"    	=> $customer_order->shipping_country,
			"x_ship_to_state"      	=> $customer_order->shipping_state,
			"x_ship_to_zip"        	=> $customer_order->shipping_postcode,
			
			// Some Customer Information
			"x_cust_id"            	=> $customer_order->user_id,
			"x_customer_ip"        	=> $_SERVER['REMOTE_ADDR'],
			
		);
	
		// Send this payload to Authorize.net for processing
		$response = wp_remote_post( $environment_url, array(
			'method'    => 'POST',
			'body'      => http_build_query( $payload ),
			'timeout'   => 90,
			'sslverify' => false,
		) );

		if ( is_wp_error( $response ) ) 
			throw new Exception( __( 'We are currently experiencing problems trying to connect to this payment gateway. Sorry for the inconvenience.', 'spyr-authorizenet-aim' ) );

		if ( empty( $response['body'] ) )
			throw new Exception( __( 'Authorize.net\'s Response was empty.', 'spyr-authorizenet-aim' ) );
			
		// Retrieve the body's resopnse if no errors found
		$response_body = wp_remote_retrieve_body( $response );

		// Parse the response into something we can read
		foreach ( preg_split( "/\r?\n/", $response_body ) as $line ) {
			$resp = explode( "|", $line );
		}

		// Get the values we need
		$r['response_code']             = $resp[0];
		$r['response_sub_code']         = $resp[1];
		$r['response_reason_code']      = $resp[2];
		$r['response_reason_text']      = $resp[3];

		// Test the code to know if the transaction went through or not.
		// 1 or 4 means the transaction was a success
		if ( ( $r['response_code'] == 1 ) || ( $r['response_code'] == 4 ) ) {
			// Payment has been successful
			$customer_order->add_order_note( __( 'Authorize.net payment completed.', 'spyr-authorizenet-aim' ) );
												 
			// Mark order as Paid
			$customer_order->payment_complete();

			// Empty the cart (Very important step)
			$woocommerce->cart->empty_cart();

			// Redirect to thank you page
			return array(
				'result'   => 'success',
				'redirect' => $this->get_return_url( $customer_order ),
			);
		} else {
			// Transaction was not succesful
			// Add notice to the cart
			wc_add_notice( $r['response_reason_text'], 'error' );
			// Add note to the order for your reference
			$customer_order->add_order_note( 'Error: '. $r['response_reason_text'] );
		}

	}
	
	// Validate fields
	public function validate_fields() {
		return true;
	}
	
	// Check if we are forcing SSL on checkout pages
	// Custom function not required by the Gateway
	public function do_ssl_check() {
		if( $this->enabled == "yes" ) {
			if( get_option( 'woocommerce_force_ssl_checkout' ) == "no" ) {
				echo "<div class=\"error\"><p>". sprintf( __( "<strong>%s</strong> is enabled and WooCommerce is not forcing the SSL certificate on your checkout page. Please ensure that you have a valid SSL certificate and that you are <a href=\"%s\">forcing the checkout pages to be secured.</a>" ), $this->method_title, admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) ."</p></div>";	
			}
		}		
	}

} // End of SPYR_AuthorizeNet_AIM

WooCommerce Authorize.net AIM Gateway

<?php
/*
Plugin Name: Authorize.net AIM - WooCommerce Gateway
Plugin URI: http://www.sitepoint.com/
Description: Extends WooCommerce by Adding the Authorize.net AIM Gateway.
Version: 1.0
Author: Yojance Rabelo, SitePoint
Author URI: http://www.sitepoint.com/
*/

// Include our Gateway Class and register Payment Gateway with WooCommerce
add_action( 'plugins_loaded', 'spyr_authorizenet_aim_init', 0 );
function spyr_authorizenet_aim_init() {
	// If the parent WC_Payment_Gateway class doesn't exist
	// it means WooCommerce is not installed on the site
	// so do nothing
	if ( ! class_exists( 'WC_Payment_Gateway' ) ) return;
	
	// If we made it this far, then include our Gateway Class
	include_once( 'woocommerce-authorizenet-aim.php' );

	// Now that we have successfully included our class,
	// Lets add it too WooCommerce
	add_filter( 'woocommerce_payment_gateways', 'spyr_add_authorizenet_aim_gateway' );
	function spyr_add_authorizenet_aim_gateway( $methods ) {
		$methods[] = 'SPYR_AuthorizeNet_AIM';
		return $methods;
	}
}

// Add custom action links
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'spyr_authorizenet_aim_action_links' );
function spyr_authorizenet_aim_action_links( $links ) {
	$plugin_links = array(
		'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=checkout' ) . '">' . __( 'Settings', 'spyr-authorizenet-aim' ) . '</a>',
	);

	// Merge our new link with the default ones
	return array_merge( $plugin_links, $links );	
}
  • Richard

    Nice work. However, instead of using Test Mode, you should offer your user the option to connect to either the Sandbox or the Production gateway. You’ll also need to store two sets of API Login and Transaction Keys as they don’t work interchangeably.

    Setting Test Mode = True would only confirm if the API Login and Transaction Key are correct, and nothing more. It would not attempt to process a transaction in the sandbox.

    • http://yojance.net Yojance Rabelo

      Excellent suggestion, Richard.

  • tudoutou

    Wow, such a detailed tutorial! I would try building one myself.

  • niikkyysaddy

    hello there thanks for this tutorial, i am trying to write a woo commerce plugin for the new BrainTree Zero payment gateway. This tutorial helped a lot!

  • http://www.prabinparajuli.blogspot.com prabin04

    nice tutorial but it shows
    “Fatal error: Class ‘SPYR_AuthorizeNet_AIM’ not found in /var/www/storefront/wp-content/plugins/woocommerce/includes/class-wc-payment-gateways.php on line 104” when i activate the plugin and go to woocommerce setting in dashboard !! any help ?

    • Thoms

      Having same problem here. Woocommerce v. 2.3.3.

    • Der

      I too, have this issue. I am trying to write a gateway plugin for Square, but the tutorial did not specify where to add the class constructor codes… do I make a new file in the woocommerce includes folder?

      • Carla

        Did you find a solution for Square? I am searching for a plugin for that.

        • Der

          Hi Carla, this was the best thing I could find:
          Plugin called
          WooCommerce Offline Credit Card Processing

          it works beautifully, except if the person inputs their expiry or CVC code wrong…then you have call them and process over the phone… also, it’s not completely automated because you still have to manually process the cards. I would use the square app on my phone so that I wasn’t tied to one certain location.

          Hoping Square gets an automated version soon as the API doesn’t seem to allow for it as far as I could find.

    • http://www.hawkdesigngroup.com Chad Reitsma

      There is an error in the guide in section “Setting up our Class
      Constructor”, they forgot to explain that the class has to extend the
      current wp_gateway_payment class so add this above the gateways
      __constructor method:

      class SPYR_AuthorizeNet_AIM extends WC_Payment_Gateway {

      function __construct() {

      // The global ID for this Payment method
      $this->id = “spyr_authorizenet_aim”;
      ….
      ….
      ….

      } // End of SPYR_AuthorizeNet_AIM

      • imanuel gittens

        Yes this stumped me for a while but I figured it out eventually.

    • Kanser Tajoer Tjr

      its because this code:

      // If we made it this far, then include our Gateway Class
      include_once( ‘woocommerce-authorizenet-aim.php’ );

      this code purpose is to load class SPYR_AuthorizeNet_AIM, but class SPYR_AuthorizeNet_AIM is in file woocommerce-authorizenet-aim-gateway.php

      so the right code is

      // If we made it this far, then include our Gateway Class
      include_once( ‘woocommerce-authorizenet-aim-gateway.php’ );

  • juni

    nice work

  • Elvis

    Great work!

  • Carlos

    Good day,

    I did exactly as you presented in the tutorial, but unfortunately it did not work nothing , when I enter the product, the payment method is only charging and can not even test .

    Can you tell me why this happens ?

  • Jason

    Awesome, this is just what I’ve been looking for. I’m curious, what does `spyr-authorizenet-aim` mean in the lines like this one: `$this->title = __( “Authorize.net AIM”, ‘spyr-authorizenet-aim’ );`

  • Aung Myin

    Hello there. Thank for your info.

    I have one question. how to disable card info form on checkout page? Please help me anyone.

  • http://www.websaucesoftware.com/ ssteinerX

    Thank you for this tutorial! I had to write a custom gateway and with a few search & replace commands and the 20 or so lines of code I had to add, I have a fully functioning custom gateway. Thank you!

  • Nicolay

    Thanks mate!

  • Sébastien Brémond

    Nice tutorial.
    A very best guide ! What else ? Fine ;)

  • yuriy

    Hello. I tested this plugin, it works, but:

    It can be integrated with a very popular payment system klarna? (she has a lot of users, and many are looking for it on the internet, but only one module for WordPress – not free)

    If so, what should be changed?

    Have you done this before?

    I think many would be interested to know how this solution is universal.
    Your article is very interesting and detailed. I would appreciate an answer.

    Thank you.

  • Chung Hock TEO

    Yojance or any other who are willing, I am looking to pay someone to create a custom payment gateway with a local bank. Please me know if anyone interested.

    • http://www.websaucesoftware.com/ ssteinerX

      I have successfully deployed a custom gateway and might be interested in developing one for you.

  • Kanser Tajoer Tjr

    thanks for this tutorial, i am trying to write a woo commerce plugin payment gateway. This tutorial helped a lot!

  • Lord Kinful

    Hi, I am a bit new to this. what should be the name of my payment class and the name of the file. I am a bit lost. I am refering to the second set of codes. I.E “Setting Up Our Class Constructor”

  • Abu Fatin

    This tutorial is the best so far. Thank you so much for writing this guide.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in WordPress, once a week, for free.