PK œqhYî¶J‚ßFßF)nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/ $#$#$#

Dir : /home2/medicu/.trash/cartflows.13/modules/webhook/classes/
Server: Linux tista.bd.svlogins.com 5.14.0-611.49.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Apr 21 16:39:08 EDT 2026 x86_64
IP: 103.159.37.114
Choose File :

Url:
Dir : /home2/medicu/.trash/cartflows.13/modules/webhook/classes/class-cartflows-webhook-dispatcher.php

<?php
/**
 * CartFlows Webhook Dispatcher.
 *
 * Core delivery engine. Schedules async webhook delivery via Action Scheduler.
 *
 * @package CartFlows
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'Cartflows_Webhook_Dispatcher' ) ) {

	/**
	 * Class Cartflows_Webhook_Dispatcher.
	 */
	class Cartflows_Webhook_Dispatcher {

		/**
		 * Action Scheduler hook name.
		 *
		 * @var string
		 */
		const AS_HOOK = 'cartflows_process_webhook_delivery';

		/**
		 * Retry backoff intervals in seconds.
		 *
		 * @var array
		 */
		const RETRY_INTERVALS = array( 60, 300, 1800, 3600, 7200 );

		/**
		 * Member Variable
		 *
		 * @var instance
		 */
		private static $instance = null;

		/**
		 * Initiator
		 *
		 * @return object initialized object of class.
		 */
		public static function get_instance() {
			if ( is_null( self::$instance ) ) {
				self::$instance = new self();
			}
			return self::$instance;
		}

		/**
		 * Constructor
		 */
		public function __construct() {
			add_action( self::AS_HOOK, array( $this, 'process_webhook_delivery' ), 10, 4 );
		}

		/**
		 * Dispatch webhooks for an event.
		 *
		 * Finds all enabled webhooks for the event and schedules Action Scheduler jobs.
		 *
		 * @param string $event Event name.
		 * @param array  $context Event context data.
		 * @return void
		 */
		public function dispatch( $event, $context = array() ) {
			$manager  = Cartflows_Webhook_Manager::get_instance();
			$webhooks = $manager->get_webhooks_for_event( $event );

			if ( empty( $webhooks ) ) {
				wcf()->logger->log( 'Webhook dispatch: No webhooks found for event ' . $event );
				return;
			}

			$object_id = isset( $context['order_id'] ) ? $context['order_id'] : ( isset( $context['step_id'] ) ? $context['step_id'] : 0 );

			foreach ( $webhooks as $webhook ) {
				// Deduplication check.
				$dedup_key = 'wcf_wh_' . md5( $webhook['id'] . '_' . $event . '_' . $object_id );

				if ( get_transient( $dedup_key ) ) {
					wcf()->logger->log( 'Webhook dispatch: Skipping duplicate for webhook ' . $webhook['id'] . ' event ' . $event );
					continue;
				}

				// Set dedup transient for 60 seconds.
				set_transient( $dedup_key, 1, 60 );

				$payload_builder = Cartflows_Webhook_Payload::get_instance();
				$payload         = $payload_builder->build( $event, $context, $webhook['id'] );
				$body            = wp_json_encode( $payload );

				wcf()->logger->log( 'Webhook dispatch: Scheduling delivery for webhook ' . $webhook['id'] . ' event ' . $event );

				// Schedule async delivery via Action Scheduler.
				if ( function_exists( 'as_schedule_single_action' ) ) {
					as_schedule_single_action(
						time(),
						self::AS_HOOK,
						array(
							'webhook_id'    => $webhook['id'],
							'body'          => $body,
							'event'         => $event,
							'retry_attempt' => 0,
						),
						'cartflows-webhooks'
					);
				}
			}
		}

		/**
		 * Process a webhook delivery (called by Action Scheduler).
		 *
		 * @param string $webhook_id Webhook ID.
		 * @param string $body JSON payload body.
		 * @param string $event Event name.
		 * @param int    $retry_attempt Current retry attempt.
		 * @return void
		 */
		public function process_webhook_delivery( $webhook_id, $body, $event, $retry_attempt = 0 ) {
			$manager = Cartflows_Webhook_Manager::get_instance();
			$webhook = $manager->get_webhook( $webhook_id );

			if ( ! $webhook ) {
				wcf()->logger->log( 'Webhook delivery: Webhook not found - ' . $webhook_id );
				return;
			}

			if ( empty( $webhook['enabled'] ) ) {
				wcf()->logger->log( 'Webhook delivery: Webhook disabled, skipping - ' . $webhook_id );
				return;
			}

			$url    = $webhook['url'];
			$secret = isset( $webhook['secret'] ) ? $webhook['secret'] : '';

			// Generate HMAC signature.
			$signature = hash_hmac( 'sha256', $body, $secret );

			$headers = array(
				'Content-Type'                  => 'application/json',
				'X-CartFlows-Webhook-Signature' => $signature,
				'X-CartFlows-Event'             => $event,
				'X-CartFlows-Delivery-ID'       => wp_generate_uuid4(),
				'User-Agent'                    => 'CartFlows-Webhook/1.0',
			);

			wcf()->logger->log( 'Webhook delivery: Sending to ' . $url . ' for event ' . $event . ' (attempt ' . ( $retry_attempt + 1 ) . ')' );

			$response = wp_safe_remote_post(
				$url,
				array(
					'body'      => $body,
					'headers'   => $headers,
					'timeout'   => 3,
					'sslverify' => true,
				)
			);

			if ( is_wp_error( $response ) ) {
				$error_message = $response->get_error_message();
				wcf()->logger->log( 'Webhook delivery failed: ' . $error_message . ' - Webhook: ' . $webhook_id );

				$manager->update_last_delivery(
					$webhook_id,
					array(
						'status'       => 'failed',
						'triggered_at' => current_time( 'mysql' ),
						'error'        => $error_message,
					)
				);

				$this->maybe_schedule_retry( $webhook_id, $body, $event, $retry_attempt, $webhook );
				return;
			}

			$response_code = wp_remote_retrieve_response_code( $response );

			if ( $response_code >= 200 && $response_code < 300 ) {
				wcf()->logger->log( 'Webhook delivery success: HTTP ' . $response_code . ' - Webhook: ' . $webhook_id );

				$manager->update_last_delivery(
					$webhook_id,
					array(
						'status'       => 'success',
						'triggered_at' => current_time( 'mysql' ),
						'error'        => '',
					)
				);
			} else {
				$error_message = 'HTTP ' . $response_code . ': ' . wp_remote_retrieve_response_message( $response );
				wcf()->logger->log( 'Webhook delivery failed: ' . $error_message . ' - Webhook: ' . $webhook_id );

				$manager->update_last_delivery(
					$webhook_id,
					array(
						'status'       => 'failed',
						'triggered_at' => current_time( 'mysql' ),
						'error'        => $error_message,
					)
				);

				$this->maybe_schedule_retry( $webhook_id, $body, $event, $retry_attempt, $webhook );
			}
		}

		/**
		 * Schedule a retry if within max retries.
		 *
		 * @param string $webhook_id Webhook ID.
		 * @param string $body JSON payload body.
		 * @param string $event Event name.
		 * @param int    $retry_attempt Current retry attempt.
		 * @param array  $webhook Webhook configuration.
		 * @return void
		 */
		private function maybe_schedule_retry( $webhook_id, $body, $event, $retry_attempt, $webhook ) {
			$max_retries  = isset( $webhook['max_retries'] ) ? absint( $webhook['max_retries'] ) : 3;
			$next_attempt = $retry_attempt + 1;

			if ( $next_attempt >= $max_retries ) {
				wcf()->logger->log( 'Webhook delivery: Max retries reached (' . $max_retries . ') for webhook ' . $webhook_id . '. Giving up.' );
				return;
			}

			$delay = isset( self::RETRY_INTERVALS[ $retry_attempt ] ) ? self::RETRY_INTERVALS[ $retry_attempt ] : 7200;

			wcf()->logger->log( 'Webhook delivery: Scheduling retry ' . $next_attempt . ' in ' . $delay . 's for webhook ' . $webhook_id );

			if ( function_exists( 'as_schedule_single_action' ) ) {
				as_schedule_single_action(
					time() + $delay,
					self::AS_HOOK,
					array(
						'webhook_id'    => $webhook_id,
						'body'          => $body,
						'event'         => $event,
						'retry_attempt' => $next_attempt,
					),
					'cartflows-webhooks'
				);
			}
		}

		/**
		 * Send a test webhook synchronously.
		 *
		 * @param string $webhook_id Webhook ID.
		 * @return array Result with 'success', 'response_code', and 'error' keys.
		 */
		public function send_test( $webhook_id ) {
			$manager = Cartflows_Webhook_Manager::get_instance();
			$webhook = $manager->get_webhook( $webhook_id );

			if ( ! $webhook ) {
				return array(
					'success'       => false,
					'response_code' => 0,
					'error'         => __( 'Webhook not found.', 'cartflows' ),
				);
			}

			$payload_builder = Cartflows_Webhook_Payload::get_instance();
			$payload         = $payload_builder->build_test( $webhook_id );
			$body            = wp_json_encode( $payload );
			$secret          = isset( $webhook['secret'] ) ? $webhook['secret'] : '';
			$signature       = hash_hmac( 'sha256', $body, $secret );

			$headers = array(
				'Content-Type'                  => 'application/json',
				'X-CartFlows-Webhook-Signature' => $signature,
				'X-CartFlows-Event'             => 'test',
				'X-CartFlows-Delivery-ID'       => wp_generate_uuid4(),
				'User-Agent'                    => 'CartFlows-Webhook/1.0',
			);

			wcf()->logger->log( 'Webhook test: Sending test to ' . $webhook['url'] );

			$response = wp_safe_remote_post(
				$webhook['url'],
				array(
					'body'      => $body,
					'headers'   => $headers,
					'timeout'   => 3,
					'sslverify' => true,
				)
			);

			if ( is_wp_error( $response ) ) {
				$error_message = $response->get_error_message();
				wcf()->logger->log( 'Webhook test failed: ' . $error_message );

				$manager->update_last_delivery(
					$webhook_id,
					array(
						'status'       => 'failed',
						'triggered_at' => current_time( 'mysql' ),
						'error'        => $error_message,
					)
				);

				return array(
					'success'       => false,
					'response_code' => 0,
					'error'         => $error_message,
				);
			}

			$response_code = wp_remote_retrieve_response_code( $response );

			$manager->update_last_delivery(
				$webhook_id,
				array(
					'status'       => ( $response_code >= 200 && $response_code < 300 ) ? 'success' : 'failed',
					'triggered_at' => current_time( 'mysql' ),
					'error'        => ( $response_code >= 200 && $response_code < 300 ) ? '' : 'HTTP ' . $response_code,
				)
			);

			wcf()->logger->log( 'Webhook test result: HTTP ' . $response_code );

			return array(
				'success'       => ( $response_code >= 200 && $response_code < 300 ),
				'response_code' => $response_code,
				'error'         => ( $response_code >= 200 && $response_code < 300 ) ? '' : 'HTTP ' . $response_code,
			);
		}
	}
}