coding standards for webhook module

This commit is contained in:
David Remer 2020-08-27 13:10:16 +03:00
parent 653a765235
commit 7fcda592f7
19 changed files with 813 additions and 409 deletions

View file

@ -55,7 +55,7 @@ class EarlyOrderHandler {
$orderId = false; $orderId = false;
foreach ( $order->purchaseUnits() as $purchaseUnit ) { foreach ( $order->purchaseUnits() as $purchaseUnit ) {
if ( $purchaseUnit->customId() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) { if ( $purchaseUnit->customId() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
$orderId = (int) $this->sanitizeCustomId( $purchaseUnit->customId() ); $orderId = (int) $this->sanitize_custom_id( $purchaseUnit->customId() );
} }
} }
if ( $orderId === $resumeOrderId ) { if ( $orderId === $resumeOrderId ) {

View file

@ -31,7 +31,7 @@ class ReturnUrlEndpoint {
exit; exit;
} }
$wcOrderId = $this->sanitizeCustomId( $order->purchaseUnits()[0]->customId() ); $wcOrderId = $this->sanitize_custom_id( $order->purchaseUnits()[0]->customId() );
if ( ! $wcOrderId ) { if ( ! $wcOrderId ) {
exit; exit;
} }

View file

@ -1,9 +1,12 @@
<?php <?php
/**
* The webhook module extensions.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1); declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks; namespace Inpsyde\PayPalCommerce\Webhooks;
return [ return array();
];

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The webhook module.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1); declare(strict_types=1);
@ -7,5 +12,5 @@ namespace Inpsyde\PayPalCommerce\Webhooks;
use Dhii\Modular\Module\ModuleInterface; use Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface { return static function (): ModuleInterface {
return new WebhookModule(); return new WebhookModule();
}; };

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The webhook module services.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1); declare(strict_types=1);
@ -11,43 +16,43 @@ use Inpsyde\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed; use Inpsyde\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
return [ return array(
'webhook.registrar' => function(ContainerInterface $container) : WebhookRegistrar { 'webhook.registrar' => function( ContainerInterface $container ) : WebhookRegistrar {
$factory = $container->get('api.factory.webhook'); $factory = $container->get( 'api.factory.webhook' );
$endpoint = $container->get('api.endpoint.webhook'); $endpoint = $container->get( 'api.endpoint.webhook' );
$restEndpoint = $container->get('webhook.endpoint.controller'); $rest_endpoint = $container->get( 'webhook.endpoint.controller' );
return new WebhookRegistrar( return new WebhookRegistrar(
$factory, $factory,
$endpoint, $endpoint,
$restEndpoint $rest_endpoint
); );
}, },
'webhook.endpoint.controller' => function(ContainerInterface $container) : IncomingWebhookEndpoint { 'webhook.endpoint.controller' => function( ContainerInterface $container ) : IncomingWebhookEndpoint {
$webhookEndpoint = $container->get('api.endpoint.webhook'); $webhook_endpoint = $container->get( 'api.endpoint.webhook' );
$webhookFactory = $container->get('api.factory.webhook'); $webhook_factory = $container->get( 'api.factory.webhook' );
$handler = $container->get('webhook.endpoint.handler'); $handler = $container->get( 'webhook.endpoint.handler' );
$logger = $container->get('woocommerce.logger.woocommerce'); $logger = $container->get( 'woocommerce.logger.woocommerce' );
$verifyRequest = ! defined('PAYPAL_WEBHOOK_REQUEST_VERIFICATION') || PAYPAL_WEBHOOK_REQUEST_VERIFICATION; $verify_request = ! defined( 'PAYPAL_WEBHOOK_REQUEST_VERIFICATION' ) || PAYPAL_WEBHOOK_REQUEST_VERIFICATION;
return new IncomingWebhookEndpoint( return new IncomingWebhookEndpoint(
$webhookEndpoint, $webhook_endpoint,
$webhookFactory, $webhook_factory,
$logger, $logger,
$verifyRequest, $verify_request,
... $handler ... $handler
); );
}, },
'webhook.endpoint.handler' => function(ContainerInterface $container) : array { 'webhook.endpoint.handler' => function( ContainerInterface $container ) : array {
$logger = $container->get('woocommerce.logger.woocommerce'); $logger = $container->get( 'woocommerce.logger.woocommerce' );
$prefix = $container->get('api.prefix'); $prefix = $container->get( 'api.prefix' );
$orderEndpoint = $container->get('api.endpoint.order'); $order_endpoint = $container->get( 'api.endpoint.order' );
return [ return array(
new CheckoutOrderApproved($logger, $prefix, $orderEndpoint), new CheckoutOrderApproved( $logger, $prefix, $order_endpoint ),
new CheckoutOrderCompleted($logger, $prefix), new CheckoutOrderCompleted( $logger, $prefix ),
new PaymentCaptureRefunded($logger, $prefix), new PaymentCaptureRefunded( $logger, $prefix ),
new PaymentCaptureReversed($logger, $prefix), new PaymentCaptureReversed( $logger, $prefix ),
new PaymentCaptureCompleted($logger, $prefix), new PaymentCaptureCompleted( $logger, $prefix ),
]; );
} },
]; );

View file

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
trait PrefixTrait {
private $prefix = '';
private function sanitizeCustomId( string $customId ): int {
$orderId = str_replace( $this->prefix, '', $customId );
return (int) $orderId;
}
}

View file

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
interface RequestHandler {
public function eventTypes(): array;
public function responsibleForRequest( \WP_REST_Request $request): bool;
public function handleRequest( \WP_REST_Request $request): \WP_REST_Response;
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Handles the Webhook CHECKOUT.ORDER.APPROVED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1); declare(strict_types=1);
@ -8,47 +13,86 @@ use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException; use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* Class CheckoutOrderApproved
*/
class CheckoutOrderApproved implements RequestHandler { class CheckoutOrderApproved implements RequestHandler {
use PrefixTrait; use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger; private $logger;
private $orderEndpoint;
public function __construct( LoggerInterface $logger, string $prefix, OrderEndpoint $orderEndpoint ) { /**
$this->logger = $logger; * The order endpoint.
$this->prefix = $prefix; *
$this->orderEndpoint = $orderEndpoint; * @var OrderEndpoint
*/
private $order_endpoint;
/**
* CheckoutOrderApproved constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
* @param OrderEndpoint $order_endpoint The order endpoint.
*/
public function __construct( LoggerInterface $logger, string $prefix, OrderEndpoint $order_endpoint ) {
$this->logger = $logger;
$this->prefix = $prefix;
$this->order_endpoint = $order_endpoint;
} }
public function eventTypes(): array { /**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array( return array(
'CHECKOUT.ORDER.APPROVED', 'CHECKOUT.ORDER.APPROVED',
); );
} }
public function responsibleForRequest( \WP_REST_Request $request ): bool { /**
return in_array( $request['event_type'], $this->eventTypes(), true ); * Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
} }
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong /**
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response { * Responsible for handling the request.
$response = array( 'success' => false ); *
$customIds = array_filter( * @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$response = array( 'success' => false );
$custom_ids = array_filter(
array_map( array_map(
static function ( array $purchaseUnit ): string { static function ( array $purchase_unit ): string {
return isset( $purchaseUnit['custom_id'] ) ? return isset( $purchase_unit['custom_id'] ) ?
(string) $purchaseUnit['custom_id'] : ''; (string) $purchase_unit['custom_id'] : '';
}, },
isset( $request['resource']['purchase_units'] ) ? isset( $request['resource']['purchase_units'] ) ?
(array) $request['resource']['purchase_units'] : array() (array) $request['resource']['purchase_units'] : array()
), ),
static function ( string $orderId ): bool { static function ( string $order_id ): bool {
return ! empty( $orderId ); return ! empty( $order_id );
} }
); );
if ( empty( $customIds ) ) { if ( empty( $custom_ids ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -70,7 +114,7 @@ class CheckoutOrderApproved implements RequestHandler {
try { try {
$order = isset( $request['resource']['id'] ) ? $order = isset( $request['resource']['id'] ) ?
$this->orderEndpoint->order( $request['resource']['id'] ) : null; $this->order_endpoint->order( $request['resource']['id'] ) : null;
if ( ! $order ) { if ( ! $order ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
@ -92,7 +136,7 @@ class CheckoutOrderApproved implements RequestHandler {
} }
if ( $order->intent() === 'CAPTURE' ) { if ( $order->intent() === 'CAPTURE' ) {
$this->orderEndpoint->capture( $order ); $this->order_endpoint->capture( $order );
} }
} catch ( RuntimeException $error ) { } catch ( RuntimeException $error ) {
$message = sprintf( $message = sprintf(
@ -114,19 +158,19 @@ class CheckoutOrderApproved implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$wcOrderIds = array_map( $wc_order_ids = array_map(
array( array(
$this, $this,
'sanitizeCustomId', 'sanitize_custom_id',
), ),
$customIds $custom_ids
); );
$args = array( $args = array(
'post__in' => $wcOrderIds, 'post__in' => $wc_order_ids,
'limit' => -1, 'limit' => -1,
); );
$wcOrders = wc_get_orders( $args ); $wc_orders = wc_get_orders( $args );
if ( ! $wcOrders ) { if ( ! $wc_orders ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal order Id. // translators: %s is the PayPal order Id.
__( 'Order for PayPal order %s not found.', 'woocommerce-paypal-commerce-gateway' ), __( 'Order for PayPal order %s not found.', 'woocommerce-paypal-commerce-gateway' ),
@ -143,20 +187,22 @@ class CheckoutOrderApproved implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$newStatus = $order->intent() === 'CAPTURE' ? 'processing' : 'on-hold'; $new_status = $order->intent() === 'CAPTURE' ? 'processing' : 'on-hold';
$statusMessage = $order->intent() === 'CAPTURE' ? $status_message = $order->intent() === 'CAPTURE' ?
__( 'Payment received.', 'woocommerce-paypal-commerce-gateway' ) __( 'Payment received.', 'woocommerce-paypal-commerce-gateway' )
: __( 'Payment can be captured.', 'woocommerce-paypal-commerce-gateway' ); : __( 'Payment can be captured.', 'woocommerce-paypal-commerce-gateway' );
foreach ( $wcOrders as $wcOrder ) { foreach ( $wc_orders as $wc_order ) {
if ( ! in_array( $wcOrder->get_status(), array( 'pending', 'on-hold' ), true ) ) { if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
continue; continue;
} }
/** /**
* @var \WC_Order $wcOrder * The Woocommerce order.
*
* @var \WC_Order $wc_order
*/ */
$wcOrder->update_status( $wc_order->update_status(
$newStatus, $new_status,
$statusMessage $status_message
); );
$this->logger->log( $this->logger->log(
'info', 'info',
@ -166,16 +212,15 @@ class CheckoutOrderApproved implements RequestHandler {
'Order %s has been updated through PayPal', 'Order %s has been updated through PayPal',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
), ),
array( array(
'request' => $request, 'request' => $request,
'order' => $wcOrder, 'order' => $wc_order,
) )
); );
} }
$response['success'] = true; $response['success'] = true;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
//phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
} }

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Handles the Webhook CHECKOUT.ORDER.COMPLETED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,44 +11,77 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* Class CheckoutOrderCompleted
*/
class CheckoutOrderCompleted implements RequestHandler { class CheckoutOrderCompleted implements RequestHandler {
use PrefixTrait; use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger; private $logger;
/**
* CheckoutOrderCompleted constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) { public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
} }
public function eventTypes(): array { /**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array( return array(
'CHECKOUT.ORDER.COMPLETED', 'CHECKOUT.ORDER.COMPLETED',
); );
} }
public function responsibleForRequest( \WP_REST_Request $request ): bool { /**
return in_array( $request['event_type'], $this->eventTypes(), true ); * Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
} }
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong /**
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response { * Responsible for handling the request.
$response = array( 'success' => false ); *
$customIds = array_filter( * @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$response = array( 'success' => false );
$custom_ids = array_filter(
array_map( array_map(
static function ( array $purchaseUnit ): string { static function ( array $purchase_unit ): string {
return isset( $purchaseUnit['custom_id'] ) ? return isset( $purchase_unit['custom_id'] ) ?
(string) $purchaseUnit['custom_id'] : ''; (string) $purchase_unit['custom_id'] : '';
}, },
isset( $request['resource']['purchase_units'] ) ? isset( $request['resource']['purchase_units'] ) ?
(array) $request['resource']['purchase_units'] : array() (array) $request['resource']['purchase_units'] : array()
), ),
static function ( string $orderId ): bool { static function ( string $order_id ): bool {
return ! empty( $orderId ); return ! empty( $order_id );
} }
); );
if ( empty( $customIds ) ) { if ( empty( $custom_ids ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -63,19 +101,19 @@ class CheckoutOrderCompleted implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$orderIds = array_map( $order_ids = array_map(
array( array(
$this, $this,
'sanitizeCustomId', 'sanitize_custom_id',
), ),
$customIds $custom_ids
); );
$args = array( $args = array(
'post__in' => $orderIds, 'post__in' => $order_ids,
'limit' => -1, 'limit' => -1,
); );
$wcOrders = wc_get_orders( $args ); $wc_orders = wc_get_orders( $args );
if ( ! $wcOrders ) { if ( ! $wc_orders ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal order Id. // translators: %s is the PayPal order Id.
__( 'Order for PayPal order %s not found.', 'woocommerce-paypal-commerce-gateway' ), __( 'Order for PayPal order %s not found.', 'woocommerce-paypal-commerce-gateway' ),
@ -92,14 +130,16 @@ class CheckoutOrderCompleted implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
foreach ( $wcOrders as $wcOrder ) { foreach ( $wc_orders as $wc_order ) {
if ( ! in_array( $wcOrder->get_status(), array( 'pending', 'on-hold' ), true ) ) { if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
continue; continue;
} }
/** /**
* @var \WC_Order $wcOrder * The Woocommerce order.
*
* @var \WC_Order $wc_order
*/ */
$wcOrder->update_status( $wc_order->update_status(
'processing', 'processing',
__( 'Payment received.', 'woocommerce-paypal-commerce-gateway' ) __( 'Payment received.', 'woocommerce-paypal-commerce-gateway' )
); );
@ -111,16 +151,16 @@ class CheckoutOrderCompleted implements RequestHandler {
'Order %s has been updated through PayPal', 'Order %s has been updated through PayPal',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
), ),
array( array(
'request' => $request, 'request' => $request,
'order' => $wcOrder, 'order' => $wc_order,
) )
); );
} }
$response['success'] = true; $response['success'] = true;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong // phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
} }

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Handles the Webhook PAYMENT.CAPTURE.COMPLETED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1); declare(strict_types=1);
@ -7,30 +12,63 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* Class PaymentCaptureCompleted
*/
class PaymentCaptureCompleted implements RequestHandler { class PaymentCaptureCompleted implements RequestHandler {
use PrefixTrait; use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger; private $logger;
/**
* PaymentCaptureCompleted constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) { public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
} }
public function eventTypes(): array { /**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array( 'PAYMENT.CAPTURE.COMPLETED' ); return array( 'PAYMENT.CAPTURE.COMPLETED' );
} }
public function responsibleForRequest( \WP_REST_Request $request ): bool { /**
return in_array( $request['event_type'], $this->eventTypes(), true ); * Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
} }
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong /**
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response { * Responsible for handling the request.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$response = array( 'success' => false ); $response = array( 'success' => false );
$orderId = isset( $request['resource']['custom_id'] ) ? $order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitizeCustomId( $request['resource']['custom_id'] ) : 0; $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
if ( ! $orderId ) { if ( ! $order_id ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -49,9 +87,9 @@ class PaymentCaptureCompleted implements RequestHandler {
$response['message'] = $message; $response['message'] = $message;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$wcOrder = wc_get_order( $orderId ); $wc_order = wc_get_order( $order_id );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) { if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -71,17 +109,17 @@ class PaymentCaptureCompleted implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
if ( $wcOrder->get_status() !== 'on-hold' ) { if ( $wc_order->get_status() !== 'on-hold' ) {
$response['success'] = true; $response['success'] = true;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$wcOrder->add_order_note( $wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' ) __( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' )
); );
$wcOrder->set_status( 'processing' ); $wc_order->set_status( 'processing' );
$wcOrder->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'true' ); $wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'true' );
$wcOrder->save(); $wc_order->save();
$this->logger->log( $this->logger->log(
'info', 'info',
sprintf( sprintf(
@ -90,15 +128,14 @@ class PaymentCaptureCompleted implements RequestHandler {
'Order %s has been updated through PayPal', 'Order %s has been updated through PayPal',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
), ),
array( array(
'request' => $request, 'request' => $request,
'order' => $wcOrder, 'order' => $wc_order,
) )
); );
$response['success'] = true; $response['success'] = true;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
} }

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Handels the Webhook PAYMENT.CAPTURE.REFUNDED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,30 +11,63 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* Class PaymentCaptureRefunded
*/
class PaymentCaptureRefunded implements RequestHandler { class PaymentCaptureRefunded implements RequestHandler {
use PrefixTrait; use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger; private $logger;
/**
* PaymentCaptureRefunded constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) { public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
} }
public function eventTypes(): array { /**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array( 'PAYMENT.CAPTURE.REFUNDED' ); return array( 'PAYMENT.CAPTURE.REFUNDED' );
} }
public function responsibleForRequest( \WP_REST_Request $request ): bool { /**
return in_array( $request['event_type'], $this->eventTypes(), true ); * Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
} }
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong /**
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response { * Responsible for handling the request.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$response = array( 'success' => false ); $response = array( 'success' => false );
$orderId = isset( $request['resource']['custom_id'] ) ? $order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitizeCustomId( $request['resource']['custom_id'] ) : 0; $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
if ( ! $orderId ) { if ( ! $order_id ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -49,8 +87,8 @@ class PaymentCaptureRefunded implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$wcOrder = wc_get_order( $orderId ); $wc_order = wc_get_order( $order_id );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) { if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal refund Id. // translators: %s is the PayPal refund Id.
__( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-commerce-gateway' ), __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-commerce-gateway' ),
@ -68,11 +106,13 @@ class PaymentCaptureRefunded implements RequestHandler {
} }
/** /**
* @var \WC_Order $wcOrder * The Woocommerce order.
*
* @var \WC_Order $wc_order
*/ */
$refund = wc_create_refund( $refund = wc_create_refund(
array( array(
'order_id' => $wcOrder->get_id(), 'order_id' => $wc_order->get_id(),
'amount' => $request['resource']['amount']['value'], 'amount' => $request['resource']['amount']['value'],
) )
); );
@ -82,7 +122,7 @@ class PaymentCaptureRefunded implements RequestHandler {
sprintf( sprintf(
// translators: %s is the order id. // translators: %s is the order id.
__( 'Order %s could not be refunded', 'woocommerce-paypal-commerce-gateway' ), __( 'Order %s could not be refunded', 'woocommerce-paypal-commerce-gateway' ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
), ),
array( array(
'request' => $request, 'request' => $request,
@ -102,16 +142,15 @@ class PaymentCaptureRefunded implements RequestHandler {
'Order %1$s has been refunded with %2$s through PayPal', 'Order %1$s has been refunded with %2$s through PayPal',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
(string) $wcOrder->get_id(), (string) $wc_order->get_id(),
(string) $refund->get_amount() (string) $refund->get_amount()
), ),
array( array(
'request' => $request, 'request' => $request,
'order' => $wcOrder, 'order' => $wc_order,
) )
); );
$response['success'] = true; $response['success'] = true;
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
} }

View file

@ -1,4 +1,12 @@
<?php <?php
/**
* Handles the following Webhooks:
* - PAYMENT.CAPTURE.REVERSED
* - PAYMENT.ORDER.CANCELLED
* - PAYMENT.CAPTURE.DENIED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,17 +14,37 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* Class PaymentCaptureReversed
*/
class PaymentCaptureReversed implements RequestHandler { class PaymentCaptureReversed implements RequestHandler {
use PrefixTrait; use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger; private $logger;
/**
* PaymentCaptureReversed constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) { public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger; $this->logger = $logger;
$this->prefix = $prefix; $this->prefix = $prefix;
} }
public function eventTypes(): array { /**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array( return array(
'PAYMENT.CAPTURE.REVERSED', 'PAYMENT.CAPTURE.REVERSED',
'PAYMENT.ORDER.CANCELLED', 'PAYMENT.ORDER.CANCELLED',
@ -24,16 +52,29 @@ class PaymentCaptureReversed implements RequestHandler {
); );
} }
public function responsibleForRequest( \WP_REST_Request $request ): bool { /**
return in_array( $request['event_type'], $this->eventTypes(), true ); * Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request ): bool {
return in_array( $request['event_type'], $this->event_types(), true );
} }
// phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong /**
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response { * Responsible for handling the request.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$response = array( 'success' => false ); $response = array( 'success' => false );
$orderId = isset( $request['resource']['custom_id'] ) ? $order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitizeCustomId( $request['resource']['custom_id'] ) : 0; $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
if ( ! $orderId ) { if ( ! $order_id ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal webhook Id. // translators: %s is the PayPal webhook Id.
__( __(
@ -53,8 +94,8 @@ class PaymentCaptureReversed implements RequestHandler {
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
$wcOrder = wc_get_order( $orderId ); $wc_order = wc_get_order( $order_id );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) { if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the PayPal refund Id. // translators: %s is the PayPal refund Id.
__( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-commerce-gateway' ), __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-commerce-gateway' ),
@ -72,9 +113,11 @@ class PaymentCaptureReversed implements RequestHandler {
} }
/** /**
* @var \WC_Order $wcOrder * The Woocommerce order.
*
* @var \WC_Order $wc_order
*/ */
$response['success'] = (bool) $wcOrder->update_status( 'cancelled' ); $response['success'] = (bool) $wc_order->update_status( 'cancelled' );
$message = $response['success'] ? sprintf( $message = $response['success'] ? sprintf(
// translators: %1$s is the order id. // translators: %1$s is the order id.
@ -82,21 +125,20 @@ class PaymentCaptureReversed implements RequestHandler {
'Order %1$s has been cancelled through PayPal', 'Order %1$s has been cancelled through PayPal',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
) : sprintf( ) : sprintf(
// translators: %1$s is the order id. // translators: %1$s is the order id.
__( 'Failed to cancel order %1$s through PayPal', 'woocommerce-paypal-commerce-gateway' ), __( 'Failed to cancel order %1$s through PayPal', 'woocommerce-paypal-commerce-gateway' ),
(string) $wcOrder->get_id() (string) $wc_order->get_id()
); );
$this->logger->log( $this->logger->log(
$response['success'] ? 'info' : 'warning', $response['success'] ? 'info' : 'warning',
$message, $message,
array( array(
'request' => $request, 'request' => $request,
'order' => $wcOrder, 'order' => $wc_order,
) )
); );
return rest_ensure_response( $response ); return rest_ensure_response( $response );
} }
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
} }

View file

@ -0,0 +1,37 @@
<?php
/**
* Trait which helps to remove the prefix of IDs.
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
/**
* Trait PrefixTrait
*/
trait PrefixTrait {
/**
* The prefix.
*
* @var string
*/
private $prefix = '';
/**
* Removes the prefix from a given Id.
*
* @param string $custom_id The custom id.
*
* @return int
*/
private function sanitize_custom_id( string $custom_id ): int {
$id = str_replace( $this->prefix, '', $custom_id );
return (int) $id;
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* The interface for the request handlers.
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
/**
* Interface RequestHandler
*/
interface RequestHandler {
/**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array;
/**
* Whether a handler is responsible for a given request or not.
*
* @param \WP_REST_Request $request The request.
*
* @return bool
*/
public function responsible_for_request( \WP_REST_Request $request): bool;
/**
* Responsible for handling the request.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request): \WP_REST_Response;
}

View file

@ -1,141 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use Inpsyde\PayPalCommerce\Webhooks\Handler\RequestHandler;
use Psr\Log\LoggerInterface;
class IncomingWebhookEndpoint {
public const NAMESPACE = 'paypal/v1';
public const ROUTE = 'incoming';
private $webhookEndpoint;
private $webhookFactory;
private $handlers;
private $logger;
private $verifyRequest;
public function __construct(
WebhookEndpoint $webhookEndpoint,
WebhookFactory $webhookFactory,
LoggerInterface $logger,
bool $verifyRequest,
RequestHandler ...$handlers
) {
$this->webhookEndpoint = $webhookEndpoint;
$this->webhookFactory = $webhookFactory;
$this->handlers = $handlers;
$this->logger = $logger;
$this->verifyRequest = $verifyRequest;
}
public function register(): bool {
return (bool) register_rest_route(
self::NAMESPACE,
self::ROUTE,
array(
'methods' => array(
'POST',
),
'callback' => array(
$this,
'handleRequest',
),
'permission_callback' => array(
$this,
'verifyRequest',
),
)
);
}
public function verifyRequest(): bool {
if ( ! $this->verifyRequest ) {
return true;
}
try {
$data = (array) get_option( WebhookRegistrar::KEY, array() );
$webhook = $this->webhookFactory->fromArray( $data );
$result = $this->webhookEndpoint->verifyCurrentRequestForWebhook( $webhook );
if ( ! $result ) {
$this->logger->log(
'error',
__( 'Illegit Webhook request detected.', 'woocommerce-paypal-commerce-gateway' ),
);
}
return $result;
} catch ( RuntimeException $exception ) {
$this->logger->log(
'error',
sprintf(
// translators: %s is the error message.
__(
'Illegit Webhook request detected: %s',
'woocommerce-paypal-commerce-gateway'
),
$exception->getMessage()
)
);
return false;
}
}
public function handleRequest( \WP_REST_Request $request ): \WP_REST_Response {
foreach ( $this->handlers as $handler ) {
if ( $handler->responsibleForRequest( $request ) ) {
$response = $handler->handleRequest( $request );
$this->logger->log(
'info',
sprintf(
// translators: %s is the event type
__( 'Webhook has been handled by %s', 'woocommerce-paypal-commerce-gateway' ),
( $handler->eventTypes() ) ? current( $handler->eventTypes() ) : ''
),
array(
'request' => $request,
'response' => $response,
)
);
return $response;
}
}
$message = sprintf(
// translators: %s is the request type.
__( 'Could not find handler for request type %s', 'woocommerce-paypal-commerce-gateway' ),
$request['event_type']
);
$this->logger->log(
'warning',
$message,
array(
'request' => $request,
)
);
$response = array(
'success' => false,
'message' => $message,
);
return rest_ensure_response( $response );
}
public function url(): string {
return rest_url( self::NAMESPACE . '/' . self::ROUTE );
}
public function handledEventTypes(): array {
$eventTypes = array();
foreach ( $this->handlers as $handler ) {
$eventTypes = array_merge( $eventTypes, $handler->eventTypes() );
}
return array_unique( $eventTypes );
}
}

View file

@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
class WebhookRegistrar {
public const EVENT_HOOK = 'ppcp-register-event';
public const KEY = 'ppcp-webhook';
private $webhookFactory;
private $endpoint;
private $restEndpoint;
public function __construct(
WebhookFactory $webhookFactory,
WebhookEndpoint $endpoint,
IncomingWebhookEndpoint $restEndpoint
) {
$this->webhookFactory = $webhookFactory;
$this->endpoint = $endpoint;
$this->restEndpoint = $restEndpoint;
}
public function register(): bool {
$webhook = $this->webhookFactory->forUrlAndEvents(
$this->restEndpoint->url(),
$this->restEndpoint->handledEventTypes()
);
try {
$created = $this->endpoint->create( $webhook );
if ( empty( $created->id() ) ) {
return false;
}
update_option(
self::KEY,
$created->toArray()
);
return true;
} catch ( RuntimeException $error ) {
wp_schedule_single_event(
time() - 1,
self::EVENT_HOOK
);
return false;
}
}
public function unregister(): bool {
$data = (array) get_option( self::KEY, array() );
if ( ! $data ) {
return false;
}
try {
$webhook = $this->webhookFactory->fromArray( $data );
$success = $this->endpoint->delete( $webhook );
} catch ( RuntimeException $error ) {
return false;
}
if ( $success ) {
delete_option( self::KEY );
}
return $success;
}
}

View file

@ -0,0 +1,214 @@
<?php
/**
* Controls the endpoint for the incoming webhooks.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use Inpsyde\PayPalCommerce\Webhooks\Handler\RequestHandler;
use Psr\Log\LoggerInterface;
/**
* Class IncomingWebhookEndpoint
*/
class IncomingWebhookEndpoint {
public const NAMESPACE = 'paypal/v1';
public const ROUTE = 'incoming';
/**
* The Webhook endpoint.
*
* @var WebhookEndpoint
*/
private $webhook_endpoint;
/**
* The Webhook Factory.
*
* @var WebhookFactory
*/
private $webhook_factory;
/**
* The Request handlers.
*
* @var RequestHandler[]
*/
private $handlers;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* Whether requests need to be verified.
*
* @var bool
*/
private $verify_request;
/**
* IncomingWebhookEndpoint constructor.
*
* @param WebhookEndpoint $webhook_endpoint The webhook endpoint.
* @param WebhookFactory $webhook_factory The webhook factory.
* @param LoggerInterface $logger The logger.
* @param bool $verify_request Whether requests need to be verified or not.
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
*/
public function __construct(
WebhookEndpoint $webhook_endpoint,
WebhookFactory $webhook_factory,
LoggerInterface $logger,
bool $verify_request,
RequestHandler ...$handlers
) {
$this->webhook_endpoint = $webhook_endpoint;
$this->webhook_factory = $webhook_factory;
$this->handlers = $handlers;
$this->logger = $logger;
$this->verify_request = $verify_request;
}
/**
* 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.
*
* @return bool
*/
public function verify_request(): bool {
if ( ! $this->verify_request ) {
return true;
}
try {
$data = (array) get_option( WebhookRegistrar::KEY, array() );
$webhook = $this->webhook_factory->fromArray( $data );
$result = $this->webhook_endpoint->verifyCurrentRequestForWebhook( $webhook );
if ( ! $result ) {
$this->logger->log(
'error',
__( 'Illegit Webhook request detected.', 'woocommerce-paypal-commerce-gateway' )
);
}
return $result;
} catch ( RuntimeException $exception ) {
$this->logger->log(
'error',
sprintf(
// translators: %s is the error message.
__(
'Illegit Webhook request detected: %s',
'woocommerce-paypal-commerce-gateway'
),
$exception->getMessage()
)
);
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 {
foreach ( $this->handlers as $handler ) {
if ( $handler->responsible_for_request( $request ) ) {
$response = $handler->handle_request( $request );
$this->logger->log(
'info',
sprintf(
// translators: %s is the event type.
__( 'Webhook has been handled by %s', 'woocommerce-paypal-commerce-gateway' ),
( $handler->event_types() ) ? current( $handler->event_types() ) : ''
),
array(
'request' => $request,
'response' => $response,
)
);
return $response;
}
}
$message = sprintf(
// translators: %s is the request type.
__( 'Could not find handler for request type %s', 'woocommerce-paypal-commerce-gateway' ),
$request['event_type']
);
$this->logger->log(
'warning',
$message,
array(
'request' => $request,
)
);
$response = array(
'success' => false,
'message' => $message,
);
return rest_ensure_response( $response );
}
/**
* Returns the URL to the endpoint.
*
* @return string
*/
public function url(): string {
return rest_url( self::NAMESPACE . '/' . self::ROUTE );
}
/**
* Returns the event types, which are handled by the endpoint.
*
* @return array
*/
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 );
}
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The webhook module.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,15 +11,19 @@ namespace Inpsyde\PayPalCommerce\Webhooks;
use Dhii\Container\ServiceProvider; use Dhii\Container\ServiceProvider;
use Dhii\Modular\Module\ModuleInterface; use Dhii\Modular\Module\ModuleInterface;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Webhook;
use Inpsyde\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use Inpsyde\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use Inpsyde\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use Interop\Container\ServiceProviderInterface; use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/**
* Class WebhookModule
*/
class WebhookModule implements ModuleInterface { class WebhookModule implements ModuleInterface {
/**
* Setup the Webhook module.
*
* @return ServiceProviderInterface
*/
public function setup(): ServiceProviderInterface { public function setup(): ServiceProviderInterface {
return new ServiceProvider( return new ServiceProvider(
require __DIR__ . '/../services.php', require __DIR__ . '/../services.php',
@ -22,12 +31,19 @@ class WebhookModule implements ModuleInterface {
); );
} }
/**
* Run the Webhook module.
*
* @param ContainerInterface $container The Container.
*/
public function run( ContainerInterface $container ) { public function run( ContainerInterface $container ) {
add_action( add_action(
'rest_api_init', 'rest_api_init',
static function () use ( $container ) { static function () use ( $container ) {
$endpoint = $container->get( 'webhook.endpoint.controller' ); $endpoint = $container->get( 'webhook.endpoint.controller' );
/** /**
* The Incoming Webhook Endpoint.
*
* @var IncomingWebhookEndpoint $endpoint * @var IncomingWebhookEndpoint $endpoint
*/ */
$endpoint->register(); $endpoint->register();
@ -38,6 +54,11 @@ class WebhookModule implements ModuleInterface {
WebhookRegistrar::EVENT_HOOK, WebhookRegistrar::EVENT_HOOK,
static function () use ( $container ) { static function () use ( $container ) {
$registrar = $container->get( 'webhook.registrar' ); $registrar = $container->get( 'webhook.registrar' );
/**
* The Webhook Registrar.
*
* @var WebhookRegistrar $endpoint
*/
$registrar->register(); $registrar->register();
} }
); );
@ -46,6 +67,11 @@ class WebhookModule implements ModuleInterface {
'woocommerce-paypal-commerce-gateway.deactivate', 'woocommerce-paypal-commerce-gateway.deactivate',
static function () use ( $container ) { static function () use ( $container ) {
$registrar = $container->get( 'webhook.registrar' ); $registrar = $container->get( 'webhook.registrar' );
/**
* The Webhook Registrar.
*
* @var WebhookRegistrar $endpoint
*/
$registrar->unregister(); $registrar->unregister();
} }
); );

View file

@ -0,0 +1,116 @@
<?php
/**
* The WebhookRegistrar registers and unregisters webhooks with PayPal.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Webhooks;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\WebhookFactory;
/**
* Class WebhookRegistrar
*/
class WebhookRegistrar {
public const EVENT_HOOK = 'ppcp-register-event';
public const KEY = 'ppcp-webhook';
/**
* The Webhook factory.
*
* @var WebhookFactory
*/
private $webhook_factory;
/**
* The Webhook endpoint.
*
* @var WebhookEndpoint
*/
private $endpoint;
/**
* The WordPress Rest API endpoint.
*
* @var IncomingWebhookEndpoint
*/
private $rest_endpoint;
/**
* WebhookRegistrar constructor.
*
* @param WebhookFactory $webhook_factory The Webhook factory.
* @param WebhookEndpoint $endpoint The Webhook endpoint.
* @param IncomingWebhookEndpoint $rest_endpoint The WordPress Rest API endpoint.
*/
public function __construct(
WebhookFactory $webhook_factory,
WebhookEndpoint $endpoint,
IncomingWebhookEndpoint $rest_endpoint
) {
$this->webhook_factory = $webhook_factory;
$this->endpoint = $endpoint;
$this->rest_endpoint = $rest_endpoint;
}
/**
* Register Webhooks with PayPal.
*
* @return bool
*/
public function register(): bool {
$webhook = $this->webhook_factory->forUrlAndEvents(
$this->rest_endpoint->url(),
$this->rest_endpoint->handled_event_types()
);
try {
$created = $this->endpoint->create( $webhook );
if ( empty( $created->id() ) ) {
return false;
}
update_option(
self::KEY,
$created->toArray()
);
return true;
} catch ( RuntimeException $error ) {
wp_schedule_single_event(
time() - 1,
self::EVENT_HOOK
);
return false;
}
}
/**
* Unregister webhooks with PayPal.
*
* @return bool
*/
public function unregister(): bool {
$data = (array) get_option( self::KEY, array() );
if ( ! $data ) {
return false;
}
try {
$webhook = $this->webhook_factory->fromArray( $data );
$success = $this->endpoint->delete( $webhook );
} catch ( RuntimeException $error ) {
return false;
}
if ( $success ) {
delete_option( self::KEY );
}
return $success;
}
}