By Devport Team | Last updated: 2025-07-12 | 19 min read

Building Custom WooCommerce Payment Gateways

Payment gateways are the backbone of any e-commerce operation, and WooCommerce's extensible architecture makes it possible to integrate virtually any payment provider. This comprehensive guide walks you through creating professional payment gateways, from basic implementations to advanced features like tokenization, subscriptions, and multi-currency support.

Whether you're integrating a regional payment provider or building a custom solution, understanding WooCommerce's payment gateway framework is crucial. We'll cover security best practices, PCI compliance, testing strategies, and real-world implementation patterns that ensure your payment gateway is both robust and user-friendly.

Table of Contents

  1. Payment Gateway Architecture
  2. Building Your First Gateway
  3. Advanced Payment Features
  4. Security and Compliance
  5. Testing and Debugging
  6. Integration Best Practices

Payment Gateway Architecture

Understanding WooCommerce Payment Flow

// Payment gateway base implementation
abstract class WC_Payment_Gateway_Base extends WC_Payment_Gateway {

    protected $api_endpoint;
    protected $api_key;
    protected $api_secret;
    protected $webhook_secret;
    protected $test_mode;

    /**
     * Gateway initialization
     */
    public function __construct() {
        $this->init_form_fields();
        $this->init_settings();

        // Define gateway properties
        $this->title = $this->get_option('title');
        $this->description = $this->get_option('description');
        $this->enabled = $this->get_option('enabled');
        $this->test_mode = 'yes' === $this->get_option('testmode');

        // API configuration
        $this->api_endpoint = $this->test_mode 
            ? 'https://api-sandbox.payment-provider.com/v1/' 
            : 'https://api.payment-provider.com/v1/';

        $this->api_key = $this->test_mode 
            ? $this->get_option('test_api_key') 
            : $this->get_option('live_api_key');

        $this->api_secret = $this->test_mode 
            ? $this->get_option('test_api_secret') 
            : $this->get_option('live_api_secret');

        $this->webhook_secret = $this->get_option('webhook_secret');

        // Hooks
        add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']);
        add_action('woocommerce_api_' . $this->id, [$this, 'webhook_handler']);

        // Scripts
        add_action('wp_enqueue_scripts', [$this, 'payment_scripts']);

        // Additional hooks for advanced features
        if ($this->supports('subscriptions')) {
            $this->init_subscription_hooks();
        }

        if ($this->supports('tokenization')) {
            $this->init_tokenization_hooks();
        }
    }

    /**
     * Check if gateway is available
     */
    public function is_available() {
        if ('yes' !== $this->enabled) {
            return false;
        }

        // Check if API credentials are set
        if (empty($this->api_key) || empty($this->api_secret)) {
            return false;
        }

        // Currency check
        if (!$this->is_currency_supported()) {
            return false;
        }

        // Country check
        if (!$this->is_country_supported()) {
            return false;
        }

        // Custom availability checks
        return apply_filters('woocommerce_gateway_' . $this->id . '_is_available', true, $this);
    }

    /**
     * Check if currency is supported
     */
    protected function is_currency_supported() {
        $supported_currencies = $this->get_supported_currencies();
        $current_currency = get_woocommerce_currency();

        return in_array($current_currency, $supported_currencies, true);
    }

    /**
     * Make API request
     */
    protected function api_request($endpoint, $data = [], $method = 'POST') {
        $url = $this->api_endpoint . $endpoint;

        $args = [
            'method' => $method,
            'timeout' => 45,
            'redirection' => 5,
            'httpversion' => '1.1',
            'blocking' => true,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json',
                'X-API-Version' => '2024-01',
                'X-Client-Library' => 'WooCommerce/' . WC()->version,
            ],
        ];

        if (!empty($data)) {
            $args['body'] = json_encode($data);
        }

        // Log request in test mode
        if ($this->test_mode) {
            $this->log('API Request to ' . $endpoint . ': ' . print_r($data, true));
        }

        $response = wp_remote_request($url, $args);

        if (is_wp_error($response)) {
            $this->log('API Error: ' . $response->get_error_message());
            throw new Exception($response->get_error_message());
        }

        $body = wp_remote_retrieve_body($response);
        $code = wp_remote_retrieve_response_code($response);

        // Log response in test mode
        if ($this->test_mode) {
            $this->log('API Response: ' . $body);
        }

        $data = json_decode($body, true);

        if ($code >= 400) {
            $error_message = isset($data['message']) ? $data['message'] : 'API request failed';
            throw new Exception($error_message);
        }

        return $data;
    }

    /**
     * Log gateway events
     */
    protected function log($message) {
        if ($this->test_mode) {
            $logger = wc_get_logger();
            $logger->debug($message, ['source' => $this->id]);
        }
    }
}

Implementing Core Gateway Methods

// Complete payment gateway implementation
class WC_Gateway_Custom_Provider extends WC_Payment_Gateway_Base {

    /**
     * Constructor
     */
    public function __construct() {
        $this->id = 'custom_provider';
        $this->icon = plugins_url('assets/images/logo.png', __FILE__);
        $this->has_fields = true;
        $this->method_title = __('Custom Payment Provider', 'woocommerce');
        $this->method_description = __('Accept payments through Custom Provider', 'woocommerce');

        // Gateway supports
        $this->supports = [
            'products',
            'refunds',
            'subscriptions',
            'subscription_cancellation',
            'subscription_suspension',
            'subscription_reactivation',
            'subscription_amount_changes',
            'subscription_date_changes',
            'subscription_payment_method_change',
            'subscription_payment_method_change_customer',
            'subscription_payment_method_change_admin',
            'multiple_subscriptions',
            'tokenization',
            'add_payment_method',
        ];

        parent::__construct();
    }

    /**
     * Initialize gateway settings
     */
    public function init_form_fields() {
        $this->form_fields = [
            'enabled' => [
                'title' => __('Enable/Disable', 'woocommerce'),
                'type' => 'checkbox',
                'label' => __('Enable Custom Payment Provider', 'woocommerce'),
                'default' => 'yes',
            ],
            'title' => [
                'title' => __('Title', 'woocommerce'),
                'type' => 'text',
                'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'),
                'default' => __('Custom Payment', 'woocommerce'),
                'desc_tip' => true,
            ],
            'description' => [
                'title' => __('Description', 'woocommerce'),
                'type' => 'textarea',
                'description' => __('Payment method description that the customer will see on your checkout.', 'woocommerce'),
                'default' => __('Pay securely with Custom Provider.', 'woocommerce'),
            ],
            'testmode' => [
                'title' => __('Test mode', 'woocommerce'),
                'type' => 'checkbox',
                'label' => __('Enable Test Mode', 'woocommerce'),
                'default' => 'yes',
                'description' => __('Place the payment gateway in test mode using test API keys.', 'woocommerce'),
            ],
            'test_api_key' => [
                'title' => __('Test API Key', 'woocommerce'),
                'type' => 'text',
                'description' => __('Enter your test API key', 'woocommerce'),
                'desc_tip' => true,
            ],
            'test_api_secret' => [
                'title' => __('Test API Secret', 'woocommerce'),
                'type' => 'password',
                'description' => __('Enter your test API secret', 'woocommerce'),
                'desc_tip' => true,
            ],
            'live_api_key' => [
                'title' => __('Live API Key', 'woocommerce'),
                'type' => 'text',
                'description' => __('Enter your live API key', 'woocommerce'),
                'desc_tip' => true,
            ],
            'live_api_secret' => [
                'title' => __('Live API Secret', 'woocommerce'),
                'type' => 'password',
                'description' => __('Enter your live API secret', 'woocommerce'),
                'desc_tip' => true,
            ],
            'webhook_secret' => [
                'title' => __('Webhook Secret', 'woocommerce'),
                'type' => 'password',
                'description' => sprintf(
                    __('Enter the webhook secret. Webhook URL: %s', 'woocommerce'),
                    add_query_arg('wc-api', $this->id, home_url('/'))
                ),
            ],
            'capture' => [
                'title' => __('Capture', 'woocommerce'),
                'type' => 'checkbox',
                'label' => __('Capture payment immediately', 'woocommerce'),
                'default' => 'yes',
                'description' => __('Whether to capture payment immediately or authorize only.', 'woocommerce'),
            ],
            'debug' => [
                'title' => __('Debug Log', 'woocommerce'),
                'type' => 'checkbox',
                'label' => __('Enable logging', 'woocommerce'),
                'default' => 'no',
                'description' => sprintf(
                    __('Log events such as API requests. You can check the logs in %s', 'woocommerce'),
                    '<a href="' . admin_url('admin.php?page=wc-status&tab=logs') . '">WooCommerce > Status > Logs</a>'
                ),
            ],
        ];
    }

    /**
     * Payment form on checkout page
     */
    public function payment_fields() {
        $description = $this->get_description();
        if ($description) {
            echo wpautop(wptexturize($description));
        }

        if ($this->supports('tokenization') && is_checkout()) {
            $this->tokenization_script();
            $this->saved_payment_methods();
        }

        ?>
        <fieldset id="wc-<?php echo esc_attr($this->id); ?>-form" class="wc-payment-form">
            <?php do_action('woocommerce_credit_card_form_start', $this->id); ?>

            <div class="form-row form-row-wide">
                <label for="<?php echo esc_attr($this->id); ?>-card-number">
                    <?php esc_html_e('Card Number', 'woocommerce'); ?> <span class="required">*</span>
                </label>
                <input 
                    id="<?php echo esc_attr($this->id); ?>-card-number" 
                    class="input-text wc-credit-card-form-card-number" 
                    type="text" 
                    maxlength="20" 
                    autocomplete="off" 
                    placeholder="•••• •••• •••• ••••" 
                    name="<?php echo esc_attr($this->id); ?>-card-number" 
                />
            </div>

            <div class="form-row form-row-first">
                <label for="<?php echo esc_attr($this->id); ?>-card-expiry">
                    <?php esc_html_e('Expiry (MM/YY)', 'woocommerce'); ?> <span class="required">*</span>
                </label>
                <input 
                    id="<?php echo esc_attr($this->id); ?>-card-expiry" 
                    class="input-text wc-credit-card-form-card-expiry" 
                    type="text" 
                    autocomplete="off" 
                    placeholder="<?php esc_attr_e('MM / YY', 'woocommerce'); ?>" 
                    name="<?php echo esc_attr($this->id); ?>-card-expiry" 
                />
            </div>

            <div class="form-row form-row-last">
                <label for="<?php echo esc_attr($this->id); ?>-card-cvc">
                    <?php esc_html_e('Card Code', 'woocommerce'); ?> <span class="required">*</span>
                </label>
                <input 
                    id="<?php echo esc_attr($this->id); ?>-card-cvc" 
                    class="input-text wc-credit-card-form-card-cvc" 
                    type="text" 
                    autocomplete="off" 
                    placeholder="<?php esc_attr_e('CVC', 'woocommerce'); ?>" 
                    name="<?php echo esc_attr($this->id); ?>-card-cvc" 
                />
            </div>

            <?php if ($this->supports('tokenization') && is_user_logged_in()) : ?>
                <div class="form-row form-row-wide">
                    <p class="form-row woocommerce-SavedPaymentMethods-saveNew">
                        <input 
                            id="wc-<?php echo esc_attr($this->id); ?>-new-payment-method" 
                            name="wc-<?php echo esc_attr($this->id); ?>-new-payment-method" 
                            type="checkbox" 
                            value="true" 
                        />
                        <label for="wc-<?php echo esc_attr($this->id); ?>-new-payment-method">
                            <?php esc_html_e('Save payment method', 'woocommerce'); ?>
                        </label>
                    </p>
                </div>
            <?php endif; ?>

            <?php do_action('woocommerce_credit_card_form_end', $this->id); ?>

            <div class="clear"></div>
        </fieldset>
        <?php
    }

    /**
     * Process payment
     */
    public function process_payment($order_id) {
        $order = wc_get_order($order_id);

        try {
            // Validate payment fields
            $this->validate_fields();

            // Prepare payment data
            $payment_data = $this->prepare_payment_data($order);

            // Create payment intent
            $intent = $this->api_request('payment_intents', $payment_data);

            if (isset($intent['requires_action']) && $intent['requires_action']) {
                // 3D Secure or additional authentication required
                return [
                    'result' => 'success',
                    'redirect' => $intent['redirect_url'],
                ];
            }

            if ($intent['status'] === 'succeeded') {
                // Payment successful
                $order->payment_complete($intent['id']);

                // Store payment metadata
                $order->update_meta_data('_payment_intent_id', $intent['id']);
                $order->update_meta_data('_payment_method_id', $intent['payment_method']);

                // Save token if requested
                if ($this->supports('tokenization') && isset($_POST['wc-' . $this->id . '-new-payment-method'])) {
                    $this->save_payment_token($order, $intent['payment_method']);
                }

                $order->save();

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

                // Return success
                return [
                    'result' => 'success',
                    'redirect' => $this->get_return_url($order),
                ];
            }

            throw new Exception(__('Payment failed. Please try again.', 'woocommerce'));

        } catch (Exception $e) {
            wc_add_notice($e->getMessage(), 'error');
            $this->log('Payment error: ' . $e->getMessage());

            return [
                'result' => 'fail',
                'redirect' => '',
            ];
        }
    }

    /**
     * Validate payment fields
     */
    public function validate_fields() {
        $card_number = isset($_POST[$this->id . '-card-number']) 
            ? wc_clean($_POST[$this->id . '-card-number']) 
            : '';

        $card_expiry = isset($_POST[$this->id . '-card-expiry']) 
            ? wc_clean($_POST[$this->id . '-card-expiry']) 
            : '';

        $card_cvc = isset($_POST[$this->id . '-card-cvc']) 
            ? wc_clean($_POST[$this->id . '-card-cvc']) 
            : '';

        // Remove spaces and hyphens
        $card_number = str_replace([' ', '-'], '', $card_number);

        // Validate card number
        if (empty($card_number) || !$this->is_valid_card_number($card_number)) {
            wc_add_notice(__('Please enter a valid card number.', 'woocommerce'), 'error');
            return false;
        }

        // Validate expiry
        if (empty($card_expiry) || !$this->is_valid_expiry($card_expiry)) {
            wc_add_notice(__('Please enter a valid expiry date.', 'woocommerce'), 'error');
            return false;
        }

        // Validate CVC
        if (empty($card_cvc) || !$this->is_valid_cvc($card_cvc)) {
            wc_add_notice(__('Please enter a valid card security code.', 'woocommerce'), 'error');
            return false;
        }

        return true;
    }

    /**
     * Webhook handler
     */
    public function webhook_handler() {
        $payload = file_get_contents('php://input');
        $sig_header = $_SERVER['HTTP_SIGNATURE'] ?? '';

        try {
            // Verify webhook signature
            if (!$this->verify_webhook_signature($payload, $sig_header)) {
                throw new Exception('Invalid signature');
            }

            $event = json_decode($payload, true);

            switch ($event['type']) {
                case 'payment_intent.succeeded':
                    $this->handle_payment_success($event['data']);
                    break;

                case 'payment_intent.failed':
                    $this->handle_payment_failure($event['data']);
                    break;

                case 'charge.refunded':
                    $this->handle_refund_webhook($event['data']);
                    break;

                case 'charge.dispute.created':
                    $this->handle_dispute($event['data']);
                    break;

                default:
                    $this->log('Unhandled webhook event: ' . $event['type']);
            }

            status_header(200);
            exit;

        } catch (Exception $e) {
            $this->log('Webhook error: ' . $e->getMessage());
            status_header(400);
            exit($e->getMessage());
        }
    }
}

Building Your First Gateway

Step-by-Step Implementation

// Minimal working payment gateway
class WC_Gateway_Simple extends WC_Payment_Gateway {

    public function __construct() {
        $this->id = 'simple_gateway';
        $this->icon = '';
        $this->has_fields = false;
        $this->method_title = 'Simple Gateway';
        $this->method_description = 'A simple payment gateway example';

        // Load settings
        $this->init_form_fields();
        $this->init_settings();

        $this->title = $this->get_option('title');
        $this->description = $this->get_option('description');
        $this->enabled = $this->get_option('enabled');

        // Save settings
        add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']);
    }

    public function init_form_fields() {
        $this->form_fields = [
            'enabled' => [
                'title' => 'Enable/Disable',
                'type' => 'checkbox',
                'label' => 'Enable Simple Gateway',
                'default' => 'yes'
            ],
            'title' => [
                'title' => 'Title',
                'type' => 'text',
                'description' => 'This controls the title displayed during checkout.',
                'default' => 'Simple Payment',
                'desc_tip' => true,
            ],
            'description' => [
                'title' => 'Description',
                'type' => 'textarea',
                'description' => 'This controls the description displayed during checkout.',
                'default' => 'Pay with our simple gateway.',
            ],
        ];
    }

    public function process_payment($order_id) {
        $order = wc_get_order($order_id);

        // Mark as on-hold (we're awaiting payment)
        $order->update_status('on-hold', __('Awaiting payment confirmation', 'woocommerce'));

        // Reduce stock
        wc_reduce_stock_levels($order_id);

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

        // Return success
        return [
            'result' => 'success',
            'redirect' => $this->get_return_url($order)
        ];
    }
}

// Register the gateway
add_filter('woocommerce_payment_gateways', function($gateways) {
    $gateways[] = 'WC_Gateway_Simple';
    return $gateways;
});

Advanced Payment Features

Tokenization Implementation

// Token-based payment implementation
class WC_Gateway_Tokenized extends WC_Payment_Gateway_CC {

    public function __construct() {
        $this->supports = [
            'products',
            'refunds',
            'tokenization',
            'add_payment_method',
        ];

        parent::__construct();
    }

    /**
     * Add payment method via account page
     */
    public function add_payment_method() {
        try {
            // Create payment method on provider
            $payment_method = $this->create_payment_method();

            // Create and save token
            $token = new WC_Payment_Token_CC();
            $token->set_token($payment_method['id']);
            $token->set_gateway_id($this->id);
            $token->set_card_type($payment_method['card']['brand']);
            $token->set_last4($payment_method['card']['last4']);
            $token->set_expiry_month($payment_method['card']['exp_month']);
            $token->set_expiry_year($payment_method['card']['exp_year']);
            $token->set_user_id(get_current_user_id());
            $token->save();

            return [
                'result' => 'success',
                'redirect' => wc_get_endpoint_url('payment-methods'),
            ];

        } catch (Exception $e) {
            wc_add_notice($e->getMessage(), 'error');
            return [
                'result' => 'failure',
            ];
        }
    }

    /**
     * Process payment with saved token
     */
    protected function process_token_payment($token, $order) {
        $payment_data = [
            'amount' => $order->get_total() * 100, // Convert to cents
            'currency' => $order->get_currency(),
            'payment_method' => $token->get_token(),
            'customer' => $this->get_customer_id($order),
            'metadata' => [
                'order_id' => $order->get_id(),
                'order_key' => $order->get_order_key(),
            ],
        ];

        $result = $this->api_request('charges', $payment_data);

        if ($result['status'] === 'succeeded') {
            $order->payment_complete($result['id']);
            return true;
        }

        return false;
    }
}

Subscription Support

// Subscription-compatible gateway
class WC_Gateway_Subscription_Ready extends WC_Payment_Gateway_Base {

    public function __construct() {
        $this->supports = array_merge($this->supports, [
            'subscriptions',
            'subscription_cancellation',
            'subscription_suspension',
            'subscription_reactivation',
            'subscription_amount_changes',
            'subscription_date_changes',
            'subscription_payment_method_change',
            'multiple_subscriptions',
        ]);

        parent::__construct();

        // Subscription hooks
        add_action('woocommerce_scheduled_subscription_payment_' . $this->id, [$this, 'scheduled_subscription_payment'], 10, 2);
        add_action('woocommerce_subscription_failing_payment_method_updated_' . $this->id, [$this, 'update_failing_payment_method'], 10, 2);
        add_action('woocommerce_subscription_cancelled_' . $this->id, [$this, 'cancel_subscription']);
    }

    /**
     * Process scheduled subscription payment
     */
    public function scheduled_subscription_payment($renewal_total, $renewal_order) {
        try {
            $subscription = wcs_get_subscriptions_for_renewal_order($renewal_order);
            $subscription = reset($subscription);

            // Get saved payment method
            $payment_token_id = $subscription->get_meta('_payment_token');
            $token = WC_Payment_Tokens::get($payment_token_id);

            if (!$token || $token->get_user_id() !== $subscription->get_user_id()) {
                throw new Exception('Invalid payment token');
            }

            // Process payment
            $payment_data = [
                'amount' => $renewal_total * 100,
                'currency' => $renewal_order->get_currency(),
                'payment_method' => $token->get_token(),
                'customer' => $this->get_customer_id($renewal_order),
                'description' => sprintf(
                    'Subscription renewal for %s',
                    $subscription->get_id()
                ),
                'metadata' => [
                    'order_id' => $renewal_order->get_id(),
                    'subscription_id' => $subscription->get_id(),
                    'renewal' => true,
                ],
            ];

            $result = $this->api_request('charges', $payment_data);

            if ($result['status'] === 'succeeded') {
                $renewal_order->payment_complete($result['id']);
                $subscription->add_order_note(
                    sprintf('Renewal payment processed successfully. Transaction ID: %s', $result['id'])
                );
            } else {
                throw new Exception('Payment failed');
            }

        } catch (Exception $e) {
            $renewal_order->update_status('failed', $e->getMessage());
            $subscription->add_order_note(
                sprintf('Renewal payment failed: %s', $e->getMessage())
            );
        }
    }
}

Security and Compliance

PCI Compliance Implementation

// PCI-compliant tokenization
class WC_Gateway_PCI_Compliant extends WC_Payment_Gateway {

    /**
     * Enqueue secure payment scripts
     */
    public function payment_scripts() {
        if (!is_checkout() || !$this->is_available()) {
            return;
        }

        // Provider's secure JS library
        wp_enqueue_script(
            $this->id . '-js',
            'https://js.payment-provider.com/v3/',
            [],
            null,
            true
        );

        // Our integration script
        wp_enqueue_script(
            $this->id . '-integration',
            plugins_url('assets/js/checkout.js', __FILE__),
            ['jquery', $this->id . '-js'],
            '1.0.0',
            true
        );

        // Localize script with gateway params
        wp_localize_script($this->id . '-integration', 'gateway_params', [
            'public_key' => $this->get_public_key(),
            'is_test_mode' => $this->test_mode,
            'ajax_url' => WC_AJAX::get_endpoint('process_payment'),
            'nonce' => wp_create_nonce('payment-nonce'),
            'i18n' => [
                'card_number_invalid' => __('Card number is invalid', 'woocommerce'),
                'card_expiry_invalid' => __('Card expiry is invalid', 'woocommerce'),
                'card_cvc_invalid' => __('Card CVC is invalid', 'woocommerce'),
                'processing' => __('Processing...', 'woocommerce'),
            ],
        ]);
    }

    /**
     * Tokenize card without touching sensitive data
     */
    public function tokenize_payment_method() {
        check_ajax_referer('payment-nonce', 'nonce');

        try {
            $token_data = json_decode(stripslashes($_POST['token_data']), true);

            // Validate token with provider
            $validation = $this->api_request('tokens/validate', [
                'token' => $token_data['id'],
                'fingerprint' => $this->get_device_fingerprint(),
            ]);

            if (!$validation['valid']) {
                throw new Exception('Invalid token');
            }

            // Return token for form submission
            wp_send_json_success([
                'token' => $token_data['id'],
                'card' => [
                    'brand' => $validation['card']['brand'],
                    'last4' => $validation['card']['last4'],
                ],
            ]);

        } catch (Exception $e) {
            wp_send_json_error($e->getMessage());
        }
    }
}

Testing and Debugging

Comprehensive Testing Framework

// Payment gateway testing utilities
class WC_Gateway_Test_Helper {

    private $gateway;
    private $test_cards = [
        'success' => '4242424242424242',
        'declined' => '4000000000000002',
        'insufficient_funds' => '4000000000000341',
        'expired' => '4000000000000069',
        'incorrect_cvc' => '4000000000000127',
        'processing_error' => '4000000000000119',
        '3d_secure' => '4000000000003220',
    ];

    public function __construct($gateway) {
        $this->gateway = $gateway;
    }

    /**
     * Run comprehensive gateway tests
     */
    public function run_tests() {
        $results = [];

        // Test API connectivity
        $results['api_connection'] = $this->test_api_connection();

        // Test payment processing
        foreach ($this->test_cards as $scenario => $card_number) {
            $results['payments'][$scenario] = $this->test_payment($card_number);
        }

        // Test refunds
        $results['refunds'] = $this->test_refunds();

        // Test webhooks
        $results['webhooks'] = $this->test_webhooks();

        // Test tokenization
        if ($this->gateway->supports('tokenization')) {
            $results['tokenization'] = $this->test_tokenization();
        }

        return $results;
    }

    /**
     * Test payment processing
     */
    private function test_payment($card_number) {
        // Create test order
        $order = wc_create_order([
            'status' => 'pending',
            'customer_id' => 1,
        ]);

        $order->add_product(wc_get_product(1), 1);
        $order->calculate_totals();

        // Simulate payment data
        $_POST[$this->gateway->id . '-card-number'] = $card_number;
        $_POST[$this->gateway->id . '-card-expiry'] = '12/25';
        $_POST[$this->gateway->id . '-card-cvc'] = '123';

        // Process payment
        $result = $this->gateway->process_payment($order->get_id());

        // Clean up
        wp_delete_post($order->get_id(), true);

        return [
            'success' => $result['result'] === 'success',
            'message' => $result['message'] ?? 'Payment processed',
        ];
    }
}

// Usage in development
if (defined('WP_DEBUG') && WP_DEBUG) {
    add_action('admin_init', function() {
        if (isset($_GET['test_gateway'])) {
            $gateway = new WC_Gateway_Custom_Provider();
            $tester = new WC_Gateway_Test_Helper($gateway);
            $results = $tester->run_tests();

            echo '<pre>';
            print_r($results);
            echo '</pre>';
            exit;
        }
    });
}

Integration Best Practices

Error Handling and Recovery

// Robust error handling
class WC_Gateway_Error_Handler {

    private $gateway;
    private $max_retries = 3;

    /**
     * Process payment with retry logic
     */
    public function process_payment_with_retry($order, $payment_data) {
        $attempts = 0;
        $last_error = null;

        while ($attempts < $this->max_retries) {
            try {
                $attempts++;

                // Add retry metadata
                $payment_data['metadata']['attempt'] = $attempts;

                // Process payment
                $result = $this->gateway->api_request('charges', $payment_data);

                if ($result['status'] === 'succeeded') {
                    return $result;
                }

                // Handle soft declines
                if ($this->is_retryable_error($result)) {
                    $last_error = $result['error']['message'];
                    sleep(pow(2, $attempts)); // Exponential backoff
                    continue;
                }

                // Non-retryable error
                throw new Exception($result['error']['message']);

            } catch (Exception $e) {
                $last_error = $e->getMessage();

                // Check if network error (retryable)
                if ($this->is_network_error($e) && $attempts < $this->max_retries) {
                    sleep(pow(2, $attempts));
                    continue;
                }

                // Log error
                $this->log_payment_error($order, $e, $attempts);

                throw $e;
            }
        }

        // Max retries reached
        throw new Exception(
            sprintf('Payment failed after %d attempts: %s', $attempts, $last_error)
        );
    }

    /**
     * Webhook processing with idempotency
     */
    public function process_webhook_idempotent($event) {
        $event_id = $event['id'];
        $processed_key = 'webhook_processed_' . $event_id;

        // Check if already processed
        if (get_transient($processed_key)) {
            return ['status' => 'already_processed'];
        }

        // Lock to prevent concurrent processing
        $lock_key = 'webhook_lock_' . $event_id;
        if (!$this->acquire_lock($lock_key)) {
            return ['status' => 'processing'];
        }

        try {
            // Process webhook
            $result = $this->gateway->handle_webhook_event($event);

            // Mark as processed
            set_transient($processed_key, true, DAY_IN_SECONDS * 7);

            return $result;

        } finally {
            $this->release_lock($lock_key);
        }
    }
}

Building custom payment gateways requires careful attention to security, user experience, and reliability. By following these patterns and best practices, you can create professional payment integrations that handle millions in transactions while maintaining PCI compliance and providing excellent user experience.