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;
foreach ( $order->purchaseUnits() as $purchaseUnit ) {
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 ) {

View file

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

View file

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

View file

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

View file

@ -1,4 +1,9 @@
<?php
/**
* The webhook module services.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1);
@ -11,43 +16,43 @@ use Inpsyde\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
use Psr\Container\ContainerInterface;
return [
return array(
'webhook.registrar' => function(ContainerInterface $container) : WebhookRegistrar {
$factory = $container->get('api.factory.webhook');
$endpoint = $container->get('api.endpoint.webhook');
$restEndpoint = $container->get('webhook.endpoint.controller');
return new WebhookRegistrar(
$factory,
$endpoint,
$restEndpoint
);
},
'webhook.endpoint.controller' => function(ContainerInterface $container) : IncomingWebhookEndpoint {
$webhookEndpoint = $container->get('api.endpoint.webhook');
$webhookFactory = $container->get('api.factory.webhook');
$handler = $container->get('webhook.endpoint.handler');
$logger = $container->get('woocommerce.logger.woocommerce');
$verifyRequest = ! defined('PAYPAL_WEBHOOK_REQUEST_VERIFICATION') || PAYPAL_WEBHOOK_REQUEST_VERIFICATION;
'webhook.registrar' => function( ContainerInterface $container ) : WebhookRegistrar {
$factory = $container->get( 'api.factory.webhook' );
$endpoint = $container->get( 'api.endpoint.webhook' );
$rest_endpoint = $container->get( 'webhook.endpoint.controller' );
return new WebhookRegistrar(
$factory,
$endpoint,
$rest_endpoint
);
},
'webhook.endpoint.controller' => function( ContainerInterface $container ) : IncomingWebhookEndpoint {
$webhook_endpoint = $container->get( 'api.endpoint.webhook' );
$webhook_factory = $container->get( 'api.factory.webhook' );
$handler = $container->get( 'webhook.endpoint.handler' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$verify_request = ! defined( 'PAYPAL_WEBHOOK_REQUEST_VERIFICATION' ) || PAYPAL_WEBHOOK_REQUEST_VERIFICATION;
return new IncomingWebhookEndpoint(
$webhookEndpoint,
$webhookFactory,
$logger,
$verifyRequest,
... $handler
);
},
'webhook.endpoint.handler' => function(ContainerInterface $container) : array {
$logger = $container->get('woocommerce.logger.woocommerce');
$prefix = $container->get('api.prefix');
$orderEndpoint = $container->get('api.endpoint.order');
return [
new CheckoutOrderApproved($logger, $prefix, $orderEndpoint),
new CheckoutOrderCompleted($logger, $prefix),
new PaymentCaptureRefunded($logger, $prefix),
new PaymentCaptureReversed($logger, $prefix),
new PaymentCaptureCompleted($logger, $prefix),
];
}
];
return new IncomingWebhookEndpoint(
$webhook_endpoint,
$webhook_factory,
$logger,
$verify_request,
... $handler
);
},
'webhook.endpoint.handler' => function( ContainerInterface $container ) : array {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
$prefix = $container->get( 'api.prefix' );
$order_endpoint = $container->get( 'api.endpoint.order' );
return array(
new CheckoutOrderApproved( $logger, $prefix, $order_endpoint ),
new CheckoutOrderCompleted( $logger, $prefix ),
new PaymentCaptureRefunded( $logger, $prefix ),
new PaymentCaptureReversed( $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
/**
* Handles the Webhook CHECKOUT.ORDER.APPROVED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
@ -8,47 +13,86 @@ use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
/**
* Class CheckoutOrderApproved
*/
class CheckoutOrderApproved implements RequestHandler {
use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
private $orderEndpoint;
public function __construct( LoggerInterface $logger, string $prefix, OrderEndpoint $orderEndpoint ) {
$this->logger = $logger;
$this->prefix = $prefix;
$this->orderEndpoint = $orderEndpoint;
/**
* The order endpoint.
*
* @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(
'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 {
$response = array( 'success' => false );
$customIds = array_filter(
/**
* 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 );
$custom_ids = array_filter(
array_map(
static function ( array $purchaseUnit ): string {
return isset( $purchaseUnit['custom_id'] ) ?
(string) $purchaseUnit['custom_id'] : '';
static function ( array $purchase_unit ): string {
return isset( $purchase_unit['custom_id'] ) ?
(string) $purchase_unit['custom_id'] : '';
},
isset( $request['resource']['purchase_units'] ) ?
(array) $request['resource']['purchase_units'] : array()
),
static function ( string $orderId ): bool {
return ! empty( $orderId );
static function ( string $order_id ): bool {
return ! empty( $order_id );
}
);
if ( empty( $customIds ) ) {
if ( empty( $custom_ids ) ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
__(
@ -70,7 +114,7 @@ class CheckoutOrderApproved implements RequestHandler {
try {
$order = isset( $request['resource']['id'] ) ?
$this->orderEndpoint->order( $request['resource']['id'] ) : null;
$this->order_endpoint->order( $request['resource']['id'] ) : null;
if ( ! $order ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
@ -92,7 +136,7 @@ class CheckoutOrderApproved implements RequestHandler {
}
if ( $order->intent() === 'CAPTURE' ) {
$this->orderEndpoint->capture( $order );
$this->order_endpoint->capture( $order );
}
} catch ( RuntimeException $error ) {
$message = sprintf(
@ -114,19 +158,19 @@ class CheckoutOrderApproved implements RequestHandler {
return rest_ensure_response( $response );
}
$wcOrderIds = array_map(
$wc_order_ids = array_map(
array(
$this,
'sanitizeCustomId',
'sanitize_custom_id',
),
$customIds
$custom_ids
);
$args = array(
'post__in' => $wcOrderIds,
$args = array(
'post__in' => $wc_order_ids,
'limit' => -1,
);
$wcOrders = wc_get_orders( $args );
if ( ! $wcOrders ) {
$wc_orders = wc_get_orders( $args );
if ( ! $wc_orders ) {
$message = sprintf(
// translators: %s is the PayPal order Id.
__( 'Order for PayPal order %s not found.', 'woocommerce-paypal-commerce-gateway' ),
@ -143,20 +187,22 @@ class CheckoutOrderApproved implements RequestHandler {
return rest_ensure_response( $response );
}
$newStatus = $order->intent() === 'CAPTURE' ? 'processing' : 'on-hold';
$statusMessage = $order->intent() === 'CAPTURE' ?
$new_status = $order->intent() === 'CAPTURE' ? 'processing' : 'on-hold';
$status_message = $order->intent() === 'CAPTURE' ?
__( 'Payment received.', 'woocommerce-paypal-commerce-gateway' )
: __( 'Payment can be captured.', 'woocommerce-paypal-commerce-gateway' );
foreach ( $wcOrders as $wcOrder ) {
if ( ! in_array( $wcOrder->get_status(), array( 'pending', 'on-hold' ), true ) ) {
foreach ( $wc_orders as $wc_order ) {
if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
continue;
}
/**
* @var \WC_Order $wcOrder
* The Woocommerce order.
*
* @var \WC_Order $wc_order
*/
$wcOrder->update_status(
$newStatus,
$statusMessage
$wc_order->update_status(
$new_status,
$status_message
);
$this->logger->log(
'info',
@ -166,16 +212,15 @@ class CheckoutOrderApproved implements RequestHandler {
'Order %s has been updated through PayPal',
'woocommerce-paypal-commerce-gateway'
),
(string) $wcOrder->get_id()
(string) $wc_order->get_id()
),
array(
'request' => $request,
'order' => $wcOrder,
'order' => $wc_order,
)
);
}
$response['success'] = true;
return rest_ensure_response( $response );
}
//phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
}

View file

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

View file

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

View file

@ -1,4 +1,9 @@
<?php
/**
* Handels the Webhook PAYMENT.CAPTURE.REFUNDED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
@ -6,30 +11,63 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
/**
* Class PaymentCaptureRefunded
*/
class PaymentCaptureRefunded implements RequestHandler {
use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* PaymentCaptureRefunded constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger;
$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' );
}
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 );
$orderId = isset( $request['resource']['custom_id'] ) ?
$this->sanitizeCustomId( $request['resource']['custom_id'] ) : 0;
if ( ! $orderId ) {
$order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
if ( ! $order_id ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
__(
@ -49,8 +87,8 @@ class PaymentCaptureRefunded implements RequestHandler {
return rest_ensure_response( $response );
}
$wcOrder = wc_get_order( $orderId );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf(
// translators: %s is the PayPal refund Id.
__( '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(
array(
'order_id' => $wcOrder->get_id(),
'order_id' => $wc_order->get_id(),
'amount' => $request['resource']['amount']['value'],
)
);
@ -82,7 +122,7 @@ class PaymentCaptureRefunded implements RequestHandler {
sprintf(
// translators: %s is the order id.
__( 'Order %s could not be refunded', 'woocommerce-paypal-commerce-gateway' ),
(string) $wcOrder->get_id()
(string) $wc_order->get_id()
),
array(
'request' => $request,
@ -102,16 +142,15 @@ class PaymentCaptureRefunded implements RequestHandler {
'Order %1$s has been refunded with %2$s through PayPal',
'woocommerce-paypal-commerce-gateway'
),
(string) $wcOrder->get_id(),
(string) $wc_order->get_id(),
(string) $refund->get_amount()
),
array(
'request' => $request,
'order' => $wcOrder,
'order' => $wc_order,
)
);
$response['success'] = true;
return rest_ensure_response( $response );
}
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
}

View file

@ -1,4 +1,12 @@
<?php
/**
* Handles the following Webhooks:
* - PAYMENT.CAPTURE.REVERSED
* - PAYMENT.ORDER.CANCELLED
* - PAYMENT.CAPTURE.DENIED
*
* @package Inpsyde\PayPalCommerce\Webhooks\Handler
*/
declare(strict_types=1);
@ -6,17 +14,37 @@ namespace Inpsyde\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
/**
* Class PaymentCaptureReversed
*/
class PaymentCaptureReversed implements RequestHandler {
use PrefixTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* PaymentCaptureReversed constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
*/
public function __construct( LoggerInterface $logger, string $prefix ) {
$this->logger = $logger;
$this->prefix = $prefix;
}
public function eventTypes(): array {
/**
* The event types a handler handles.
*
* @return array
*/
public function event_types(): array {
return array(
'PAYMENT.CAPTURE.REVERSED',
'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 );
$orderId = isset( $request['resource']['custom_id'] ) ?
$this->sanitizeCustomId( $request['resource']['custom_id'] ) : 0;
if ( ! $orderId ) {
$order_id = isset( $request['resource']['custom_id'] ) ?
$this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
if ( ! $order_id ) {
$message = sprintf(
// translators: %s is the PayPal webhook Id.
__(
@ -53,8 +94,8 @@ class PaymentCaptureReversed implements RequestHandler {
return rest_ensure_response( $response );
}
$wcOrder = wc_get_order( $orderId );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) {
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf(
// translators: %s is the PayPal refund Id.
__( '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(
// translators: %1$s is the order id.
@ -82,21 +125,20 @@ class PaymentCaptureReversed implements RequestHandler {
'Order %1$s has been cancelled through PayPal',
'woocommerce-paypal-commerce-gateway'
),
(string) $wcOrder->get_id()
(string) $wc_order->get_id()
) : sprintf(
// translators: %1$s is the order id.
__( 'Failed to cancel order %1$s through PayPal', 'woocommerce-paypal-commerce-gateway' ),
(string) $wcOrder->get_id()
(string) $wc_order->get_id()
);
$this->logger->log(
$response['success'] ? 'info' : 'warning',
$message,
array(
'request' => $request,
'order' => $wcOrder,
'order' => $wc_order,
)
);
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
/**
* The webhook module.
*
* @package Inpsyde\PayPalCommerce\Webhooks
*/
declare(strict_types=1);
@ -6,15 +11,19 @@ namespace Inpsyde\PayPalCommerce\Webhooks;
use Dhii\Container\ServiceProvider;
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 Psr\Container\ContainerInterface;
/**
* Class WebhookModule
*/
class WebhookModule implements ModuleInterface {
/**
* Setup the Webhook module.
*
* @return ServiceProviderInterface
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
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 ) {
add_action(
'rest_api_init',
static function () use ( $container ) {
$endpoint = $container->get( 'webhook.endpoint.controller' );
/**
* The Incoming Webhook Endpoint.
*
* @var IncomingWebhookEndpoint $endpoint
*/
$endpoint->register();
@ -38,6 +54,11 @@ class WebhookModule implements ModuleInterface {
WebhookRegistrar::EVENT_HOOK,
static function () use ( $container ) {
$registrar = $container->get( 'webhook.registrar' );
/**
* The Webhook Registrar.
*
* @var WebhookRegistrar $endpoint
*/
$registrar->register();
}
);
@ -46,6 +67,11 @@ class WebhookModule implements ModuleInterface {
'woocommerce-paypal-commerce-gateway.deactivate',
static function () use ( $container ) {
$registrar = $container->get( 'webhook.registrar' );
/**
* The Webhook Registrar.
*
* @var WebhookRegistrar $endpoint
*/
$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;
}
}