169 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Webhook Validator
 | 
						|
 *
 | 
						|
 * @link       https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature_post
 | 
						|
 *
 | 
						|
 * @package    easy-digital-downloads
 | 
						|
 * @subpackage Gateways\PayPal\Webhooks
 | 
						|
 * @copyright  Copyright (c) 2021, Sandhills Development, LLC
 | 
						|
 * @license    GPL2+
 | 
						|
 * @since      2.11
 | 
						|
 */
 | 
						|
 | 
						|
namespace EDD\Gateways\PayPal\Webhooks;
 | 
						|
 | 
						|
use EDD\Gateways\PayPal\API;
 | 
						|
use EDD\Gateways\PayPal\Exceptions\API_Exception;
 | 
						|
 | 
						|
class Webhook_Validator {
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Headers from the webhook
 | 
						|
	 *
 | 
						|
	 * @var array
 | 
						|
	 * @since 2.11
 | 
						|
	 */
 | 
						|
	private $headers;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Webhook event
 | 
						|
	 *
 | 
						|
	 * @var object
 | 
						|
	 * @since 2.11
 | 
						|
	 */
 | 
						|
	private $event;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Maps the incoming header key to the outgoing API request key.
 | 
						|
	 *
 | 
						|
	 * @var string[]
 | 
						|
	 * @since 2.11
 | 
						|
	 */
 | 
						|
	private $header_map = array(
 | 
						|
		'PAYPAL-AUTH-ALGO'         => 'auth_algo',
 | 
						|
		'PAYPAL-CERT-URL'          => 'cert_url',
 | 
						|
		'PAYPAL-TRANSMISSION-ID'   => 'transmission_id',
 | 
						|
		'PAYPAL-TRANSMISSION-SIG'  => 'transmission_sig',
 | 
						|
		'PAYPAL-TRANSMISSION-TIME' => 'transmission_time'
 | 
						|
	);
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Webhook_Validator constructor.
 | 
						|
	 *
 | 
						|
	 * @param array  $headers
 | 
						|
	 * @param object $event
 | 
						|
	 *
 | 
						|
	 * @since 2.11
 | 
						|
	 */
 | 
						|
	public function __construct( $headers, $event ) {
 | 
						|
		$this->headers = array_change_key_case( $headers, CASE_UPPER );
 | 
						|
		$this->event   = $event;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Verifies the signature.
 | 
						|
	 *
 | 
						|
	 * @since 2.11
 | 
						|
	 * @return true
 | 
						|
	 * @throws API_Exception
 | 
						|
	 * @throws \InvalidArgumentException
 | 
						|
	 */
 | 
						|
	public function verify_signature() {
 | 
						|
		$api = new API();
 | 
						|
 | 
						|
		$response = $api->make_request( 'v1/notifications/verify-webhook-signature', $this->get_body() );
 | 
						|
 | 
						|
		if ( 200 !== $api->last_response_code ) {
 | 
						|
			throw new API_Exception( sprintf(
 | 
						|
				'Invalid response code: %d. Response: %s',
 | 
						|
				$api->last_response_code,
 | 
						|
				json_encode( $response )
 | 
						|
			) );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( empty( $response->verification_status ) || 'SUCCESS' !== strtoupper( $response->verification_status ) ) {
 | 
						|
			throw new API_Exception( sprintf(
 | 
						|
				'Verification failure. Response: %s',
 | 
						|
				json_encode( $response )
 | 
						|
			) );
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Validates that we have all the required headers.
 | 
						|
	 *
 | 
						|
	 * @since 2.11
 | 
						|
	 * @throws \InvalidArgumentException
 | 
						|
	 */
 | 
						|
	private function validate_headers() {
 | 
						|
		foreach ( array_keys( $this->header_map ) as $required_key ) {
 | 
						|
			if ( ! array_key_exists( $required_key, $this->headers ) ) {
 | 
						|
				throw new \InvalidArgumentException( sprintf(
 | 
						|
					'Missing PayPal header %s',
 | 
						|
					$required_key
 | 
						|
				) );
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Retrieves the webhook ID for the current mode.
 | 
						|
	 *
 | 
						|
	 * @since 2.11
 | 
						|
	 * @return string
 | 
						|
	 * @throws \Exception
 | 
						|
	 */
 | 
						|
	private function get_webhook_id() {
 | 
						|
		$id = get_webhook_id();
 | 
						|
 | 
						|
		if ( empty( $id ) ) {
 | 
						|
			throw new \Exception( 'No webhook created in current mode.' );
 | 
						|
		}
 | 
						|
 | 
						|
		return $id;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Builds arguments for the body of the API request.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 * @throws \InvalidArgumentException
 | 
						|
	 * @throws \Exception
 | 
						|
	 */
 | 
						|
	private function get_body() {
 | 
						|
		$this->validate_headers();
 | 
						|
 | 
						|
		$body = array(
 | 
						|
			'webhook_id'    => $this->get_webhook_id(),
 | 
						|
			'webhook_event' => $this->event
 | 
						|
		);
 | 
						|
 | 
						|
		// Add arguments from the headers.
 | 
						|
		foreach ( $this->header_map as $header_key => $body_key ) {
 | 
						|
			$body[ $body_key ] = $this->headers[ $header_key ];
 | 
						|
		}
 | 
						|
 | 
						|
		return $body;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Validates the webhook from the current request.
 | 
						|
	 *
 | 
						|
	 * @param object $event Webhook event.
 | 
						|
	 *
 | 
						|
	 * @since 2.11
 | 
						|
	 * @return true
 | 
						|
	 * @throws API_Exception
 | 
						|
	 * @throws \InvalidArgumentException
 | 
						|
	 */
 | 
						|
	public static function validate_from_request( $event ) {
 | 
						|
		$validator = new Webhook_Validator( getallheaders(), $event );
 | 
						|
 | 
						|
		return $validator->verify_signature();
 | 
						|
	}
 | 
						|
 | 
						|
}
 |