installed plugin Easy Digital Downloads
version 3.1.0.3
This commit is contained in:
@ -0,0 +1,278 @@
|
||||
<?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
|
||||
) );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user