279 lines
8.0 KiB
PHP
279 lines
8.0 KiB
PHP
<?php
|
|
/**
|
|
* Webhook Functions
|
|
*
|
|
* @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;
|
|
use EDD\Gateways\PayPal\Exceptions\Authentication_Exception;
|
|
|
|
/**
|
|
* Returns the webhook URL.
|
|
*
|
|
* @since 2.11
|
|
* @return string
|
|
*/
|
|
function get_webhook_url() {
|
|
return rest_url( Webhook_Handler::REST_NAMESPACE . '/' . Webhook_Handler::REST_ROUTE );
|
|
}
|
|
|
|
/**
|
|
* Returns the ID of the webhook.
|
|
*
|
|
* @param string $mode API mode. Either `sandbox` or `live`. If omitted, current store mode is used.
|
|
*
|
|
* @since 2.11
|
|
* @return string|false
|
|
*/
|
|
function get_webhook_id( $mode = '' ) {
|
|
if ( empty( $mode ) ) {
|
|
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
|
|
}
|
|
|
|
return get_option( sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode ) );
|
|
}
|
|
|
|
/**
|
|
* Returns the list of webhook events that EDD requires.
|
|
*
|
|
* @link https://developer.paypal.com/docs/api-basics/notifications/webhooks/event-names/#sales
|
|
*
|
|
* @todo Would be nice to use the EDD 3.0 registry for this at some point.
|
|
*
|
|
* @param string $mode Store mode. Either `sandbox` or `live`.
|
|
*
|
|
* @since 2.11
|
|
* @return array
|
|
*/
|
|
function get_webhook_events( $mode = '' ) {
|
|
$events = array(
|
|
'PAYMENT.CAPTURE.COMPLETED' => '\\EDD\\Gateways\\PayPal\\Webhooks\\Events\\Payment_Capture_Completed',
|
|
'PAYMENT.CAPTURE.DENIED' => '\\EDD\\Gateways\\PayPal\\Webhooks\\Events\\Payment_Capture_Denied',
|
|
'PAYMENT.CAPTURE.REFUNDED' => '\\EDD\\Gateways\\PayPal\\Webhooks\\Events\\Payment_Capture_Refunded',
|
|
'PAYMENT.CAPTURE.REVERSED' => '\\EDD\\Gateways\\PayPal\\Webhooks\\Events\\Payment_Capture_Refunded',
|
|
);
|
|
|
|
/**
|
|
* Filters the webhook events.
|
|
*
|
|
* @param array $events Array of events that PayPal will send webhooks for.
|
|
* @param string $mode Mode the webhook is being created in. Either `sandbox` or `live`.
|
|
*
|
|
* @since 2.11
|
|
*/
|
|
return (array) apply_filters( 'edd_paypal_webhook_events', $events, $mode );
|
|
}
|
|
|
|
/**
|
|
* Creates a webhook.
|
|
*
|
|
* @param string $mode Store mode. Either `sandbox` or `live`. If omitted, current store mode is used.
|
|
*
|
|
* @return true
|
|
* @throws API_Exception
|
|
* @throws Authentication_Exception
|
|
*/
|
|
function create_webhook( $mode = '' ) {
|
|
if ( ! is_ssl() ) {
|
|
throw new API_Exception( __( 'An SSL certificate is required to create a PayPal webhook.', 'easy-digital-downloads' ) );
|
|
}
|
|
|
|
if ( empty( $mode ) ) {
|
|
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
|
|
}
|
|
|
|
$webhook_url = get_webhook_url();
|
|
|
|
$api = new API( $mode );
|
|
|
|
// First, list webhooks in case it's already added.
|
|
try {
|
|
$response = $api->make_request( 'v1/notifications/webhooks', array(), array(), 'GET' );
|
|
if ( ! empty( $response->webhooks ) && is_array( $response->webhooks ) ) {
|
|
foreach ( $response->webhooks as $webhook ) {
|
|
if ( ! empty( $webook->id ) && ! empty( $webhook->url ) && $webhook_url === $webhook->url ) {
|
|
update_option( sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode ), sanitize_text_field( $webhook->id ) );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} catch ( \Exception $e ) {
|
|
// Continue to webhook creation.
|
|
}
|
|
|
|
$event_types = array();
|
|
|
|
foreach ( array_keys( get_webhook_events( $mode ) ) as $event ) {
|
|
$event_types[] = array( 'name' => $event );
|
|
}
|
|
|
|
$response = $api->make_request( 'v1/notifications/webhooks', array(
|
|
'url' => $webhook_url,
|
|
'event_types' => $event_types
|
|
) );
|
|
|
|
if ( 201 !== $api->last_response_code ) {
|
|
throw new API_Exception( sprintf(
|
|
/* Translators: %d - HTTP response code; %s - Full response from the API. */
|
|
__( 'Invalid response code %d while creating webhook. Response: %s', 'easy-digital-downloads' ),
|
|
$api->last_response_code,
|
|
json_encode( $response )
|
|
) );
|
|
}
|
|
|
|
if ( empty( $response->id ) ) {
|
|
throw new API_Exception( __( 'Unexpected response from PayPal.', 'easy-digital-downloads' ) );
|
|
}
|
|
|
|
update_option( sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode ), sanitize_text_field( $response->id ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Syncs the webhook with expected data. This replaces the webhook URL and event types with
|
|
* what EDD expects them to be. This can be used when the events need to be updated in
|
|
* the event that some are missing.
|
|
*
|
|
* @param string $mode Either `sandbox` or `live` mode. If omitted, current store mode is used.
|
|
*
|
|
* @since 2.11
|
|
* @return true
|
|
* @throws API_Exception
|
|
* @throws Authentication_Exception
|
|
*/
|
|
function sync_webhook( $mode = '' ) {
|
|
if ( empty( $mode ) ) {
|
|
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
|
|
}
|
|
|
|
$webhook_id = get_webhook_id( $mode );
|
|
if ( empty( $webhook_id ) ) {
|
|
throw new \Exception( esc_html__( 'Webhook not configured.', 'easy-digital-downloads' ) );
|
|
}
|
|
|
|
$event_types = array();
|
|
foreach ( array_keys( get_webhook_events( $mode ) ) as $event ) {
|
|
$event_types[] = array( 'name' => $event );
|
|
}
|
|
|
|
$new_data = array(
|
|
array(
|
|
'op' => 'replace',
|
|
'path' => '/url',
|
|
'value' => get_webhook_url()
|
|
),
|
|
array(
|
|
'op' => 'replace',
|
|
'path' => '/event_types',
|
|
'value' => $event_types
|
|
)
|
|
);
|
|
|
|
$api = new API( $mode );
|
|
$response = $api->make_request( 'v1/notifications/webhooks/' . urlencode( $webhook_id ), $new_data, array(), 'PATCH' );
|
|
if ( 400 === $api->last_response_code && isset( $response->name ) && 'WEBHOOK_PATCH_REQUEST_NO_CHANGE' === $response->name ) {
|
|
return true;
|
|
}
|
|
|
|
if ( 200 !== $api->last_response_code ) {
|
|
throw new API_Exception( sprintf(
|
|
/* Translators: %d - HTTP response code; %s - Full response from the API. */
|
|
__( 'Invalid response code %d while syncing webhook. Response: %s', 'easy-digital-downloads' ),
|
|
$api->last_response_code,
|
|
json_encode( $response )
|
|
) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Retrieves information about the webhook EDD created.
|
|
*
|
|
* @param string $mode Mode to get the webhook in. If omitted, current store mode is used.
|
|
*
|
|
* @return object|false Webhook object on success, false if there is no webhook set up.
|
|
* @throws API_Exception
|
|
* @throws Authentication_Exception
|
|
*/
|
|
function get_webhook_details( $mode = '' ) {
|
|
if ( empty( $mode ) ) {
|
|
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
|
|
}
|
|
|
|
$webhook_id = get_option( sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode ) );
|
|
|
|
// Bail if webhook was never set.
|
|
if ( ! $webhook_id ) {
|
|
return false;
|
|
}
|
|
|
|
$api = new API( $mode );
|
|
$response = $api->make_request( 'v1/notifications/webhooks/' . urlencode( $webhook_id ), array(), array(), 'GET' );
|
|
if ( 200 !== $api->last_response_code ) {
|
|
if ( 404 === $api->last_response_code ) {
|
|
throw new API_Exception(
|
|
__( 'Your store is currently not receiving webhook notifications, create the webhooks to reconnect.', 'easy-digital-downloads' )
|
|
);
|
|
} else {
|
|
throw new API_Exception( sprintf(
|
|
/* Translators: %d - HTTP response code. */
|
|
__( 'Invalid response code %d while retrieving webhook details.', 'easy-digital-downloads' ),
|
|
$api->last_response_code
|
|
) );
|
|
}
|
|
}
|
|
|
|
if ( empty( $response->id ) ) {
|
|
throw new API_Exception( __( 'Unexpected response from PayPal when retrieving webhook details.', 'easy-digital-downloads' ) );
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Deletes the webhook.
|
|
*
|
|
* @since 2.11
|
|
*
|
|
* @param string $mode
|
|
*
|
|
* @throws API_Exception
|
|
* @throws Authentication_Exception
|
|
*/
|
|
function delete_webhook( $mode = '' ) {
|
|
if ( empty( $mode ) ) {
|
|
$mode = edd_is_test_mode() ? API::MODE_SANDBOX : API::MODE_LIVE;
|
|
}
|
|
|
|
$webhook_name = sanitize_key( 'edd_paypal_commerce_webhook_id_' . $mode );
|
|
$webhook_id = get_option( $webhook_name );
|
|
|
|
// Bail if webhook was never set.
|
|
if ( ! $webhook_id ) {
|
|
return;
|
|
}
|
|
|
|
$api = new API( $mode );
|
|
|
|
$api->make_request( 'v1/notifications/webhooks/' . urlencode( $webhook_id ), array(), array(), 'DELETE' );
|
|
|
|
if ( 204 !== $api->last_response_code ) {
|
|
throw new API_Exception( sprintf(
|
|
/* Translators: %d - HTTP response code. */
|
|
__( 'Invalid response code %d while deleting webhook.', 'easy-digital-downloads' ),
|
|
$api->last_response_code
|
|
) );
|
|
}
|
|
}
|