<?php
/**
* Handles the Webhook CHECKOUT.ORDER.APPROVED
*
* @package WooCommerce\PayPalCommerce\Webhooks\Handler
*/
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use WC_Checkout;
use WC_Order;
use WC_Session_Handler;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\Vendor\Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Session\MemoryWcSession;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
/**
* Class CheckoutOrderApproved
*/
class CheckoutOrderApproved implements \WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandler
{
use \WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The Session handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The funding source renderer.
*
* @var FundingSourceRenderer
*/
protected $funding_source_renderer;
/**
* The processor for orders.
*
* @var OrderProcessor
*/
protected $order_processor;
/**
* CheckoutOrderApproved constructor.
*
* @param LoggerInterface $logger The logger.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param OrderProcessor $order_processor The Order Processor.
*/
public function __construct(LoggerInterface $logger, OrderEndpoint $order_endpoint, SessionHandler $session_handler, FundingSourceRenderer $funding_source_renderer, OrderProcessor $order_processor)
{
$this->logger = $logger;
$this->order_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->funding_source_renderer = $funding_source_renderer;
$this->order_processor = $order_processor;
}
/**
* The event types a handler handles.
*
* @return string[]
*/
public function event_types(): array
{
return array('CHECKOUT.ORDER.APPROVED');
}
/**
* 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);
}
/**
* 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
{
$order_id = isset($request['resource']['id']) ? $request['resource']['id'] : null;
if (!$order_id) {
return $this->failure_response(sprintf('No order ID in webhook event %s.', $request['id'] ?: ''));
}
$order = $this->order_endpoint->order($order_id);
$wc_orders = array();
$wc_order_ids = $this->get_wc_order_ids_from_request($request);
if (empty($wc_order_ids)) {
$customer_ids = $this->get_wc_customer_ids_from_request($request);
if (empty($customer_ids)) {
return $this->no_custom_ids_response($request);
}
$customer_id = $customer_ids[0];
if ($order->status()->is(OrderStatus::COMPLETED)) {
$this->logger->info("Order {$order->id()} already completed.");
return $this->success_response();
}
if (!(bool) apply_filters('woocommerce_paypal_payments_order_approved_webhook_can_create_wc_order', \false)) {
return $this->success_response();
}
$wc_session = new WC_Session_Handler();
$session_data = $wc_session->get_session($customer_id);
if (!is_array($session_data)) {
return $this->failure_response("Failed to get session data {$customer_id}");
}
MemoryWcSession::replace_session_handler($session_data, $customer_id);
wc_load_cart();
WC()->cart->get_cart_from_session();
WC()->cart->calculate_shipping();
$form = $this->session_handler->checkout_form();
if (!$form) {
return $this->failure_response(sprintf('Failed to create WC order in webhook event %s, checkout data not found.', $request['id'] ?: ''));
}
$checkout = new WC_Checkout();
$wc_order_id = $checkout->create_order($form);
$wc_order = wc_get_order($wc_order_id);
if (!$wc_order instanceof WC_Order) {
return $this->failure_response(sprintf('Failed to create WC order in webhook event %s.', $request['id'] ?: ''));
}
$funding_source = $this->session_handler->funding_source();
if ($funding_source) {
$wc_order->set_payment_method_title($this->funding_source_renderer->render_name($funding_source));
}
if (is_numeric($customer_id)) {
$wc_order->set_customer_id((int) $customer_id);
}
$wc_order->save();
$wc_orders[] = $wc_order;
add_action('shutdown', function () use ($customer_id): void {
$session = WC()->session;
assert($session instanceof WC_Session_Handler);
/**
* Wrong type-hint.
*
* @psalm-suppress InvalidScalarArgument
* @phpstan-ignore argument.type
*/
$session->delete_session($customer_id);
$session->forget_session();
});
} else {
$wc_orders = $this->get_wc_orders_from_custom_ids($wc_order_ids);
if (!$wc_orders) {
return $this->no_wc_orders_response($request);
}
}
foreach ($wc_orders as $wc_order) {
if (PayUponInvoiceGateway::ID === $wc_order->get_payment_method() || OXXOGateway::ID === $wc_order->get_payment_method()) {
continue;
}
try {
/**
* This filter controls if the method 'process()' from OrderProcessor will be called.
* So you can implement your own for example on subscriptions
*
* - true bool controls execution of 'OrderProcessor::process()'
* - null because it's mostly for PayPalGateway instance to handle
* - $wc_order \WC_Order
*/
$process = apply_filters('woocommerce_paypal_payments_before_order_process', \true, null, $wc_order);
if ($process) {
$this->order_processor->process($wc_order);
}
} catch (RuntimeException $exception) {
return $this->failure_response(sprintf('Failed to process WC order %s: %s.', (string) $wc_order->get_id(), $exception->getMessage()));
}
$this->logger->info(sprintf('WC order %s has been processed after approval in PayPal.', (string) $wc_order->get_id()));
}
return $this->success_response();
}
}