File "IncomingWebhookEndpoint.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/woocommerce-paypal-payments/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php
File size: 7.54 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Controls the endpoint for the incoming webhooks.
*
* @package WooCommerce\PayPalCommerce\Webhooks
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks;
use phpDocumentor\Reflection\Types\This;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandler;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
/**
* Class IncomingWebhookEndpoint
*/
class IncomingWebhookEndpoint {
use RequestHandlerTrait;
const NAMESPACE = 'paypal/v1';
const ROUTE = 'incoming';
/**
* The Webhook endpoint.
*
* @var WebhookEndpoint
*/
private $webhook_endpoint;
/**
* Our registered webhook.
*
* @var Webhook|null
*/
private $webhook;
/**
* The Request handlers.
*
* @var RequestHandler[]
*/
private $handlers;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* Whether requests need to be verified.
*
* @var bool
*/
private $verify_request;
/**
* The webhook event factory.
*
* @var WebhookEventFactory
*/
private $webhook_event_factory;
/**
* The simulation handler.
*
* @var WebhookSimulation
*/
private $simulation;
/**
* The last webhook event storage.
*
* @var WebhookEventStorage
*/
private $last_webhook_event_storage;
/**
* Cached webhook verification results
* to avoid repeating requests when permission_callback is called multiple times.
*
* @var array<string, bool>
*/
private $verification_results = array();
/**
* IncomingWebhookEndpoint constructor.
*
* @param WebhookEndpoint $webhook_endpoint The webhook endpoint.
* @param Webhook|null $webhook Our registered webhook.
* @param LoggerInterface $logger The logger.
* @param bool $verify_request Whether requests need to be verified or not.
* @param WebhookEventFactory $webhook_event_factory The webhook event factory.
* @param WebhookSimulation $simulation The simulation handler.
* @param WebhookEventStorage $last_webhook_event_storage The last webhook event storage.
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
*/
public function __construct(
WebhookEndpoint $webhook_endpoint,
?Webhook $webhook,
LoggerInterface $logger,
bool $verify_request,
WebhookEventFactory $webhook_event_factory,
WebhookSimulation $simulation,
WebhookEventStorage $last_webhook_event_storage,
RequestHandler ...$handlers
) {
$this->webhook_endpoint = $webhook_endpoint;
$this->webhook = $webhook;
$this->handlers = $handlers;
$this->logger = $logger;
$this->verify_request = $verify_request;
$this->webhook_event_factory = $webhook_event_factory;
$this->last_webhook_event_storage = $last_webhook_event_storage;
$this->simulation = $simulation;
}
/**
* Registers the endpoint.
*
* @return bool
*/
public function register(): bool {
return (bool) register_rest_route(
self::NAMESPACE,
self::ROUTE,
array(
'methods' => array(
'POST',
),
'callback' => array(
$this,
'handle_request',
),
'permission_callback' => array(
$this,
'verify_request',
),
)
);
}
/**
* Verifies the current request.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function verify_request( \WP_REST_Request $request ): bool {
if ( ! $this->verify_request ) {
return true;
}
if ( ! $this->webhook ) {
$this->logger->error( 'Failed to retrieve stored webhook data.' );
return false;
}
try {
$event = $this->event_from_request( $request );
} catch ( RuntimeException $exception ) {
$this->logger->error( 'Webhook parsing failed: ' . $exception->getMessage() );
return false;
}
$cache_key = $event->id();
if ( isset( $this->verification_results[ $cache_key ] ) ) {
return $this->verification_results[ $cache_key ];
}
try {
if ( $this->simulation->is_simulation_event( $event ) ) {
return true;
}
$result = $this->webhook_endpoint->verify_current_request_for_webhook( $this->webhook );
if ( ! $result ) {
$this->logger->error( 'Webhook verification failed.' );
}
$this->verification_results[ $cache_key ] = $result;
return $result;
} catch ( RuntimeException $exception ) {
$this->logger->error( 'Webhook verification failed: ' . $exception->getMessage() );
$this->verification_results[ $cache_key ] = false;
return false;
}
}
/**
* Handles the request.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$event = $this->event_from_request( $request );
$this->logger->debug(
sprintf(
'Webhook %s received of type %s and by resource "%s"',
$event->id(),
$event->event_type(),
$event->resource_type()
)
);
$this->last_webhook_event_storage->save( $event );
if ( $this->simulation->is_simulation_event( $event ) ) {
$this->logger->info( 'Received simulated webhook.' );
$this->simulation->receive( $event );
return $this->success_response();
}
foreach ( $this->handlers as $handler ) {
if ( $handler->responsible_for_request( $request ) ) {
$event_type = ( $handler->event_types() ? current( $handler->event_types() ) : '' ) ?: '';
$this->logger->debug(
sprintf(
'Webhook is going to be handled by %s on %s',
$event_type,
get_class( $handler )
)
);
$response = $handler->handle_request( $request );
$this->logger->info(
sprintf(
'Webhook has been handled by %s on %s',
$event_type,
get_class( $handler )
)
);
return $response;
}
}
$event_type = $request['event_type'] ?: '';
if ( in_array( $event_type, array( 'BILLING_AGREEMENTS.AGREEMENT.CREATED' ), true ) ) {
return $this->success_response();
}
$message = sprintf(
'Could not find handler for request type %s',
$event_type
);
return $this->failure_response( $message );
}
/**
* Returns the URL to the endpoint.
*
* @return string
*/
public function url(): string {
$url = rest_url( self::NAMESPACE . '/' . self::ROUTE );
$url = str_replace( 'http://', 'https://', $url );
$ngrok_host = getenv( 'NGROK_HOST' );
if ( $ngrok_host ) {
$host = wp_parse_url( $url, PHP_URL_HOST );
if ( $host ) {
$url = str_replace( $host, $ngrok_host, $url );
}
}
return $url;
}
/**
* Returns the event types, which are handled by the endpoint.
*
* @return string[]
*/
public function handled_event_types(): array {
$event_types = array();
foreach ( $this->handlers as $handler ) {
$event_types = array_merge( $event_types, $handler->event_types() );
}
return array_unique( $event_types );
}
/**
* Creates WebhookEvent from request data.
*
* @param \WP_REST_Request $request The request with event data.
*
* @return WebhookEvent
* @throws RuntimeException When failed to create.
*/
private function event_from_request( \WP_REST_Request $request ): WebhookEvent {
return $this->webhook_event_factory->from_array( $request->get_params() );
}
}