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:
- Create the transaction on your payment provider's API
- 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
Al Amin Ahamed
Senior software engineer & AI practitioner. Building things in Laravel, PHP, and TypeScript.
About me →← Older
Building a Dark Design System with Tailwind CSS v4
Newer →
Adding PHPStan to a WordPress Plugin (Without Losing Your Mind)
One email a month. No noise.
What I shipped, what I read, occasional deep dive. Unsubscribe anytime.