Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

A payment method

A payment method in WooCommerce is called a payment gateway. It is a class extending WC_Payment_Gateway: it describes its settings and one main method, process_payment(), which decides what to do with the order after Place order is pressed.

A simple gateway

Let us start with payment by invoice — without calling a bank. Create the file includes/class-acme-gateway.php:

// wp-content/plugins/acme-shop/includes/class-acme-gateway.php
class Acme_Gateway extends WC_Payment_Gateway
{
    public function __construct()
    {
        $this->id = 'acme_invoice';
        $this->method_title = 'Pay by invoice';
        $this->method_description = 'The customer receives an invoice and pays by transfer.';
        $this->has_fields = false;
 
        $this->init_form_fields();
        $this->init_settings();
 
        $this->title = $this->get_option('title');
        $this->description = $this->get_option('description');
 
        add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']);
    }
 
    public function init_form_fields(): void
    {
        $this->form_fields = [
            'enabled' => ['title' => 'Enable', 'type' => 'checkbox', 'default' => 'no'],
            'title' => ['title' => 'Title', 'type' => 'text', 'default' => 'Pay by invoice'],
            'description' => ['title' => 'Description', 'type' => 'textarea', 'default' => 'The invoice will be emailed.'],
        ];
    }
 
    public function process_payment($orderId): array
    {
        $order = wc_get_order($orderId);
 
        $order->update_status('on-hold', 'Awaiting payment by invoice.');
        WC()->cart->empty_cart();
 
        return [
            'result' => 'success',
            'redirect' => $this->get_return_url($order),
        ];
    }
}

The constructor sets the ID and reads the settings. The form_fields array becomes the gateway's settings page, and the add_action line saves it. process_payment() puts the order on hold, empties the cart and returns the address of the Order received page.

Registration

// wp-content/plugins/acme-shop/acme-shop.php
add_filter('woocommerce_payment_gateways', function (array $gateways): array {
    require_once __DIR__ . '/includes/class-acme-gateway.php';
 
    $gateways[] = Acme_Gateway::class;
 
    return $gateways;
});

Open WooCommerce → Settings → Payments: Pay by invoice has appeared in the list. Enable it — the method becomes available on the classic checkout page.

What process_payment returns

KeyValue
resultsuccess or failure
redirectWhere to send the customer on success

On failure call wc_add_notice('Error text', 'error') and return ['result' => 'failure'] — the customer stays on the checkout page.

Paying through a bank

A real gateway creates a payment at the payment service and sends the customer to its page. Add the field 'api_key' => ['title' => 'API key', 'type' => 'password'] to form_fields and replace the method:

// wp-content/plugins/acme-shop/includes/class-acme-gateway.php
public function process_payment($orderId): array
{
    $order = wc_get_order($orderId);
 
    $response = wp_remote_post('https://pay.example.com/v1/payments', [
        'headers' => ['Authorization' => 'Bearer ' . $this->get_option('api_key')],
        'body' => [
            'amount' => $order->get_total(),
            'currency' => $order->get_currency(),
            'order' => $order->get_id(),
            'return_url' => $this->get_return_url($order),
        ],
    ]);
 
    if (is_wp_error($response)) {
        wc_add_notice('Could not reach the bank. Please try again.', 'error');
 
        return ['result' => 'failure'];
    }
 
    $payment = json_decode(wp_remote_retrieve_body($response), true);
 
    $order->update_meta_data('_acme_payment_id', $payment['id']);
    $order->save();
 
    return ['result' => 'success', 'redirect' => $payment['checkout_url']];
}

The order stays in Pending payment, the payment ID is saved in it, and the customer goes to the bank page. Every service has its own address and request fields — here they are made up.

The bank's notification

The bank reports a payment with a separate request to the site. WooCommerce has a ready-made address for it, /wc-api/acme_invoice/:

// wp-content/plugins/acme-shop/includes/orders.php
add_action('woocommerce_api_acme_invoice', function () {
    $payload = json_decode(file_get_contents('php://input'), true);
 
    $orders = wc_get_orders([
        'limit' => 1,
        'meta_query' => [['key' => '_acme_payment_id', 'value' => $payload['id'] ?? '']],
    ]);
 
    if ($orders && ($payload['status'] ?? '') === 'paid') {
        $orders[0]->payment_complete($payload['id']);
    }
 
    status_header(200);
    exit;
});

payment_complete() moves the order to Processing, reduces stock and triggers emails. Before that the request signature must be verified the way the bank's documentation describes — otherwise anyone who knows the address can pay for an order.

The block checkout

In the block checkout the gateway does not appear by itself: it has to be registered on the JavaScript side as well. Until that is done, use the classic page with the [woocommerce_checkout] shortcode.

Common mistake

An order is marked as paid in process_payment() or on the return page — before the bank's notification. The customer closed the tab or forged the return address, and the goods leave without money. Only a verified notification makes an order paid. And card data must not pass through your server: it is entered on the payment service's page.

Thanks for reading!

Donate to the site — we work hard for you.