Back to 0fee
0fee

WHMCS, WordPress, and WooCommerce Plugins

How we built 3 platform plugins for 0fee.dev -- WHMCS, WordPress, and WooCommerce -- plus downloadable code samples in 5 languages. By Juste A. Gnimavo.

Thales & Claude | March 25, 2026 12 min 0fee
pluginswordpresswoocommercewhmcs

SDKs serve developers who write code. But a massive segment of the payments market is not writing code at all. They are running WordPress sites, WooCommerce stores, and WHMCS hosting platforms. These users need plugins -- installable packages that integrate with their existing platforms through configuration, not programming.

In Session 084, we built three complete plugins and a code samples system: approximately 2,500 lines across 11 files, all in a single session. This article covers the architecture of each plugin, the design decisions that shaped them, and the code samples template system that rounds out the developer experience.

The Three Plugins

PluginPlatformLinesFilesKey Feature
WHMCS ModuleWHMCS 8+~5502Hosted checkout + webhook verification
WordPress SimpleWordPress 5+~6003Shortcodes [zerofee_button] + [zerofee_form]
WooCommerce GatewayWooCommerce 7+~8002Full gateway + refund + HPOS compatible

All three follow the same integration pattern: redirect the customer to 0fee.dev's hosted checkout page, then receive a webhook when the payment completes. This hosted checkout approach means the plugins never handle sensitive payment data directly, which simplifies PCI compliance.

WHMCS Payment Gateway Module

WHMCS (Web Host Manager Complete Solution) is the dominant billing platform for hosting providers. It supports custom payment gateway modules through a well-defined PHP interface.

Module Structure

sdks/whmcs/
  zerofee.php              # Main gateway module (~270 lines)
  callback/zerofee.php     # Webhook receiver (~280 lines)
  README.md                # Installation and configuration guide

The Gateway Module

WHMCS gateway modules implement a set of required functions prefixed with the module name. The main file defines configuration fields and the payment link generation:

php<?php
// sdks/whmcs/zerofee.php

function zerofee_config() {
    return [
        'FriendlyName' => ['Type' => 'System', 'Value' => '0fee.dev'],
        'apiKey' => [
            'FriendlyName' => 'API Key',
            'Type' => 'text',
            'Size' => '64',
            'Description' => 'Your 0fee.dev API key (sk_live_... or sk_test_...)',
        ],
        'webhookSecret' => [
            'FriendlyName' => 'Webhook Secret',
            'Type' => 'password',
            'Size' => '64',
            'Description' => 'Your webhook signing secret for verification',
        ],
        'testMode' => [
            'FriendlyName' => 'Test Mode',
            'Type' => 'yesno',
            'Description' => 'Enable sandbox mode for testing',
        ],
    ];
}

function zerofee_link($params) {
    // Extract WHMCS invoice data
    $invoiceId = $params['invoiceid'];
    $amount = $params['amount'];
    $currency = $params['currency'];

    // Create payment via 0fee.dev API
    $response = zerofee_api_call('/v1/payments', [
        'amount' => (float) $amount,
        'source_currency' => $currency,
        'payment_reference' => 'WHMCS-INV-' . $invoiceId,
        'metadata' => [
            'whmcs_invoice_id' => $invoiceId,
            'whmcs_client_id' => $params['clientdetails']['userid'],
        ],
        'success_url' => $params['systemurl'] . '/viewinvoice.php?id=' . $invoiceId,
        'cancel_url' => $params['systemurl'] . '/viewinvoice.php?id=' . $invoiceId,
        'webhook_url' => $params['systemurl'] . '/modules/gateways/callback/zerofee.php',
    ], $params['apiKey']);

    if ($response && isset($response['checkout_url'])) {
        return '<a href="' . $response['checkout_url'] . '" class="btn btn-primary">'
             . 'Pay with 0fee.dev</a>';
    }

    return '<p>Payment gateway temporarily unavailable.</p>';
}

Webhook Callback

The callback file handles payment status updates from 0fee.dev. It verifies the webhook signature using HMAC-SHA256 before processing:

php<?php
// sdks/whmcs/callback/zerofee.php

require_once __DIR__ . '/../../../init.php';
require_once __DIR__ . '/../../../includes/gatewayfunctions.php';
require_once __DIR__ . '/../../../includes/invoicefunctions.php';

$gatewayModuleName = 'zerofee';
$gatewayParams = getGatewayVariables($gatewayModuleName);

// Read the raw webhook payload
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_ZEROFEE_SIGNATURE'] ?? '';

// Verify HMAC-SHA256 signature
$expectedSignature = hash_hmac('sha256', $payload, $gatewayParams['webhookSecret']);
if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

$event = json_decode($payload, true);

if ($event['type'] === 'payment.completed') {
    $payment = $event['data'];
    $invoiceId = $payment['metadata']['whmcs_invoice_id'] ?? null;

    if ($invoiceId) {
        // Validate the invoice exists and amount matches
        checkCbInvoiceID($invoiceId, $gatewayModuleName);
        checkCbTransID($payment['id']);

        // Apply payment to WHMCS invoice
        addInvoicePayment(
            $invoiceId,
            $payment['id'],
            $payment['amount'],
            0, // fee
            $gatewayModuleName
        );
    }
}

http_response_code(200);
echo json_encode(['status' => 'received']);

Refund Support

WHMCS supports refunds through a _refund function. When an administrator initiates a refund from the WHMCS admin panel, the module calls the 0fee.dev refund endpoint:

phpfunction zerofee_refund($params) {
    $transactionId = $params['transid'];
    $amount = $params['amount'];

    $response = zerofee_api_call(
        '/v1/payments/' . $transactionId . '/refund',
        ['amount' => (float) $amount],
        $params['apiKey']
    );

    if ($response && $response['status'] === 'refunded') {
        return [
            'status' => 'success',
            'rawdata' => $response,
            'transid' => $response['refund_id'],
        ];
    }

    return [
        'status' => 'error',
        'rawdata' => $response,
    ];
}

WordPress Simple Payment Plugin

The WordPress plugin targets site owners who want to accept payments without running an e-commerce platform. Think donation buttons, event registration fees, membership payments, or simple product purchases.

Plugin Structure

sdks/wordpress/zerofee-payments/
  zerofee-payments.php       # Main plugin file
  assets/css/zerofee.css     # Button and form styling
  assets/js/zerofee.js       # AJAX payment handling
  README.md                  # Installation guide

Shortcodes

The plugin registers two shortcodes that site owners can drop into any page or post:

php// Registration
add_shortcode('zerofee_button', 'zerofee_render_button');
add_shortcode('zerofee_form', 'zerofee_render_form');

The Button Shortcode creates a pre-configured payment button:

[zerofee_button amount="25.00" currency="USD" label="Pay $25" reference="donation"]

This renders a styled button that, when clicked, creates a payment via AJAX and redirects to the 0fee.dev hosted checkout page.

The Form Shortcode creates a payment form where the customer enters the amount:

[zerofee_form currency="USD" min="5" max="500" label="Custom Donation"]

This renders an input field for the amount plus a submit button, allowing variable-amount payments.

AJAX Payment Flow

The payment flow is entirely AJAX-based to avoid full page reloads:

javascript// assets/js/zerofee.js

document.addEventListener('click', function(e) {
    if (e.target.matches('.zerofee-pay-button')) {
        e.preventDefault();
        const button = e.target;
        const amount = button.dataset.amount;
        const currency = button.dataset.currency;
        const reference = button.dataset.reference;

        button.disabled = true;
        button.textContent = 'Processing...';

        fetch(zerofeeAjax.ajaxUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({
                action: 'zerofee_create_payment',
                nonce: zerofeeAjax.nonce,
                amount: amount,
                currency: currency,
                reference: reference
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success && data.data.checkout_url) {
                window.location.href = data.data.checkout_url;
            } else {
                alert('Payment initiation failed. Please try again.');
                button.disabled = false;
                button.textContent = button.dataset.label || 'Pay Now';
            }
        });
    }
});

Settings Page

The plugin adds a settings page under the WordPress admin menu where the site owner configures their API key:

phpfunction zerofee_settings_page() {
    ?>
    <div class="wrap">
        <h1>0fee.dev Payment Settings</h1>
        <form method="post" action="options.php">
            <?php settings_fields('zerofee_settings'); ?>
            <table class="form-table">
                <tr>
                    <th>API Key</th>
                    <td>
                        <input type="password" name="zerofee_api_key"
                               value="<?php echo esc_attr(get_option('zerofee_api_key')); ?>"
                               class="regular-text" />
                        <p class="description">
                            Enter your 0fee.dev API key. Use sk_test_... for testing.
                        </p>
                    </td>
                </tr>
                <tr>
                    <th>Webhook Secret</th>
                    <td>
                        <input type="password" name="zerofee_webhook_secret"
                               value="<?php echo esc_attr(get_option('zerofee_webhook_secret')); ?>"
                               class="regular-text" />
                    </td>
                </tr>
                <tr>
                    <th>Success Page</th>
                    <td>
                        <?php wp_dropdown_pages([
                            'name' => 'zerofee_success_page',
                            'selected' => get_option('zerofee_success_page'),
                            'show_option_none' => '-- Select Page --',
                        ]); ?>
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

WooCommerce Payment Gateway

WooCommerce is the largest e-commerce platform by market share, powering over 36% of online stores. Our WooCommerce plugin extends the WC_Payment_Gateway class to integrate natively with the WooCommerce checkout flow.

Plugin Structure

sdks/wordpress/woocommerce-zerofee/
  woocommerce-zerofee.php                         # Plugin bootstrap
  includes/class-wc-gateway-zerofee.php           # Gateway class (~500 lines)
  README.md                                        # Installation guide

Gateway Class

phpclass WC_Gateway_ZeroFee extends WC_Payment_Gateway {

    public function __construct() {
        $this->id = 'zerofee';
        $this->method_title = '0fee.dev';
        $this->method_description = 'Accept payments via 0fee.dev - 53+ providers, 200+ countries.';
        $this->has_fields = false;
        $this->supports = ['products', 'refunds'];

        $this->init_form_fields();
        $this->init_settings();

        $this->title = $this->get_option('title', '0fee.dev');
        $this->description = $this->get_option('description', 'Pay securely via 0fee.dev');
        $this->api_key = $this->get_option('api_key');
        $this->webhook_secret = $this->get_option('webhook_secret');

        add_action('woocommerce_update_options_payment_gateways_' . $this->id,
            [$this, 'process_admin_options']);
        add_action('woocommerce_api_zerofee_webhook',
            [$this, 'handle_webhook']);
    }
}

Checkout Flow

When a customer selects 0fee.dev at checkout, the process_payment method creates a payment and returns the hosted checkout URL:

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

    $payload = [
        'amount' => (float) $order->get_total(),
        'source_currency' => $order->get_currency(),
        'payment_reference' => 'WC-' . $order_id,
        'customer_email' => $order->get_billing_email(),
        'metadata' => [
            'wc_order_id' => $order_id,
            'wc_order_key' => $order->get_order_key(),
        ],
        'success_url' => $this->get_return_url($order),
        'cancel_url' => $order->get_cancel_order_url(),
        'webhook_url' => home_url('/wc-api/zerofee_webhook'),
    ];

    $response = $this->api_request('/v1/payments', $payload);

    if ($response && isset($response['checkout_url'])) {
        // Store the 0fee.dev transaction ID on the order
        $order->update_meta_data('_zerofee_transaction_id', $response['id']);
        $order->save();

        return [
            'result' => 'success',
            'redirect' => $response['checkout_url'],
        ];
    }

    wc_add_notice('Payment could not be initiated. Please try again.', 'error');
    return ['result' => 'failure'];
}

HPOS Compatibility

WooCommerce introduced High-Performance Order Storage (HPOS) as a replacement for the legacy post-based order storage. HPOS uses custom database tables for orders, which significantly improves query performance for stores with large order volumes.

Our plugin declares HPOS compatibility in the bootstrap file:

phpadd_action('before_woocommerce_init', function() {
    if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_storage',
            __FILE__,
            true
        );
    }
});

This declaration tells WooCommerce that our plugin works correctly with both legacy (post-based) and HPOS (custom table) order storage. Without this declaration, WooCommerce displays a compatibility warning in the admin panel.

Webhook-Based Order Updates

The webhook handler processes payment status changes and updates the WooCommerce order accordingly:

phppublic function handle_webhook() {
    $payload = file_get_contents('php://input');
    $signature = $_SERVER['HTTP_X_ZEROFEE_SIGNATURE'] ?? '';

    // Verify signature
    $expected = hash_hmac('sha256', $payload, $this->webhook_secret);
    if (!hash_equals($expected, $signature)) {
        wp_die('Invalid signature', 'Webhook Error', ['response' => 401]);
    }

    $event = json_decode($payload, true);
    $order_id = $event['data']['metadata']['wc_order_id'] ?? null;

    if (!$order_id) {
        wp_die('Missing order ID', 'Webhook Error', ['response' => 400]);
    }

    $order = wc_get_order($order_id);

    switch ($event['type']) {
        case 'payment.completed':
            $order->payment_complete($event['data']['id']);
            $order->add_order_note('Payment completed via 0fee.dev.');
            break;

        case 'payment.failed':
            $order->update_status('failed',
                'Payment failed via 0fee.dev: ' . ($event['data']['failure_reason'] ?? 'Unknown'));
            break;

        case 'payment.cancelled':
            $order->update_status('cancelled', 'Payment cancelled by customer.');
            break;
    }

    wp_die('OK', 'Webhook Received', ['response' => 200]);
}

Refund Support

Refunds initiated from the WooCommerce admin panel are forwarded to the 0fee.dev API:

phppublic function process_refund($order_id, $amount = null, $reason = '') {
    $order = wc_get_order($order_id);
    $transaction_id = $order->get_meta('_zerofee_transaction_id');

    if (!$transaction_id) {
        return new WP_Error('missing_txn', 'No 0fee.dev transaction ID found.');
    }

    $response = $this->api_request(
        '/v1/payments/' . $transaction_id . '/refund',
        ['amount' => (float) $amount, 'reason' => $reason]
    );

    if ($response && $response['status'] === 'refunded') {
        $order->add_order_note(sprintf(
            'Refund of %s processed via 0fee.dev. Refund ID: %s',
            wc_price($amount),
            $response['refund_id']
        ));
        return true;
    }

    return new WP_Error('refund_failed', 'Refund could not be processed.');
}

Code Samples and SDKs Dashboard

Session 084 also updated the SDKs dashboard page to include a "Code Samples" tab alongside the existing SDK listings.

Plugin Status Indicators

The SDKs page now shows status indicators for each plugin:

PluginStatusDownload
WHMCS ModuleAvailableZIP download
WordPress PluginAvailableZIP download
WooCommerce GatewayAvailableZIP download
Shopify AppComing Soon--
PrestaShop ModuleComing Soon--

Downloadable Code Samples

The code samples system generates ready-to-use integration examples that developers can download and run immediately. Each sample is self-contained with no external dependencies beyond the language's standard HTTP library.

typescript// frontend/src/utils/codeSamplesTemplate.ts

export function generateSample(
  language: string,
  apiKey: string
): { filename: string; content: string } {
  switch (language) {
    case 'html':
      return {
        filename: 'zerofee-checkout.html',
        content: `<!DOCTYPE html>
<html>
<head><title>0fee.dev Checkout</title></head>
<body>
  <button id="pay-btn">Pay $10.00</button>
  <script>
    document.getElementById('pay-btn').onclick = async () => {
      const res = await fetch('https://api.0fee.dev/v1/payments', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer ${apiKey}',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          amount: 10.00,
          source_currency: 'USD',
          payment_reference: 'HTML-SAMPLE-001'
        })
      });
      const data = await res.json();
      window.location.href = data.checkout_url;
    };
  </script>
</body>
</html>`
      };

    case 'curl':
      return {
        filename: 'zerofee-payment.sh',
        content: `#!/bin/bash
curl -X POST https://api.0fee.dev/v1/payments \\
  -H "Authorization: Bearer ${apiKey}" \\
  -H "Content-Type: application/json" \\
  -d '{
    "amount": 10.00,
    "source_currency": "USD",
    "payment_reference": "CURL-SAMPLE-001"
  }'`
      };
    // ... PHP, Python, Node.js samples
  }
}

The samples template file is approximately 920 lines, covering five languages with complete, runnable examples. Each sample includes:

  • API authentication with the developer's actual API key (pre-filled from their dashboard).
  • Payment creation with the simplified 3-field API.
  • Response handling and redirect to checkout URL.
  • Comments explaining each step.

Design Decisions

Hosted Checkout Over Direct Integration

All three plugins use the hosted checkout (redirect) flow rather than embedded payment forms. This was deliberate:

  1. PCI compliance: The merchant's server never touches card data.
  2. Maintenance: UI changes to the checkout page do not require plugin updates.
  3. Provider coverage: The hosted checkout page supports all 53+ providers automatically; an embedded form would need to be updated every time we add a provider.

WordPress and WooCommerce as Separate Plugins

We could have built a single WordPress plugin that detects WooCommerce and activates gateway functionality. We chose separate plugins because:

  • A WordPress site without WooCommerce should not load WooCommerce-specific code.
  • The WooCommerce plugin depends on WooCommerce APIs that may not exist on a plain WordPress installation.
  • Separate plugins allow independent versioning and updates.

No External Dependencies

All three plugins use PHP's built-in curl functions for HTTP requests. No Composer, no Guzzle, no external libraries. This is critical for WordPress plugins where users install via ZIP upload and expect everything to work without running composer install.

The Numbers

MetricValue
Total files created11
Total lines of code~2,500
Session time1 session
Platforms covered3 (WHMCS, WordPress, WooCommerce)
Code sample languages5 (HTML, PHP, Python, Node.js, cURL)
External dependencies0

Three platform plugins and a code samples system in a single session. This is the kind of velocity that the CEO + AI CTO model makes possible. Each plugin follows established platform conventions, implements proper security (webhook signature verification, nonce validation, input sanitization), and provides a complete integration path from installation to production.


This article is part of the "How We Built 0fee.dev" series. 0fee.dev is a payment orchestrator covering 53+ providers across 200+ countries, built by Juste A. GNIMAVO and Claude from Abidjan with zero human engineers. Follow the series for the complete build story.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles