How to build a custom WooCommerce payment gateway?

Building a custom WooCommerce payment gateway might sound intimidating, but it’s totally achievable with some coding know-how and a clear understanding of the process. In a nutshell, you’ll be creating a plugin that integrates with WooCommerce’s payment API, allowing your store to process payments through a service not natively supported. This gives you immense flexibility and control over your checkout experience.

Why Go Custom?

Before we dive into the how, let’s briefly touch on the why. You might consider a custom gateway if:

  • You’re using a niche payment provider: Your preferred local or industry-specific payment processor simply doesn’t have an existing WooCommerce integration.
  • You need unique functionality: Maybe you want to implement a very specific payment flow, offer tiered pricing based on the payment method, or integrate with an internal accounting system that’s not supported by off-the-shelf solutions.
  • Cost savings: For high-volume stores, developing your own gateway might be more cost-effective than ongoing fees from third-party plugins.
  • Full control: You get to dictate every aspect of the integration, from the UI on the checkout page to the backend processing.

While this offers great power, remember it also comes with responsibility. You’ll be maintaining this code, ensuring its security, and keeping up with any API changes from your payment provider.

Before writing a single line of code, you need a proper sandbox to play in. Don’t experiment on your live site!

Local Development Setup

  • Local Server: Tools like MAMP, XAMPP, or Local by Kinsta are excellent for setting up a WordPress environment on your computer. They create a local server (Apache or Nginx), a database (MySQL/MariaDB), and PHP.
  • WordPress Installation: Install a fresh copy of WordPress within your local server environment.
  • WooCommerce Installation: Install and activate WooCommerce on your local WordPress site. Go through the setup wizard to configure basic store settings, products, and a test currency.

Essential Tools and Resources

  • Code Editor: A good code editor like VS Code, Sublime Text, or PHPStorm will make your life much easier with features like syntax highlighting, auto-completion, and debugging.
  • WooCommerce Documentation: This is your bible. Specifically, you’ll be looking at the “Payment Gateway API” and “Developer Resources” sections. Get familiar with the hooks and filters available.
  • Payment Gateway API Documentation: The API documentation for the specific payment provider you want to integrate with is absolutely crucial. This will tell you how to send transaction requests, handle responses, and manage refunds.
  • PHP Knowledge: A solid understanding of PHP is non-negotiable. You’ll be working heavily with object-oriented programming (OOP).
  • Basic JavaScript/jQuery (Optional but helpful): For front-end validation or dynamic form changes on the checkout page.
  • Developer Tools (Browser): Chrome’s DevTools or Firefox’s Developer Tools are invaluable for debugging front-end issues, network requests, and seeing console output.

If you’re looking to enhance your WooCommerce store by creating a custom payment gateway, you might find it helpful to explore related resources that provide additional insights and guidance. For instance, check out this informative article on building custom payment solutions, which can complement your understanding of the process: How to Build a Custom WooCommerce Payment Gateway. This resource offers practical tips and best practices that can streamline your development efforts and ensure a seamless payment experience for your customers.

The Foundation: Your Plugin Structure

Every payment gateway in WooCommerce is essentially a standalone WordPress plugin. Let’s outline the basic structure.

Plugin Folder and Main File

  1. Create a New Folder: Inside wp-content/plugins/, create a new folder for your gateway, e.g., my-custom-gateway.
  2. Main Plugin File: Inside my-custom-gateway, create a PHP file that will be your main plugin file, e.g., my-custom-gateway.php.

Plugin Header

At the top of your my-custom-gateway.php file, include the standard WordPress plugin header:

“`php

/*

Plugin Name: My Custom WooCommerce Gateway

Plugin URI: https://example.com/my-custom-gateway

Description: A custom payment gateway for WooCommerce.

Version: 1.0.0

Author: Your Name

Author URI: https://example.com

License: GPLv2 or later

Text Domain: my-custom-gateway

*/

if ( ! defined( ‘ABSPATH’ ) ) {

exit; // Exit if accessed directly

}

// Ensure WooCommerce is active

if ( ! in_array( ‘woocommerce/woocommerce.php’, apply_filters( ‘active_plugins’, get_option( ‘active_plugins’ ) ) ) ) {

return;

}

“`

This header provides WordPress with essential information about your plugin. The if ( ! defined( 'ABSPATH' ) ) check is a security measure. The last if statement ensures WooCommerce is active before your gateway tries to load, preventing fatal errors.

Building Your Custom Gateway Class

The core of your payment gateway will be a PHP class that extends WooCommerce’s WC_Payment_Gateway class. This base class provides a lot of the boilerplate functionality you’ll need.

Extending WC_Payment_Gateway

In your my-custom-gateway.php file (or ideally, in a separate file like includes/class-my-custom-gateway.php for better organization), define your class:

“`php

function my_custom_gateway_init() {

if ( ! class_exists( ‘WC_Payment_Gateway’ ) ) {

return;

}

class WC_My_Custom_Gateway extends WC_Payment_Gateway {

/**

  • Constructor for the gateway.

*/

public function __construct() {

$this->id = ‘my_custom_gateway’; // Unique ID for your gateway

$this->icon = apply_filters( ‘woocommerce_my_custom_gateway_icon’, ” ); // Icon shown on checkout

$this->has_fields = false; // Set to true if you collect CC info on your site

$this->method_title = __( ‘My Custom Gateway’, ‘my-custom-gateway’ );

$this->method_description = __( ‘Take payments via My Custom Gateway.’, ‘my-custom-gateway’ );

// Load the settings.

$this->init_form_fields();

$this->init_settings();

// Define user setting variables.

$this->title = $this->get_option( ‘title’ );

$this->description = $this->get_option( ‘description’ );

$this->instructions = $this->get_option( ‘instructions’, $this->description );

$this->test_mode = ‘yes’ === $this->get_option( ‘test_mode’, ‘no’ );

$this->api_key = $this->get_option( ‘api_key’ ); // Example API key

// Actions.

add_action( ‘woocommerce_update_options_payment_gateways_’ . $this->id, array( $this, ‘process_admin_options’ ) );

add_action( ‘woocommerce_thankyou_’ . $this->id, array( $this, ‘thankyou_page’ ) ); // For instructions on thankyou page

// You might need to add hooks for webhook handling, etc.

// Enable or disable the gateway directly from your code based on certain conditions

// E.g., if API key is not set.

if ( empty( $this->api_key ) && ! is_admin() ) {

$this->enabled = ‘no’;

}

}

/**

  • Initialize Gateway Settings Form Fields.

*/

public function init_form_fields() {

$this->form_fields = apply_filters( ‘woocommerce_my_custom_gateway_form_fields’, array(

‘enabled’ => array(

‘title’ => __( ‘Enable/Disable’, ‘my-custom-gateway’ ),

‘type’ => ‘checkbox’,

‘label’ => __( ‘Enable My Custom Gateway’, ‘my-custom-gateway’ ),

‘default’ => ‘no’,

),

‘title’ => array(

‘title’ => __( ‘Title’, ‘my-custom-gateway’ ),

‘type’ => ‘text’,

‘description’ => __( ‘This controls the title which the user sees during checkout.’, ‘my-custom-gateway’ ),

‘default’ => __( ‘My Custom Gateway’, ‘my-custom-gateway’ ),

‘desc_tip’ => true,

),

‘description’ => array(

‘title’ => __( ‘Description’, ‘my-custom-gateway’ ),

‘type’ => ‘textarea’,

‘description’ => __( ‘This controls the description which the user sees during checkout.’, ‘my-custom-gateway’ ),

‘default’ => __( ‘Pay with My Custom Gateway.’, ‘my-custom-gateway’ ),

‘desc_tip’ => true,

),

‘instructions’ => array(

‘title’ => __( ‘Instructions’, ‘my-custom-gateway’ ),

‘type’ => ‘textarea’,

‘description’ => __( ‘Instructions that will be added to the thank you page and emails.’, ‘my-custom-gateway’ ),

‘default’ => ”,

‘desc_tip’ => true,

),

‘test_mode’ => array(

‘title’ => __( ‘Test Mode’, ‘my-custom-gateway’ ),

‘type’ => ‘checkbox’,

‘label’ => __( ‘Enable Test Mode’, ‘my-custom-gateway’ ),

‘default’ => ‘no’,

‘description’ => __( ‘Place the gateway in test mode using test API keys.’, ‘my-custom-gateway’ ),

),

‘api_key’ => array(

‘title’ => __( ‘API Key’, ‘my-custom-gateway’ ),

‘type’ => ‘text’,

‘description’ => __( ‘Enter your My Custom Gateway API Key here.’, ‘my-custom-gateway’ ),

‘default’ => ”,

‘desc_tip’ => true,

),

// Add more settings fields as needed (e.g., API Secret, Webhook URL, etc.)

) );

}

/**

  • Output for the order received page.

*/

public function thankyou_page() {

if ( $this->instructions ) {

echo wpautop( wptexturize( $this->instructions ) );

}

}

/**

  • Process the payment and return the result.

*

  • @param int $order_id
  • @return array

*/

public function process_payment( $order_id ) {

$order = wc_get_order( $order_id );

// Mark as on-hold (or processing, depending on your gateway’s immediate response)

// until payment is verified.

$order->update_status( ‘on-hold’, __( ‘Awaiting payment from My Custom Gateway.’, ‘my-custom-gateway’ ) );

// Reduce stock levels.

wc_reduce_stock_levels( $order_id );

// Remove cart.

WC()->cart->empty_cart();

// Return thankyou redirect URL and success.

return array(

‘result’ => ‘success’,

‘redirect’ => $this->get_return_url( $order )

);

}

// You might need to override other methods from WC_Payment_Gateway or add new ones,

// such as validate_fields(), process_refund(), etc.

}

}

add_action( ‘woocommerce_init’, ‘my_custom_gateway_init’ );

// Add your custom gateway to WooCommerce

function add_my_custom_gateway( $gateways ) {

$gateways[] = ‘WC_My_Custom_Gateway’;

return $gateways;

}

add_filter( ‘woocommerce_payment_gateways’, ‘add_my_custom_gateway’ );

“`

Let’s break down the key parts of this class:

  • __construct(): This is where you set the basic properties of your gateway (id, method_title, description). You also call init_form_fields() to define your settings, init_settings() to load them, and hook into woocommerce_update_options_payment_gateways_ to save options when the admin clicks “Save changes.”
  • init_form_fields(): This method defines the settings fields that will appear in the WooCommerce > Settings > Payments tab for your gateway. WooCommerce handles rendering these fields and saving their values automatically.
  • thankyou_page(): If you have custom instructions you want to display on the order received page, implement them here.
  • process_payment( $order_id ): This is the most critical method. It’s called when a customer clicks “Place Order” and your gateway is selected. This is where you:
  1. Fetch the WC_Order object.
  2. Interact with your payment provider’s API.
  3. Handle the API response (success, failure, pending).
  4. Update the order status (e.g., processing, on-hold, failed).
  5. Reduce stock, empty the cart.
  6. Return an array with result and redirect (the URL to send the customer to after placing the order).

Important Considerations for process_payment

  • API Interaction: This is where you’ll use cURL or Guzzle (a PHP HTTP client) to send requests to your payment provider’s API. You’ll typically send details like the order total, currency, customer details, and potentially payment token/card details if you’re handling that on-site.
  • Error Handling: What happens if the API call fails or returns an error? You need to set the order status to failed and potentially display an error message to the user. wc_add_notice( 'Payment failed: ' . $error_message, 'error' ); is your friend here.
  • Security: If you handle sensitive payment information (like credit card numbers), you must ensure PCI compliance. Ideally, you’d use a tokenization service from your payment provider to avoid handling raw card data yourself.
  • Webhooks: Many modern payment gateways operate asynchronously. The process_payment function might only initiate the payment. The actual success or failure might be communicated back to your server via a webhook. You’ll need a separate endpoint and listener for this, which we’ll touch on later.

Handling Payment Processing Logic

This is where your custom gateway interacts with the outside world – your chosen payment provider.

Interacting with the Payment Provider API

You’ll typically use PHP’s cURL extension or a library like Guzzle for HTTP requests.

“`php

// Example of a simplistic API call within process_payment

// (This is highly simplified and illustrative)

$api_endpoint = $this->test_mode ? ‘https://test-api.mygateway.com/charge’ : ‘https://api.mygateway.com/charge’;

$api_key = $this->api_key;

$amount = $order->get_total();

$currency = $order->get_currency();

$order_id_str = $order->get_order_number(); // Or get_id() for internal ID

$request_body = json_encode(array(

‘api_key’ => $api_key,

‘amount’ => $amount,

‘currency’ => $currency,

‘order_ref’ => $order_id_str,

‘customer’ => array(

‘first_name’ => $order->get_billing_first_name(),

‘last_name’ => $order->get_billing_last_name(),

’email’ => $order->get_billing_email(),

),

// … other data required by your provider, e.g., tokenized card data

));

$ch = curl_init();

curl_setopt( $ch, CURLOPT_URL, $api_endpoint );

curl_setopt( $ch, CURLOPT_POST, 1 );

curl_setopt( $ch, CURLOPT_POSTFIELDS, $request_body );

curl_setopt( $ch, CURLOPT_HTTPHEADER, array(

‘Content-Type: application/json’,

‘Authorization: Bearer ‘ . $api_key // Or whatever authentication your API uses

));

curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

$response = curl_exec( $ch );

$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

curl_close( $ch );

$response_data = json_decode( $response, true );

if ( $http_code === 200 && isset( $response_data[‘status’] ) && $response_data[‘status’] === ‘success’ ) {

// Payment successful

$order->payment_complete( $response_data[‘transaction_id’] ); // Store provider’s transaction ID

$order->add_order_note( __( ‘My Custom Gateway payment completed successfully.’, ‘my-custom-gateway’ ) );

wc_reduce_stock_levels( $order_id );

WC()->cart->empty_cart();

return array(

‘result’ => ‘success’,

‘redirect’ => $this->get_return_url( $order )

);

} else {

// Payment failed or API error

$error_message = isset( $response_data[‘message’] ) ? $response_data[‘message’] : __( ‘Payment processing failed.’, ‘my-custom-gateway’ );

$order->update_status( ‘failed’, sprintf( __( ‘My Custom Gateway payment failed: %s’, ‘my-custom-gateway’ ), $error_message ) );

$order->add_order_note( sprintf( __( ‘My Custom Gateway payment failed: %s’, ‘my-custom-gateway’ ), $error_message ) );

wc_add_notice( sprintf( __( ‘Payment failed, please try again. Reason: %s’, ‘my-custom-gateway’ ), $error_message ), ‘error’ );

return array(

‘result’ => ‘fail’,

‘redirect’ => ” // Stay on checkout page

);

}

“`

This is a simplistic example. Real-world API interactions can be much more complex, involving multiple steps, redirects, and custom error codes. Always refer to your payment provider’s API documentation.

Handling Synchronous vs. Asynchronous Payments

  • Synchronous: The process_payment function receives an immediate response from the payment gateway (e.g., “approved” or “declined”). This is common for direct credit card processing where the card is charged instantly.
  • Asynchronous: The process_payment function initiates the payment, but the final outcome (success/failure) is communicated later via a webhook. This is common for methods like bank transfers, “pay later” options, or even some credit card flows where 3D Secure authentication causes a redirect.

For asynchronous payments, you’ll need:

  1. A webhook endpoint: A specific URL on your site where the payment provider can send notifications (e.g., yourdomain.com/?wc-api=my_custom_gateway_webhook).
  2. A webhook listener: PHP code that receives and processes these notifications. This code needs to verify the webhook’s authenticity (e.g., signature verification) and then update the corresponding WooCommerce order.

“`php

// Example for webhook listener (in your main plugin file)

add_action( ‘woocommerce_api_my_custom_gateway_webhook’, ‘my_custom_gateway_webhook_handler’ );

function my_custom_gateway_webhook_handler() {

// Get the instance of your gateway class

$gateway = new WC_My_Custom_Gateway();

// Verify webhook signature (CRITICAL SECURITY STEP!)

// Refer to your payment provider’s docs for how to do this.

// If verification fails, return 401 or 403.

// For example: if ( ! $gateway->verify_webhook_signature() ) { wp_die( ‘Invalid signature’, ”, array( ‘response’ => 401 ) ); }

$payload = file_get_contents( ‘php://input’ );

$data = json_decode( $payload, true );

// Log for debugging

error_log( ‘My Custom Gateway Webhook Received: ‘ . print_r( $data, true ) );

if ( isset( $data[‘event_type’] ) && isset( $data[‘order_id’] ) ) {

$order_id = absint( $data[‘order_id’] );

$order = wc_get_order( $order_id );

if ( ! $order ) {

wp_die( ‘Order not found.’, ”, array( ‘response’ => 404 ) );

}

switch ( $data[‘event_type’] ) {

case ‘payment_succeeded’:

if ( ! $order->is_paid() ) {

$transaction_id = isset( $data[‘transaction_id’] ) ? sanitize_text_field( $data[‘transaction_id’] ) : ”;

$order->payment_complete( $transaction_id );

$order->add_order_note( __( ‘Webhook: My Custom Gateway payment succeeded.’, ‘my-custom-gateway’ ) );

}

break;

case ‘payment_failed’:

if ( ! $order->is_paid() ) {

$order->update_status( ‘failed’, __( ‘Webhook: My Custom Gateway payment failed.’, ‘my-custom-gateway’ ) );

$order->add_order_note( __( ‘Webhook: My Custom Gateway payment failed.’, ‘my-custom-gateway’ ) );

}

break;

// Add other event types as needed (refunded, cancelled, etc.)

}

}

// Always return a 200 OK response to the payment provider

status_header( 200 );

exit;

}

“`

If you’re looking to enhance your WooCommerce store by creating a custom payment gateway, you might find it helpful to explore related resources that provide additional insights. For instance, an informative article on the topic can be found at this link, which offers practical tips and guidance on integrating various payment options seamlessly into your online shop. By leveraging such resources, you can ensure a smoother experience for your customers while expanding your payment capabilities.

Enhancing User Experience (UI/UX)

The checkout process should feel seamless and trustworthy for your customers.

Custom Fields on Checkout

If your gateway requires extra information from the customer (e.g., a specific account number or a referral code), you can add custom fields to the checkout page.

“`php

// In your gateway class

public function payment_fields() {

if ( $this->description ) {

echo wpautop( wptexturize( $this->description ) );

}

// Display your custom fields here

?>

}

// Validate the custom fields in your gateway class

public function validate_fields() {

if ( empty( $_POST[‘my_custom_gateway_account_id’] ) ) {

wc_add_notice( __( ‘My Custom Gateway Account ID is required!’, ‘my-custom-gateway’ ), ‘error’ );

return false;

}

return true;

}

“`

You would then retrieve $_POST['my_custom_gateway_account_id'] in your process_payment function.

Styling and Icons

You can add a custom icon to your gateway that appears on the checkout page. In the constructor, you set $this->icon.

“`php

$this->icon = apply_filters( ‘woocommerce_my_custom_gateway_icon’, plugins_url( ‘assets/images/my-custom-gateway-icon.png’, __FILE__ ) );

“`

Remember to create the assets/images folder in your plugin and place your my-custom-gateway-icon.png there. You might also want to add custom CSS for your payment fields if they deviate significantly from default WooCommerce styles.

If you’re looking to enhance your WooCommerce store with a tailored payment solution, you might find it helpful to explore a related article that delves into the intricacies of creating a custom WooCommerce payment gateway. This resource provides valuable insights and step-by-step guidance that can complement your understanding of the process. For more information, check out this informative piece on custom payment gateways which can significantly streamline your e-commerce transactions.

Testing and Debugging Your Gateway

Thorough testing is crucial to ensure payments are processed correctly and securely.

Development Mode and Logging

  • WooCommerce Debug Log: Enable debugging in WooCommerce (WooCommerce > Status > Tools > WooCommerce logs). You can write custom log messages in your plugin using wc_get_logger():

“`php

$logger = wc_get_logger();

$context = array( ‘source’ => ‘my-custom-gateway’ ); // Helps filter logs

$logger->info( ‘API Request sent: ‘ . print_r( $request_body, true ), $context );

$logger->error( ‘API Error: ‘ . $error_message, $context );

“`

  • PHP Error Logs: Ensure WP_DEBUG is set to true in wp-config.php and check your PHP error logs for any fatal errors or warnings.
  • Payment Provider Sandbox: Use the test credentials and sandbox environment provided by your payment gateway. Never test with real money until absolutely confident.

Testing Scenarios

  • Successful Payment: Process an order and ensure it transitions to “Processing” or “Completed” correctly, and stock is reduced.
  • Failed Payment: Simulate a failed payment from your gateway’s sandbox (e.g., using a specific test card number or response code) and ensure the order status changes to “Failed” and an error message is displayed.
  • Pending Payment: Test scenarios where the payment goes into a “pending” state and is later confirmed or failed via a webhook.
  • Refunds: If your gateway supports refunds, implement the process_refund() method in your class and test it from the WooCommerce order details page.
  • Edge Cases:
  • Large order amounts.
  • Different currencies (if supported).
  • Guest checkout vs. logged-in users.
  • Network issues or API timeouts (how does your gateway handle these?).
  • Security: Pay special attention to security. Are API keys stored securely? Is data transmitted over HTTPS? Are webhooks verified?

Best Practices and Security

When dealing with payments, security is paramount.

Data Security

  • HTTPS Everywhere: Your entire site, especially the checkout pages, must use HTTPS.
  • API Key Management: Store API keys and secrets securely. Don’t hardcode them into your plugin. Use WooCommerce settings fields, and consider environment variables for production environments.
  • PCI DSS Compliance: If you are handling credit card data directly on your server, you need to be PCI DSS compliant. This is extremely complex and usually why developers opt for tokenization or hosted payment pages provided by the payment gateway. Avoid handling raw credit card data whenever possible.
  • Sanitization and Validation: Always sanitize user input (sanitize_text_field, absint, esc_url) and validate data before processing or storing it.

Code Organization and Maintainability

  • Separate Files: For larger plugins, put helper functions, API clients, and the main gateway class into separate files (e.g., includes/).
  • Namespacing: Use PHP namespaces to avoid conflicts with other plugins.
  • Translatability: Use __() and _e() for all user-facing strings so your plugin can be translated.
  • WooCommerce Hooks: Leverage WooCommerce hooks and filters (add_action, add_filter) to integrate cleanly without modifying core WooCommerce files.
  • Error Logging: Implement robust logging to help diagnose issues in production.

Updates and Compatibility

  • WooCommerce Updates: WooCommerce updates can sometimes introduce breaking changes. Keep an eye on the WooCommerce developer blog and release notes. Your custom gateway will need maintenance.
  • PHP Versions: Ensure your code is compatible with current and commonly used PHP versions.
  • Payment Gateway API Changes: Your payment provider might update their API. You’ll need to monitor their developer documentation and update your plugin accordingly.

Building a custom WooCommerce payment gateway is a rewarding challenge. It offers unparalleled flexibility but demands careful attention to detail, robust error handling, and unwavering commitment to security. By following these steps and continuously referring to the WooCommerce and your payment provider’s documentation, you can create a reliable and effective solution for your store.