From e29ef9c7d60e951c4f86a6f84114e4034ebdd40e Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 14 Jun 2023 08:59:30 +0300 Subject: [PATCH] Use continuation mode when APM does not redirect back --- modules/ppcp-button/services.php | 2 + .../src/Endpoint/CreateOrderEndpoint.php | 43 ++++++++++------ .../ppcp-button/src/Helper/ContextTrait.php | 8 +++ modules/ppcp-session/src/SessionHandler.php | 2 + modules/ppcp-session/src/SessionModule.php | 50 +++++++++++++++++++ modules/ppcp-wc-gateway/services.php | 6 +++ .../src/Gateway/PayPalGateway.php | 10 +++- 7 files changed, 105 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index f378a3836..fffd57226 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -125,6 +125,7 @@ return array( $container->get( 'button.basic-checkout-validation-enabled' ), $container->get( 'button.early-wc-checkout-validation-enabled' ), $container->get( 'button.pay-now-contexts' ), + $container->get( 'wcgateway.funding-sources-without-redirect' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, @@ -176,6 +177,7 @@ return array( $container->get( 'button.early-wc-checkout-validation-enabled' ), $container->get( 'button.pay-now-contexts' ), $container->get( 'button.handle-shipping-in-paypal' ), + $container->get( 'wcgateway.funding-sources-without-redirect' ), $logger ); }, diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 83d5f89b1..9ac7aea26 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -152,6 +152,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $handle_shipping_in_paypal; + /** + * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. + * + * @var string[] + */ + private $funding_sources_without_redirect; + /** * The logger. * @@ -175,6 +182,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. * @param string[] $pay_now_contexts The contexts that should have the Pay Now button. * @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup. + * @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -191,23 +199,25 @@ class CreateOrderEndpoint implements EndpointInterface { bool $early_validation_enabled, array $pay_now_contexts, bool $handle_shipping_in_paypal, + array $funding_sources_without_redirect, LoggerInterface $logger ) { - $this->request_data = $request_data; - $this->purchase_unit_factory = $purchase_unit_factory; - $this->shipping_preference_factory = $shipping_preference_factory; - $this->api_endpoint = $order_endpoint; - $this->payer_factory = $payer_factory; - $this->session_handler = $session_handler; - $this->settings = $settings; - $this->early_order_handler = $early_order_handler; - $this->registration_needed = $registration_needed; - $this->card_billing_data_mode = $card_billing_data_mode; - $this->early_validation_enabled = $early_validation_enabled; - $this->pay_now_contexts = $pay_now_contexts; - $this->handle_shipping_in_paypal = $handle_shipping_in_paypal; - $this->logger = $logger; + $this->request_data = $request_data; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->shipping_preference_factory = $shipping_preference_factory; + $this->api_endpoint = $order_endpoint; + $this->payer_factory = $payer_factory; + $this->session_handler = $session_handler; + $this->settings = $settings; + $this->early_order_handler = $early_order_handler; + $this->registration_needed = $registration_needed; + $this->card_billing_data_mode = $card_billing_data_mode; + $this->early_validation_enabled = $early_validation_enabled; + $this->pay_now_contexts = $pay_now_contexts; + $this->handle_shipping_in_paypal = $handle_shipping_in_paypal; + $this->funding_sources_without_redirect = $funding_sources_without_redirect; + $this->logger = $logger; } /** @@ -288,6 +298,11 @@ class CreateOrderEndpoint implements EndpointInterface { } if ( 'checkout' === $data['context'] ) { + if ( ! in_array( $funding_source, $this->funding_sources_without_redirect, true ) ) { + $this->session_handler->replace_order( $order ); + $this->session_handler->replace_funding_source( $funding_source ); + } + if ( ! $this->early_order_handler->should_create_early_order() || $this->registration_needed diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index daddbb0a9..c8125066d 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button\Helper; +use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; + trait ContextTrait { /** @@ -56,6 +58,12 @@ trait ContextTrait { return false; } + if ( ! $order->status()->is( OrderStatus::APPROVED ) + && ! $order->status()->is( OrderStatus::COMPLETED ) + ) { + return false; + } + $source = $order->payment_source(); if ( $source && $source->card() ) { return false; // Ignore for DCC. diff --git a/modules/ppcp-session/src/SessionHandler.php b/modules/ppcp-session/src/SessionHandler.php index 7bceb34a6..be269332e 100644 --- a/modules/ppcp-session/src/SessionHandler.php +++ b/modules/ppcp-session/src/SessionHandler.php @@ -55,6 +55,8 @@ class SessionHandler { public function order() { $this->load_session(); + do_action( 'ppcp_session_get_order', $this->order, $this ); + return $this->order; } diff --git a/modules/ppcp-session/src/SessionModule.php b/modules/ppcp-session/src/SessionModule.php index a27effb47..cecc1a98f 100644 --- a/modules/ppcp-session/src/SessionModule.php +++ b/modules/ppcp-session/src/SessionModule.php @@ -9,6 +9,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Session; +use Psr\Log\LoggerInterface; +use Throwable; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; +use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController; @@ -19,6 +24,12 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; * Class SessionModule */ class SessionModule implements ModuleInterface { + /** + * A flag to avoid multiple requests to reload order. + * + * @var bool + */ + private $reloaded_order = false; /** * {@inheritDoc} @@ -46,6 +57,45 @@ class SessionModule implements ModuleInterface { $controller->run(); } ); + + add_action( + 'ppcp_session_get_order', + function ( ?Order $order, SessionHandler $session_handler ) use ( $c ): void { + if ( ! isset( WC()->session ) ) { + return; + } + + if ( $this->reloaded_order ) { + return; + } + + if ( ! $order ) { + return; + } + + if ( $order->status()->is( OrderStatus::APPROVED ) + || $order->status()->is( OrderStatus::COMPLETED ) + ) { + return; + } + + $order_endpoint = $c->get( 'api.endpoint.order' ); + assert( $order_endpoint instanceof OrderEndpoint ); + + $this->reloaded_order = true; + + try { + $session_handler->replace_order( $order_endpoint->order( $order->id() ) ); + } catch ( Throwable $exception ) { + $logger = $c->get( 'woocommerce.logger.woocommerce' ); + assert( $logger instanceof LoggerInterface ); + + $logger->warning( 'Failed to reload PayPal order in the session: ' . $exception->getMessage() ); + } + }, + 10, + 2 + ); } /** diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index c90fe0ef7..c2244f348 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -903,6 +903,12 @@ return array( 'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ), ); }, + /** + * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. + */ + 'wcgateway.funding-sources-without-redirect' => static function( ContainerInterface $container ): array { + return array( 'paypal', 'paylater', 'venmo', 'card' ); + }, 'wcgateway.settings.funding-sources' => static function( ContainerInterface $container ): array { return array_diff_key( $container->get( 'wcgateway.all-funding-sources' ), diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 0a5835c31..71634f07a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; @@ -253,8 +254,13 @@ class PayPalGateway extends \WC_Payment_Gateway { $funding_source = $this->session_handler->funding_source(); if ( $funding_source ) { - $this->title = $this->funding_source_renderer->render_name( $funding_source ); - $this->description = $this->funding_source_renderer->render_description( $funding_source ); + $order = $this->session_handler->order(); + if ( $order && + ( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::COMPLETED ) ) + ) { + $this->title = $this->funding_source_renderer->render_name( $funding_source ); + $this->description = $this->funding_source_renderer->render_description( $funding_source ); + } } $this->init_form_fields();