Gateways

Building a Custom Gateway

How to register a custom payment gateway using the inzc_payment_gateways filter, the Gateway_Interface contract, and automatic webhook endpoint registration with a complete, fully coded example.

Influenzic’s gateway registry is fully extensible. Any developer can register a custom gateway by filtering inzc_payment_gateways and providing a descriptor array. No core files need to be edited, and webhook REST endpoints are auto-registered from the descriptor — you do not need to call register_rest_route() yourself.

The Registry Filter

The filter fires at plugins_loaded priority 20. Addon plugins that hook at the default priority (10) are always registered in time.

add_filter( 'inzc_payment_gateways', function( array $gateways ): array {

    $gateways['my_gateway'] = [
        'id'           => 'my_gateway',
        'label'        => __( 'My Gateway', 'my-addon' ),
        'class'        => My_AddonMy_Gateway::class,
        'webhook_slug' => 'my-gateway',   // URL-safe [a-z0-9_-]
        'redux_fields' => [
            [
                'id'      => 'inzc_my_gateway_api_key',
                'type'    => 'password',
                'title'   => 'API Key',
                'default' => '',
            ],
            [
                'id'      => 'inzc_my_gateway_webhook_secret',
                'type'    => 'password',
                'title'   => 'Webhook Secret',
                'default' => '',
            ],
        ],
    ];

    return $gateways;

} );

Descriptor Keys

Key Required Description
id Yes Unique gateway slug. Used as the array key, in database references, and as the gateway parameter in REST/AJAX calls.
label Yes Human-readable name shown in the settings panel and on the checkout page.
class Yes Fully-qualified class name implementing InfluenzicApiGatewaysGateway_Interface.
webhook_slug Yes Appended to /influenzic/v1/webhook/{slug} for the auto-registered webhook endpoint. Must be URL-safe.
redux_fields Yes Array of Redux Framework field arrays. These are rendered automatically in Influenzic → Settings under a tab named after your gateway label. Pass an empty array if no settings are needed.

The Gateway Interface

Your class must implement InfluenzicApiGatewaysGateway_Interface. Below is a complete, fully implemented example showing how to retrieve settings, perform an API request to a gateway, handle webhook validations, verify HMAC signatures, and dispatch payment triggers.

namespace My_Addon;

use InfluenzicApiGatewaysGateway_Interface;

class My_Gateway implements Gateway_Interface {

    /**
     * Retrieve gateway options from the Redux framework settings.
     */
    private function get_api_key(): string {
        // Use the inzc_get_option helper function to retrieve options configured via redux_fields
        return (string) inzc_get_option( 'inzc_my_gateway_api_key', '' );
    }

    private function get_webhook_secret(): string {
        return (string) inzc_get_option( 'inzc_my_gateway_webhook_secret', '' );
    }

    /**
     * Initiate a payment. Called at checkout time.
     * $order_data contains: brand_id, campaign_id, campaign_title, batch_id,
     *   offers, totals (subtotal/fee/total), payment_type (e.g. checkout, topup),
     *   cancel_url, return_url.
     * Return an array or WP_Error.
     */
    public function initiate( array $order_data ) {
        $total        = $order_data['totals']['total'];
        $batch_id     = $order_data['batch_id'];
        $payment_type = $order_data['payment_type'];

        // Build request payload for the external gateway API
        $payload = [
            'amount'      => $total, // amount in minor units
            'currency'    => get_option( 'inzc_currency', 'USD' ),
            'reference'   => $batch_id,
            'callback'    => $order_data['return_url'],
            'metadata'    => [
                'inzc_payment_type' => $payment_type, // IMPORTANT: required for dispatcher
                'inzc_batch_id'     => $batch_id,
                'brand_id'          => $order_data['brand_id'],
            ]
        ];

        // Perform the API post request
        $response = wp_remote_post( 'https://api.mygateway.example/v1/charges', [
            'body'    => wp_json_encode( $payload ),
            'headers' => [
                'Authorization' => 'Bearer ' . $this->get_api_key(),
                'Content-Type'  => 'application/json'
            ]
        ] );

        if ( is_wp_error( $response ) ) {
            return $response; // Return WP_Error to show standard checkout error notice
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( empty( $body['status'] ) || 'success' !== $body['status'] ) {
            return new WP_Error( 'gateway_initiate_failed', $body['message'] ?? 'Failed to connect to provider.' );
        }

        return [
            'success'      => true,
            'gateway'      => 'my_gateway',
            'redirect_url' => $body['checkout_url'],
            'order_ref'    => $batch_id,
        ];
    }

    /**
     * Verify a payment reference after redirect or in a webhook.
     * Return true only when the gateway confirms the charge succeeded.
     */
    public function verify( string $reference ): bool {
        $response = wp_remote_get( "https://api.mygateway.example/v1/charges/{$reference}", [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->get_api_key()
            ]
        ] );

        if ( is_wp_error( $response ) ) {
            return false;
        }

        $body = json_decode( wp_remote_retrieve_body( $response ), true );
        return ! empty( $body['paid'] ) && true === $body['paid'];
    }

    /**
     * Handle an inbound webhook from the gateway.
     * 1. Verify the payload signature using the webhook secret.
     * 2. Extract 'inzc_payment_type' from the metadata stored at initiate() time.
     * 3. Call inzc_dispatch_payment_complete( $payment_type, $metadata, $totals ).
     */
    public function handle_webhook( WP_REST_Request $request ): WP_REST_Response {
        $signature = $request->get_header( 'X-MyGateway-Signature' );
        $payload   = $request->get_body();

        // 1. Verify webhook signature (HMAC SHA256)
        $expected = hash_hmac( 'sha256', $payload, $this->get_webhook_secret() );
        if ( ! hash_equals( $expected, (string) $signature ) ) {
            return new WP_REST_Response( ['error' => 'Invalid signature'], 400 );
        }

        $data = json_decode( $payload, true );
        if ( empty( $data['metadata']['inzc_payment_type'] ) ) {
            return new WP_REST_Response( ['error' => 'Missing payment type metadata'], 400 );
        }

        // 2. Extracted values
        $payment_type = $data['metadata']['inzc_payment_type']; // "checkout" or "topup"
        $metadata     = $data['metadata'];
        $totals       = [
            'total' => $data['amount'] // amount received in minor units
        ];

        // 3. Dispatch the complete operation
        // The dispatcher handles invoice/escrow routing automatically based on payment_type
        inzc_dispatch_payment_complete( $payment_type, $metadata, $totals );

        return new WP_REST_Response( ['received' => true], 200 );
    }

    /**
     * Return the ISO 4217 currency codes this gateway can process.
     * Return an empty array to indicate no restriction (accepts all currencies).
     */
    public function supported_currencies(): array {
        return ['NGN', 'GHS', 'ZAR', 'USD'];
    }
}
Always verify webhook signatures. Before acting on a webhook payload, verify the request signature using your gateway’s HMAC or public-key method. Return a 400 response on failure to prevent forged events from crediting wallets.
Webhook endpoint is auto-registered. Influenzic registers /wp-json/influenzic/v1/webhook/my-gateway automatically using the webhook_slug from your descriptor. Do not call register_rest_route() yourself — the Webhook_Controller handles all gateway routing.

Currency Filtering

The registry uses supported_currencies() to automatically exclude gateways that cannot process the operator’s configured platform currency. An operator running NGN will not see Stripe-only gateways if you restrict yours to NGN. Return an empty array ([]) if your gateway accepts all currencies (as Stripe does).

Subscription Support (Optional)

If your gateway supports recurring billing, also implement:

  • charge_renewal( string $gateway_subscription_id, float $amount, array $context ) — called by the subscription cron on renewal dates
  • cancel_subscription( string $gateway_subscription_id ) — called when a brand cancels their plan

Gateways that do not support subscriptions may return a WP_Error with code gateway_manual_renewal from charge_renewal() to trigger the manual-renewal fallback flow.

Was this article helpful?