> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/woocommerce/woocommerce/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Gateway API

> Build custom payment gateways for WooCommerce using the Payment Gateway API

# Payment Gateway API

The WooCommerce Payment Gateway API allows you to create custom payment methods that integrate seamlessly with your WooCommerce store. This guide covers everything from basic gateway creation to advanced payment flows.

## Overview

Payment gateways in WooCommerce are class-based and can be added through plugins. They extend the `WC_Payment_Gateway` class to handle payment processing, settings management, and order handling.

## Types of Payment Gateways

WooCommerce supports four main types of payment gateways:

<Accordion title="Form-based Gateways">
  Redirects users to the payment processor's website to complete payment. The user clicks a button that takes them offsite.

  **Examples:** PayPal Standard, Authorize.net DPM

  **Advantages:**

  * Lower PCI compliance requirements
  * Reduced security burden on your store
  * Easier to implement
</Accordion>

<Accordion title="iFrame-based Gateways">
  Loads the payment system inside an iframe on your store, keeping users on your site while processing payment externally.

  **Examples:** SagePay Form, PayPal Advanced

  **Advantages:**

  * Users stay on your domain
  * Moderate security requirements
  * Better user experience than full redirects
</Accordion>

<Accordion title="Direct Gateways">
  Payment fields appear directly on the checkout page. Payment is processed when the user clicks "Place Order."

  **Examples:** PayPal Pro, Authorize.net AIM, Stripe

  **Requirements:**

  * SSL certificate required
  * PCI compliance necessary
  * Higher security standards

  **Advantages:**

  * Seamless checkout experience
  * Full control over the payment flow
  * No redirects
</Accordion>

<Accordion title="Offline Gateways">
  No online payment is processed. Used for cash, checks, bank transfers, or other manual payment methods.

  **Examples:** Check, Bank Transfer, Cash on Delivery

  **Advantages:**

  * Simple to implement
  * No technical requirements
  * Useful for B2B or local businesses
</Accordion>

<Note>
  Form and iFrame-based gateways post data offsite, reducing security concerns. Direct gateways require SSL certificates and may require PCI compliance.
</Note>

## Creating a Basic Payment Gateway

### Step 1: Set Up the Plugin

Create a new plugin file that hooks into WooCommerce:

```php theme={null}
<?php
/**
 * Plugin Name: Custom Payment Gateway
 * Description: A custom payment gateway for WooCommerce
 * Version: 1.0.0
 * Author: Your Name
 * Text Domain: custom-payment-gateway
 */

defined( 'ABSPATH' ) || exit;

// Initialize the gateway class
add_action( 'plugins_loaded', 'init_custom_payment_gateway' );

function init_custom_payment_gateway() {
    if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
        return;
    }
    
    // Include the gateway class
    require_once plugin_dir_path( __FILE__ ) . 'class-wc-gateway-custom.php';
    
    // Add the gateway to WooCommerce
    add_filter( 'woocommerce_payment_gateways', 'add_custom_gateway' );
}

function add_custom_gateway( $methods ) {
    $methods[] = 'WC_Gateway_Custom';
    return $methods;
}
```

### Step 2: Create the Gateway Class

Your gateway must extend `WC_Payment_Gateway`:

```php theme={null}
class WC_Gateway_Custom extends WC_Payment_Gateway {
    
    /**
     * Constructor for the gateway.
     */
    public function __construct() {
        // Gateway ID
        $this->id = 'custom_gateway';
        
        // Gateway icon (optional)
        $this->icon = '';
        
        // Has payment fields on checkout?
        $this->has_fields = false;
        
        // Title shown in admin
        $this->method_title = __( 'Custom Payment Gateway', 'custom-payment-gateway' );
        
        // Description shown in admin
        $this->method_description = __( 'Accept payments through our custom gateway', 'custom-payment-gateway' );
        
        // Gateway supports
        $this->supports = array(
            'products',
            'refunds',
        );
        
        // Load the settings
        $this->init_form_fields();
        $this->init_settings();
        
        // Get settings
        $this->title = $this->get_option( 'title' );
        $this->description = $this->get_option( 'description' );
        $this->enabled = $this->get_option( 'enabled' );
        $this->testmode = 'yes' === $this->get_option( 'testmode' );
        
        // Save settings
        add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
    }
    
    /**
     * Initialize gateway settings form fields.
     */
    public function init_form_fields() {
        $this->form_fields = array(
            'enabled' => array(
                'title'       => __( 'Enable/Disable', 'custom-payment-gateway' ),
                'label'       => __( 'Enable Custom Payment Gateway', 'custom-payment-gateway' ),
                'type'        => 'checkbox',
                'description' => '',
                'default'     => 'no',
            ),
            'title' => array(
                'title'       => __( 'Title', 'custom-payment-gateway' ),
                'type'        => 'text',
                'description' => __( 'Payment method title that customers see during checkout.', 'custom-payment-gateway' ),
                'default'     => __( 'Custom Payment', 'custom-payment-gateway' ),
                'desc_tip'    => true,
            ),
            'description' => array(
                'title'       => __( 'Description', 'custom-payment-gateway' ),
                'type'        => 'textarea',
                'description' => __( 'Payment method description shown during checkout.', 'custom-payment-gateway' ),
                'default'     => __( 'Pay securely using our custom payment gateway.', 'custom-payment-gateway' ),
                'desc_tip'    => true,
            ),
            'testmode' => array(
                'title'       => __( 'Test Mode', 'custom-payment-gateway' ),
                'label'       => __( 'Enable Test Mode', 'custom-payment-gateway' ),
                'type'        => 'checkbox',
                'description' => __( 'Place the payment gateway in test mode.', 'custom-payment-gateway' ),
                'default'     => 'yes',
                'desc_tip'    => true,
            ),
        );
    }
    
    /**
     * Process the payment and return the result.
     *
     * @param int $order_id Order ID.
     * @return array
     */
    public function process_payment( $order_id ) {
        $order = wc_get_order( $order_id );
        
        // Mark as on-hold (we're awaiting the payment)
        $order->update_status( 'on-hold', __( 'Awaiting payment confirmation', 'custom-payment-gateway' ) );
        
        // Reduce stock levels
        wc_reduce_stock_levels( $order_id );
        
        // Remove cart
        WC()->cart->empty_cart();
        
        // Return thankyou redirect
        return array(
            'result'   => 'success',
            'redirect' => $this->get_return_url( $order ),
        );
    }
}
```

## Gateway Properties

The `WC_Payment_Gateway` class provides several important properties you can configure:

<ParamField path="id" type="string" required>
  Unique identifier for your gateway (e.g., 'custom\_gateway')
</ParamField>

<ParamField path="icon" type="string">
  URL to an image shown next to the gateway name on checkout
</ParamField>

<ParamField path="has_fields" type="boolean" default="false">
  Set to `true` if payment fields should appear on checkout (for direct gateways)
</ParamField>

<ParamField path="method_title" type="string" required>
  Title shown on the admin payment settings page
</ParamField>

<ParamField path="method_description" type="string">
  Description shown on the admin payment settings page
</ParamField>

<ParamField path="supports" type="array">
  Array of features the gateway supports:

  * `products` - Standard product checkout
  * `refunds` - Refund processing via API
  * `subscriptions` - WooCommerce Subscriptions
  * `pre-orders` - WooCommerce Pre-Orders
  * `tokenization` - Saved payment methods
  * `add_payment_method` - Add payment method from My Account
  * `default_credit_card_form` - Use WooCommerce's default credit card form
</ParamField>

## Direct Payment Gateways

For gateways that collect payment information directly on the checkout page:

### Enable Payment Fields

```php theme={null}
public function __construct() {
    // ... other settings ...
    
    $this->has_fields = true;
    
    // Use default credit card form
    $this->supports = array(
        'products',
        'refunds',
        'default_credit_card_form',
    );
}
```

### Add Custom Payment Fields

```php theme={null}
/**
 * Display payment fields on the checkout page.
 */
public function payment_fields() {
    // Display description
    if ( $this->description ) {
        echo wpautop( wp_kses_post( $this->description ) );
    }
    
    // Display custom fields
    ?>
    <div class="custom-payment-fields">
        <p class="form-row form-row-wide">
            <label for="custom-card-number">
                <?php esc_html_e( 'Card Number', 'custom-payment-gateway' ); ?>
                <span class="required">*</span>
            </label>
            <input 
                id="custom-card-number" 
                name="custom_card_number" 
                type="text" 
                autocomplete="off"
                class="input-text" 
                placeholder="•••• •••• •••• ••••"
            />
        </p>
        
        <p class="form-row form-row-first">
            <label for="custom-card-expiry">
                <?php esc_html_e( 'Expiry Date', 'custom-payment-gateway' ); ?>
                <span class="required">*</span>
            </label>
            <input 
                id="custom-card-expiry" 
                name="custom_card_expiry" 
                type="text" 
                autocomplete="off"
                class="input-text" 
                placeholder="MM / YY"
            />
        </p>
        
        <p class="form-row form-row-last">
            <label for="custom-card-cvc">
                <?php esc_html_e( 'Card Code (CVC)', 'custom-payment-gateway' ); ?>
                <span class="required">*</span>
            </label>
            <input 
                id="custom-card-cvc" 
                name="custom_card_cvc" 
                type="text" 
                autocomplete="off"
                class="input-text" 
                placeholder="CVC"
            />
        </p>
    </div>
    <?php
}
```

### Validate Payment Fields

```php theme={null}
/**
 * Validate payment fields on the checkout page.
 *
 * @return bool
 */
public function validate_fields() {
    $card_number = isset( $_POST['custom_card_number'] ) ? wc_clean( wp_unslash( $_POST['custom_card_number'] ) ) : '';
    $card_expiry = isset( $_POST['custom_card_expiry'] ) ? wc_clean( wp_unslash( $_POST['custom_card_expiry'] ) ) : '';
    $card_cvc = isset( $_POST['custom_card_cvc'] ) ? wc_clean( wp_unslash( $_POST['custom_card_cvc'] ) ) : '';
    
    if ( empty( $card_number ) ) {
        wc_add_notice( __( 'Card number is required', 'custom-payment-gateway' ), 'error' );
        return false;
    }
    
    if ( empty( $card_expiry ) ) {
        wc_add_notice( __( 'Expiry date is required', 'custom-payment-gateway' ), 'error' );
        return false;
    }
    
    if ( empty( $card_cvc ) ) {
        wc_add_notice( __( 'Card security code is required', 'custom-payment-gateway' ), 'error' );
        return false;
    }
    
    return true;
}
```

### Process Direct Payment

```php theme={null}
/**
 * Process the payment.
 *
 * @param int $order_id Order ID.
 * @return array
 */
public function process_payment( $order_id ) {
    $order = wc_get_order( $order_id );
    
    // Get payment data
    $card_number = isset( $_POST['custom_card_number'] ) ? wc_clean( wp_unslash( $_POST['custom_card_number'] ) ) : '';
    $card_expiry = isset( $_POST['custom_card_expiry'] ) ? wc_clean( wp_unslash( $_POST['custom_card_expiry'] ) ) : '';
    $card_cvc = isset( $_POST['custom_card_cvc'] ) ? wc_clean( wp_unslash( $_POST['custom_card_cvc'] ) ) : '';
    
    try {
        // Process payment with your payment API
        $response = $this->process_api_payment( $order, $card_number, $card_expiry, $card_cvc );
        
        if ( $response['success'] ) {
            // Payment successful
            $order->payment_complete( $response['transaction_id'] );
            
            // Add order note
            $order->add_order_note(
                sprintf(
                    __( 'Payment completed. Transaction ID: %s', 'custom-payment-gateway' ),
                    $response['transaction_id']
                )
            );
            
            // Return success
            return array(
                'result'   => 'success',
                'redirect' => $this->get_return_url( $order ),
            );
        } else {
            throw new Exception( $response['error_message'] );
        }
    } catch ( Exception $e ) {
        wc_add_notice( __( 'Payment error: ', 'custom-payment-gateway' ) . $e->getMessage(), 'error' );
        return array(
            'result' => 'failure',
        );
    }
}
```

<Warning>
  Direct payment gateways handling credit card data must use SSL/TLS encryption and comply with PCI DSS standards. Consider using tokenization or hosted payment fields to reduce your PCI compliance scope.
</Warning>

## Payment Gateway Callbacks

Many payment processors use webhooks or callbacks to notify your store about payment status changes.

### Setting Up the Callback URL

Use WooCommerce's WC-API endpoint:

```php theme={null}
public function __construct() {
    // ... other settings ...
    
    // Add webhook handler
    add_action( 'woocommerce_api_wc_gateway_custom', array( $this, 'webhook_handler' ) );
}

/**
 * Get the callback URL for this gateway.
 *
 * @return string
 */
public function get_callback_url() {
    return WC()->api_request_url( 'wc_gateway_custom' );
}
```

### Handle Webhook Notifications

```php theme={null}
/**
 * Handle webhook/IPN notifications.
 */
public function webhook_handler() {
    // Get the raw POST data
    $raw_post = file_get_contents( 'php://input' );
    $data = json_decode( $raw_post, true );
    
    // Verify the webhook signature
    if ( ! $this->verify_webhook_signature( $raw_post ) ) {
        status_header( 401 );
        exit;
    }
    
    // Get the order
    $order_id = isset( $data['order_id'] ) ? absint( $data['order_id'] ) : 0;
    $order = wc_get_order( $order_id );
    
    if ( ! $order ) {
        status_header( 404 );
        exit;
    }
    
    // Process based on event type
    switch ( $data['event_type'] ) {
        case 'payment_completed':
            if ( ! $order->is_paid() ) {
                $order->payment_complete( $data['transaction_id'] );
                $order->add_order_note( __( 'Payment completed via webhook', 'custom-payment-gateway' ) );
            }
            break;
            
        case 'payment_failed':
            $order->update_status( 'failed', __( 'Payment failed via webhook', 'custom-payment-gateway' ) );
            break;
            
        case 'payment_refunded':
            $refund_amount = isset( $data['refund_amount'] ) ? floatval( $data['refund_amount'] ) : 0;
            wc_create_refund( array(
                'order_id' => $order_id,
                'amount'   => $refund_amount,
                'reason'   => __( 'Refunded via payment gateway', 'custom-payment-gateway' ),
            ) );
            break;
    }
    
    status_header( 200 );
    exit;
}
```

## Processing Refunds

If your gateway supports refunds via API, add refund processing:

```php theme={null}
public function __construct() {
    // ... other settings ...
    
    $this->supports = array(
        'products',
        'refunds',
    );
}

/**
 * Process a refund if supported.
 *
 * @param int    $order_id Order ID.
 * @param float  $amount   Refund amount.
 * @param string $reason   Refund reason.
 * @return boolean|WP_Error True or false based on success, or a WP_Error object.
 */
public function process_refund( $order_id, $amount = null, $reason = '' ) {
    $order = wc_get_order( $order_id );
    
    if ( ! $order ) {
        return new WP_Error( 'invalid_order', __( 'Invalid order ID', 'custom-payment-gateway' ) );
    }
    
    $transaction_id = $order->get_transaction_id();
    
    if ( ! $transaction_id ) {
        return new WP_Error( 'no_transaction_id', __( 'No transaction ID found', 'custom-payment-gateway' ) );
    }
    
    try {
        // Call your payment API to process the refund
        $response = $this->refund_api_call( $transaction_id, $amount, $reason );
        
        if ( $response['success'] ) {
            $order->add_order_note(
                sprintf(
                    __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'custom-payment-gateway' ),
                    wc_price( $amount ),
                    $response['refund_id'],
                    $reason
                )
            );
            return true;
        } else {
            return new WP_Error( 'refund_failed', $response['error_message'] );
        }
    } catch ( Exception $e ) {
        return new WP_Error( 'refund_error', $e->getMessage() );
    }
}
```

## Order Status Best Practices

<Steps>
  <Step title="Payment Pending">
    Use **On-Hold** status when payment is submitted but needs manual verification or is awaiting confirmation from the payment processor.

    ```php theme={null}
    $order->update_status( 'on-hold', __( 'Awaiting payment confirmation', 'custom-payment-gateway' ) );
    ```
  </Step>

  <Step title="Payment Failed">
    Use **Failed** status when payment processing fails or is declined.

    ```php theme={null}
    $order->update_status( 'failed', __( 'Payment failed', 'custom-payment-gateway' ) );
    ```
  </Step>

  <Step title="Payment Complete">
    Use `payment_complete()` method when payment is successful. WooCommerce will automatically set the correct status (Processing or Completed) and handle stock reduction.

    ```php theme={null}
    $order->payment_complete( $transaction_id );
    ```
  </Step>
</Steps>

<Info>
  The `payment_complete()` method handles stock reduction and sets the appropriate order status automatically. Don't manually call stock reduction functions when using this method.
</Info>

## Tokenization and Saved Payment Methods

Allow customers to save payment methods for future use:

```php theme={null}
public function __construct() {
    // ... other settings ...
    
    $this->supports = array(
        'products',
        'tokenization',
        'add_payment_method',
    );
}

/**
 * Add payment method via account screen.
 */
public function add_payment_method() {
    // Get payment data from form
    $card_number = isset( $_POST['custom_card_number'] ) ? wc_clean( wp_unslash( $_POST['custom_card_number'] ) ) : '';
    
    try {
        // Tokenize the card with your payment API
        $response = $this->create_payment_token( $card_number );
        
        if ( $response['success'] ) {
            // Create WooCommerce payment token
            $token = new WC_Payment_Token_CC();
            $token->set_token( $response['token_id'] );
            $token->set_gateway_id( $this->id );
            $token->set_card_type( $response['card_type'] );
            $token->set_last4( $response['last4'] );
            $token->set_expiry_month( $response['exp_month'] );
            $token->set_expiry_year( $response['exp_year'] );
            $token->set_user_id( get_current_user_id() );
            $token->save();
            
            return array(
                'result'   => 'success',
                'redirect' => wc_get_endpoint_url( 'payment-methods' ),
            );
        } else {
            throw new Exception( $response['error_message'] );
        }
    } catch ( Exception $e ) {
        wc_add_notice( $e->getMessage(), 'error' );
        return array(
            'result' => 'failure',
        );
    }
}
```

## Testing Your Payment Gateway

<Steps>
  <Step title="Enable Test Mode">
    Implement a test mode in your gateway settings to use sandbox credentials.
  </Step>

  <Step title="Test Order Flow">
    * Create test products
    * Add to cart and proceed to checkout
    * Select your payment gateway
    * Complete payment with test credentials
    * Verify order status updates correctly
  </Step>

  <Step title="Test Refunds">
    Process test refunds from the WooCommerce order screen.
  </Step>

  <Step title="Test Webhooks">
    Use tools like ngrok to test webhook callbacks locally.
  </Step>
</Steps>

## Related Resources

* [WC\_Payment\_Gateway API Reference](https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html)
* [WooCommerce Settings API](/docs/settings-api)
* [Payment Token API](/docs/features/payment-token-api)
* [Checkout Block Payment Methods](/docs/block-development/extensible-blocks/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.