<?php
/**
* Endpoint to verify if an order has been approved. An approved order
* will be stored in the current session.
*
* @package WooCommerce\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
* Class ApproveOrderEndpoint
*/
class ApproveOrderEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-approve-order';
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The session handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $api_endpoint;
/**
* The 3d secure helper object.
*
* @var ThreeDSecure
*/
private $threed_secure;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The DCC applies object.
*
* @var DccApplies
*/
private $dcc_applies;
/**
* The order helper.
*
* @var OrderHelper
*/
protected $order_helper;
/**
* Whether the final review is enabled.
*
* @var bool
*/
protected $final_review_enabled;
/**
* The WC gateway.
*
* @var PayPalGateway
*/
protected $gateway;
/**
* The WooCommerce order creator.
*
* @var WooCommerceOrderCreator
*/
protected $wc_order_creator;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* ApproveOrderEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param ThreeDSecure $three_d_secure The 3d secure helper object.
* @param Settings $settings The settings.
* @param DccApplies $dcc_applies The DCC applies object.
* @param OrderHelper $order_helper The order helper.
* @param bool $final_review_enabled Whether the final review is enabled.
* @param PayPalGateway $gateway The WC gateway.
* @param WooCommerceOrderCreator $wc_order_creator The WooCommerce order creator.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
SessionHandler $session_handler,
ThreeDSecure $three_d_secure,
Settings $settings,
DccApplies $dcc_applies,
OrderHelper $order_helper,
bool $final_review_enabled,
PayPalGateway $gateway,
WooCommerceOrderCreator $wc_order_creator,
LoggerInterface $logger
) {
$this->request_data = $request_data;
$this->api_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->threed_secure = $three_d_secure;
$this->settings = $settings;
$this->dcc_applies = $dcc_applies;
$this->order_helper = $order_helper;
$this->final_review_enabled = $final_review_enabled;
$this->gateway = $gateway;
$this->wc_order_creator = $wc_order_creator;
$this->logger = $logger;
}
/**
* The nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws RuntimeException When order not found or handling failed.
*/
public function handle_request(): bool {
try {
$data = $this->request_data->read_request( $this->nonce() );
if ( ! isset( $data['order_id'] ) ) {
throw new RuntimeException(
__( 'No order id given', 'woocommerce-paypal-payments' )
);
}
$order = $this->api_endpoint->order( $data['order_id'] );
$payment_source = $order->payment_source();
if ( $payment_source && $payment_source->name() === 'card' ) {
if ( $this->settings->has( 'disable_cards' ) ) {
$disabled_cards = (array) $this->settings->get( 'disable_cards' );
$card = strtolower( $payment_source->properties()->brand ?? '' );
if ( 'master_card' === $card ) {
$card = 'mastercard';
}
if ( ! $this->dcc_applies->can_process_card( $card ) || in_array( $card, $disabled_cards, true ) ) {
throw new RuntimeException(
__(
'Unfortunately, we do not accept this card.',
'woocommerce-paypal-payments'
),
100
);
}
}
$proceed = $this->threed_secure->proceed_with_order( $order );
if ( ThreeDSecure::RETRY === $proceed ) {
throw new RuntimeException(
__(
'Something went wrong. Please try again.',
'woocommerce-paypal-payments'
)
);
}
if ( ThreeDSecure::REJECT === $proceed ) {
throw new RuntimeException(
__(
'Unfortunately, we can\'t accept your card. Please choose a different payment method.',
'woocommerce-paypal-payments'
)
);
}
$this->session_handler->replace_order( $order );
wp_send_json_success();
}
if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) {
$message = sprintf(
// translators: %s is the id of the order.
__( 'Order %s is not ready for processing yet.', 'woocommerce-paypal-payments' ),
$data['order_id']
);
$this->logger->log( 'error', $message );
throw new RuntimeException( $message );
}
$funding_source = $data['funding_source'] ?? null;
$this->session_handler->replace_funding_source( $funding_source );
$this->session_handler->replace_order( $order );
if ( apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
$this->toggle_final_review_enabled_setting();
}
$should_create_wc_order = $data['should_create_wc_order'] ?? false;
if ( ! $this->final_review_enabled && ! $this->is_checkout() && $should_create_wc_order ) {
$wc_order = $this->wc_order_creator->create_from_paypal_order( $order, WC()->cart );
$this->gateway->process_payment( $wc_order->get_id() );
$order_received_url = $wc_order->get_checkout_order_received_url();
wp_send_json_success( array( 'order_received_url' => $order_received_url ) );
}
wp_send_json_success();
return true;
} catch ( Exception $error ) {
$this->logger->error( 'Order approve failed: ' . $error->getMessage() );
wp_send_json_error(
array(
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
'message' => $error->getMessage(),
'code' => $error->getCode(),
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
)
);
return false;
}
}
/**
* Will toggle the "final confirmation" checkbox.
*
* @return void
*/
protected function toggle_final_review_enabled_setting(): void {
$final_review_enabled_setting = $this->settings->has( 'blocks_final_review_enabled' ) && $this->settings->get( 'blocks_final_review_enabled' );
$final_review_enabled_setting ? $this->settings->set( 'blocks_final_review_enabled', false ) : $this->settings->set( 'blocks_final_review_enabled', true );
$this->settings->persist();
}
}