
WooCommerce Payment Gateway Development — Complete 2026 Technical Guide
Deep technical guide to building a production-ready WooCommerce custom payment gateway using WC_Payment_Gateway, REST APIs, Blocks compatibility, secure SHA-512 hashing and webhook validation — covering PayU, Razorpay & UPI integrations.

Santosh Gautam
Full Stack Developer · India
1. Understanding WooCommerce Payment Gateway Architecture
Before building, understand how WooCommerce handles payment flow. Every custom gateway must integrate with the WC_Payment_Gateway base class and follow this lifecycle:
Customer → Checkout
↓
WC_Payment_Gateway::process_payment()
↓
Redirect / API Request to Payment Provider
↓
Payment Provider Processes Transaction
↓
Webhook Callback → Your REST Endpoint
↓
Order Status Update (Pending → Processing → Completed)- WooCommerce expects a structured response from
process_payment(). - Order state management is critical — Pending → Processing → Completed.
- Session integrity must be maintained during redirection.
- Webhooks guarantee asynchronous confirmation — critical for reliability.
- Security hash validation must occur before order completion.
Always handle the case where a user closes their browser after payment — webhooks ensure order completion even without a browser callback.
2. Extending WC_Payment_Gateway Class
The foundation of any WooCommerce payment gateway is extending the WC_Payment_Gateway abstract class and implementing the required methods.
class WC_Custom_Gateway extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'custom_gateway';
$this->method_title = 'Custom Gateway';
$this->method_description = 'Pay securely via Custom Gateway';
$this->has_fields = true;
$this->supports = ['products', 'refunds'];
// Load settings
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option('title');
$this->description = $this->get_option('description');
// Save settings hook
add_action(
'woocommerce_update_options_payment_gateways_' . $this->id,
[$this, 'process_admin_options']
);
}
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
// Reduce stock, mark pending
wc_reduce_stock_levels( $order_id );
$order->update_status( 'pending', 'Awaiting payment confirmation.' );
return [
'result' => 'success',
'redirect' => $this->get_return_url( $order ),
];
}
}- Extend
WC_Payment_Gatewaybase class. - Define unique
$this->id— used for settings and hooks. - Register admin settings with
init_form_fields(). - Implement
process_payment()with correct return array. - Declare
supportsarray for refunds and product types.
3. Secure Hashing & Checksum Validation (SHA-512)
Payment providers like PayU, Razorpay and Cashfree require a secure hash for request integrity — preventing tampering between your server and the payment gateway.
// Generate hash for payment request
function generate_payment_hash( $merchant_key, $txn_id, $amount, $email, $secret ) {
$hash_string = implode('|', [
$merchant_key,
$txn_id,
$amount,
'Product Name',
$email,
$secret
]);
return strtolower( hash( 'sha512', $hash_string ) );
}
// Validate webhook hash from payment provider
function validate_webhook_hash( $response, $secret ) {
$received_hash = $response['hash'];
$computed_hash = generate_payment_hash(
$response['key'], $response['txnid'],
$response['amount'], $response['email'], $secret
);
return hash_equals( $computed_hash, $received_hash );
}- SHA-512 — prevents request tampering in transit.
- Validates integrity between your server and payment gateway.
- Mandatory for PayU India, Razorpay and UPI integrations.
- Use
hash_equals()for timing-safe comparison — prevents timing attacks. - Secret key must never be exposed in frontend or logs.
Never use == for hash comparison — always use hash_equals() which is timing-safe and prevents timing-based attacks.
4. Handling Webhooks (IPN — Instant Payment Notification)
Webhooks are the backbone of reliable payment processing. Register a REST API endpoint to receive asynchronous payment confirmations from the gateway.
// Register REST endpoint for webhook
add_action('rest_api_init', function () {
register_rest_route('custom-gateway/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'handle_payment_webhook',
'permission_callback' => '__return_true',
]);
});
function handle_payment_webhook( WP_REST_Request $request ) {
$data = $request->get_json_params() ?: $request->get_body_params();
// 1. Validate hash
if ( ! validate_webhook_hash( $data, get_option('wc_custom_secret') ) ) {
return new WP_REST_Response(['status' => 'invalid_hash'], 400);
}
// 2. Get WooCommerce order
$order = wc_get_order( sanitize_text_field( $data['order_id'] ) );
if ( ! $order ) {
return new WP_REST_Response(['status' => 'order_not_found'], 404);
}
// 3. Update order status
if ( $data['status'] === 'success' ) {
$order->payment_complete( $data['txn_id'] );
$order->add_order_note('Payment confirmed via webhook. TXN: ' . $data['txn_id']);
} else {
$order->update_status('failed', 'Payment failed via webhook.');
}
return new WP_REST_Response(['status' => 'ok'], 200);
}- Registers a custom REST endpoint at
/wp-json/custom-gateway/v1/webhook. - Always validate hash before processing — reject invalid requests.
- Use
payment_complete()to properly finalize WooCommerce order. - Add order notes for transaction ID tracking and debugging.
- Ensures payment success even if user closes browser after payment.
5. WooCommerce Blocks Compatibility
WooCommerce is migrating to a React-based Checkout Block. Legacy PHP templates won't render in the new checkout. You must register a JavaScript payment method.
// register-payment-method.js
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { decodeEntities } from '@wordpress/html-entities';
const settings = window.wcSettings['custom_gateway_data'] ?? {};
registerPaymentMethod({
name: 'custom_gateway',
label: decodeEntities( settings.title ) || 'Custom Gateway',
content: { decodeEntities( settings.description ) },
edit: Custom Gateway payment method,
canMakePayment: () => true,
ariaLabel: 'Custom Gateway payment method',
supports: {
features: settings.supports ?? [],
},
});// PHP — Declare Blocks compatibility
class WC_Custom_Gateway_Blocks extends AbstractPaymentMethodType {
protected $name = 'custom_gateway';
public function initialize() {
$this->settings = get_option('woocommerce_custom_gateway_settings', []);
}
public function is_active() {
return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'];
}
public function get_payment_method_script_handles() {
wp_register_script(
'custom-gateway-blocks',
plugin_dir_url(__FILE__) . 'build/index.js',
['wc-blocks-registry', 'wc-settings'],
null, true
);
return ['custom-gateway-blocks'];
}
}- Required for new React-based WooCommerce Checkout Block.
- Legacy PHP checkout templates won't render in Blocks checkout.
- Ensures future WooCommerce compatibility as classic checkout is deprecated.
- Declare support using
AbstractPaymentMethodType.
6. Deployment & Production Checklist
Test in Sandbox Mode
Full payment flow test before going live.
Validate Checksum Logic
SHA-512 hash verified on all requests.
Whitelist Webhook IPs
Only accept from payment provider IPs.
Enable Logging in Testing
WooCommerce logs help debug webhook issues.
Disable Debug Logs in Prod
Logs expose sensitive data in production.
Confirm Refund Callback
Test partial & full refund flow.
Blocks Checkout Tested
Verify payment renders in React Checkout Block.
HTTPS Enforced
Never use HTTP for payment endpoints.
7. Frequently Asked Questions
Yes. Modern WooCommerce gateways must follow strict typing and PHP 8+ compatibility. Use typed properties, return types and avoid deprecated functions.