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
| Plugin | Platform | Lines | Files | Key Feature |
|---|---|---|---|---|
| WHMCS Module | WHMCS 8+ | ~550 | 2 | Hosted checkout + webhook verification |
| WordPress Simple | WordPress 5+ | ~600 | 3 | Shortcodes [zerofee_button] + [zerofee_form] |
| WooCommerce Gateway | WooCommerce 7+ | ~800 | 2 | Full 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 guideThe 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 guideShortcodes
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 guideGateway 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:
| Plugin | Status | Download |
|---|---|---|
| WHMCS Module | Available | ZIP download |
| WordPress Plugin | Available | ZIP download |
| WooCommerce Gateway | Available | ZIP download |
| Shopify App | Coming Soon | -- |
| PrestaShop Module | Coming 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:
- PCI compliance: The merchant's server never touches card data.
- Maintenance: UI changes to the checkout page do not require plugin updates.
- 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
| Metric | Value |
|---|---|
| Total files created | 11 |
| Total lines of code | ~2,500 |
| Session time | 1 session |
| Platforms covered | 3 (WHMCS, WordPress, WooCommerce) |
| Code sample languages | 5 (HTML, PHP, Python, Node.js, cURL) |
| External dependencies | 0 |
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.