Skip to content

Building a WooCommerce Payment Gateway from Scratch

12 min read

I've built payment gateway integrations for Paystack, GoCardless, Razorpay, and ShipStation. Here's the complete guide I wish existed when I started.

Gateway Architecture

A WooCommerce gateway is a class that extends WC_Payment_Gateway. You register it with:

add_filter('woocommerce_payment_gateways', function ($gateways) { $gateways[] = My_Gateway::class; return $gateways; });

The Four Methods You Must Implement

1. process_payment()

This is called when the customer clicks "Place Order". You must:

  1. Create the transaction on your payment provider's API
  2. Return ['result' => 'success', 'redirect' => $url] to redirect the customer
public function process_payment(int $order_id): array { $order = wc_get_order($order_id); $response = $this->api->create_transaction([ 'amount' => $order->get_total() * 100, // kobo/pence/cents 'email' => $order->get_billing_email(), 'reference' => $this->generate_reference($order_id), ]); if (! $response->success) { wc_add_notice($response->message, 'error'); return ['result' => 'failure']; } return [ 'result' => 'success', 'redirect' => $response->authorization_url, ]; }

2. Webhook Handler

Register a REST route for your webhook:

add_action('woocommerce_api_my_gateway', [$this, 'handle_webhook']);

Always verify the signature before processing:

public function handle_webhook(): void { $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] ?? ''; if (hash_hmac('sha512', $payload, $this->secret_key) !== $signature) { status_header(401); exit; } $event = json_decode($payload); // process event... }

3. process_refund()

WooCommerce calls this from the admin when a refund is issued:

public function process_refund(int $order_id, float $amount = null, string $reason = ''): bool|WP_Error { $order = wc_get_order($order_id); $transaction_id = $order->get_transaction_id(); $result = $this->api->refund($transaction_id, (int)($amount * 100)); if ($result->success) { $order->add_order_note("Refunded {$amount} via My Gateway"); return true; } return new WP_Error('refund_failed', $result->message); }

Testing Checklist

  • [ ] Successful payment completes order
  • [ ] Failed payment shows error, order stays pending
  • [ ] Webhook correctly marks order as paid
  • [ ] Refund deducts correct amount
  • [ ] Duplicate webhook delivery doesn't double-process
  • [ ] Invalid signature rejects with 401
Share X / Twitter LinkedIn
A

Al Amin Ahamed

Senior software engineer & AI practitioner. Building things in Laravel, PHP, and TypeScript.

About me →

One email a month. No noise.

What I shipped, what I read, occasional deep dive. Unsubscribe anytime.