From cda27b590e5ba0185318ff2ea04d5e5cc9cf0772 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Tue, 26 Aug 2025 11:06:56 +0300 Subject: [PATCH 1/6] Set customer id after cross-browser appswitch when guest --- .../src/Helper/WooCommerceOrderCreator.php | 13 ++++++++----- modules/ppcp-button/src/Session/CartData.php | 11 +++++++++++ modules/ppcp-button/src/Session/CartDataFactory.php | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 10b7905673..bcc6e4dd1e 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -97,7 +97,7 @@ public function create_from_paypal_order( Order $order, $cart ): WC_Order { $shipping = ! empty( $purchase_units ) ? $purchase_units[0]->shipping() : null; $this->configure_payment_source( $wc_order ); - $this->configure_customer( $wc_order ); + $this->configure_customer( $wc_order, $cart_data ); $this->configure_line_items( $wc_order, $cart_data, $payer, $shipping ); $this->configure_addresses( $wc_order, $payer, $shipping, $cart_data->needs_shipping() ); $this->configure_coupons( $wc_order, $cart_data->coupons() ); @@ -293,15 +293,18 @@ protected function configure_payment_source( WC_Order $wc_order ): void { /** * Configures the customer ID. - * - * @param WC_Order $wc_order The WC order. - * @return void */ - protected function configure_customer( WC_Order $wc_order ): void { + protected function configure_customer( WC_Order $wc_order, CartData $cart_data ): void { $current_user = wp_get_current_user(); if ( $current_user->ID !== 0 ) { $wc_order->set_customer_id( $current_user->ID ); + return; + } + + $saved_user_id = $cart_data->user_id(); + if ( $saved_user_id !== 0 ) { + $wc_order->set_customer_id( $saved_user_id ); } } diff --git a/modules/ppcp-button/src/Session/CartData.php b/modules/ppcp-button/src/Session/CartData.php index 8f5ad1b66e..7e2d692926 100644 --- a/modules/ppcp-button/src/Session/CartData.php +++ b/modules/ppcp-button/src/Session/CartData.php @@ -22,6 +22,8 @@ class CartData { protected bool $needs_shipping; + protected int $user_id; + protected string $cart_hash; protected ?string $paypal_order_id = null; @@ -30,17 +32,20 @@ class CartData { * @param array> $items The cart items like in $cart->get_cart_for_session() or $cart->get_cart(). * @param string[] $coupons * @param bool $needs_shipping + * @param int $user_id * @param string $cart_hash */ public function __construct( array $items, array $coupons, bool $needs_shipping, + int $user_id, string $cart_hash ) { $this->items = $items; $this->coupons = $coupons; $this->needs_shipping = $needs_shipping; + $this->user_id = $user_id; $this->cart_hash = $cart_hash; } @@ -78,6 +83,10 @@ public function needs_shipping(): bool { return $this->needs_shipping; } + public function user_id(): int { + return $this->user_id; + } + public function cart_hash(): string { return $this->cart_hash; } @@ -95,6 +104,7 @@ public function to_array(): array { 'items' => $this->items, 'coupons' => $this->coupons, 'needs_shipping' => $this->needs_shipping, + 'user_id' => $this->user_id, 'cart_hash' => $this->cart_hash, 'paypal_order_id' => $this->paypal_order_id, ); @@ -105,6 +115,7 @@ public static function from_array( array $data, ?string $key = null ): CartData $data['items'] ?? array(), $data['coupons'] ?? array(), (bool) ( $data['needs_shipping'] ?? false ), + (int) ( $data['user_id'] ?? 0 ), $data['cart_hash'] ?? '' ); $cart_data->paypal_order_id = $data['paypal_order_id'] ?? null; diff --git a/modules/ppcp-button/src/Session/CartDataFactory.php b/modules/ppcp-button/src/Session/CartDataFactory.php index de5ef6b6fe..e0ff541517 100644 --- a/modules/ppcp-button/src/Session/CartDataFactory.php +++ b/modules/ppcp-button/src/Session/CartDataFactory.php @@ -28,6 +28,7 @@ public function from_current_cart( ?WC_Cart $cart = null ): CartData { $cart->get_cart_for_session(), $cart->get_applied_coupons(), $cart->needs_shipping(), + get_current_user_id(), $cart->get_cart_hash() ); } From 420ade28ff4068bc0880595f50dc95de96afcc8a Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Tue, 26 Aug 2025 11:11:29 +0300 Subject: [PATCH 2/6] Skip log in for guests in cross-browser appswitch --- modules/ppcp-button/src/ButtonModule.php | 56 +++++++++++++++++++ .../src/Gateway/PayPalGateway.php | 2 + 2 files changed, 58 insertions(+) diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index fbbe3e3884..683cd4a76b 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -9,6 +9,7 @@ namespace WooCommerce\PayPalCommerce\Button; +use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Factory\ReturnUrlFactory; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; @@ -31,6 +32,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; /** * Class ButtonModule @@ -299,10 +301,64 @@ static function () use ( $container ) { $wc_order = $wc_order_creator->create_from_paypal_order( $paypal_order, $cart_data ); + $wc_order->update_meta_data( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY, wc_bool_to_string( true ) ); + $wc_order->save(); + // Redirect via JS because we need to keep the # parameters which are not accessible on the server side. // phpcs:ignore WordPress.Security.EscapeOutput echo ""; } ); + + /** + * By default, WC asks to log in when opening a non-guest Pay for order page as a guest, + * so we disable this for cross-browser AppSwitch. + * + * @param array $allcaps Array of key/value pairs where keys represent a capability name + * and boolean values represent whether the user has that capability. + * @param string[] $caps Required primitive capabilities for the requested capability. + * @param array $args { + * Arguments that accompany the requested capability check. + * + * @type string $0 Requested capability. + * @type int $1 Concerned user ID. + * @type mixed ...$2 Optional second and further parameters, typically object ID. + * } + * + * @returns array + * + * @psalm-suppress MissingClosureParamType + */ + add_filter( + 'user_has_cap', + static function ( $allcaps, $cap, $args ) { + if ( ! in_array( 'pay_for_order', $cap, true ) ) { + return $allcaps; + } + + $wc_order_id = $args[2] ?? null; + if ( ! is_int( $wc_order_id ) ) { + return $allcaps; + } + + $wc_order = wc_get_order( $wc_order_id ); + if ( ! $wc_order instanceof WC_Order ) { + return $allcaps; + } + + if ( ! wc_string_to_bool( $wc_order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + return $allcaps; + } + + return array_merge( + $allcaps, + array( + 'pay_for_order' => true, + ) + ); + }, + 10, + 3 + ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index dece77ba2e..1d44d2c8a7 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -60,6 +60,8 @@ class PayPalGateway extends \WC_Payment_Gateway { public const ORIGINAL_EMAIL_META_KEY = '_ppcp_paypal_billing_email'; public const ORIGINAL_PHONE_META_KEY = '_ppcp_paypal_billing_phone'; + public const CROSS_BROWSER_APPSWITCH_META_KEY = '_ppcp_cross_browser_appswitch'; + /** * List of payment sources for which we are expected to store the payer email in the WC Order metadata. */ From e6da80bc15a9e1faea619943cc0c91b3b25f5c31 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Tue, 26 Aug 2025 11:21:32 +0300 Subject: [PATCH 3/6] Disable email prompt on Pay for order page for guests after cross-browser AppSwitch --- modules/ppcp-button/src/ButtonModule.php | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index 683cd4a76b..280198418a 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -360,5 +360,36 @@ static function ( $allcaps, $cap, $args ) { 10, 3 ); + + /** + * Disabling the email prompt on Pay for order page for guests. + * Should not affect anything in most cases because it is skipped for just created orders (< 10 min). + * + * @param bool $email_verification_required + * @param WC_Order $order + * + * @returns bool + * + * @psalm-suppress MissingClosureParamType + */ + add_filter( + 'woocommerce_order_email_verification_required', + static function ( + $email_verification_required, + $order + ) { + if ( ! $order instanceof WC_Order ) { + return $email_verification_required; + } + + if ( ! wc_string_to_bool( $order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + return $email_verification_required; + } + + return false; + }, + 10, + 2 + ); } } From 64f9120d1cb5c65b1ba2dca6fb58c57867f1c2b3 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Tue, 26 Aug 2025 11:39:05 +0300 Subject: [PATCH 4/6] Skip log in for guests in cross-browser AppSwitch on order received page --- modules/ppcp-button/src/ButtonModule.php | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index 280198418a..1562c7addf 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -361,6 +361,44 @@ static function ( $allcaps, $cap, $args ) { 3 ); + /** + * By default, WC asks to log in when opening a non-guest order received page as a guest, + * so we disable this for cross-browser AppSwitch. + * + * @param bool $result + * + * @psalm-suppress MissingClosureParamType + */ + add_filter( + 'woocommerce_order_received_verify_known_shoppers', + static function( $result ) { + if ( ! is_order_received_page() ) { + return $result; + } + + $wc_order = null; + // phpcs:disable WordPress.Security.NonceVerification + if ( isset( $_GET['key'] ) && is_string( $_GET['key'] ) ) { + $wc_order_key = sanitize_text_field( wp_unslash( $_GET['key'] ) ); + // phpcs:enable WordPress.Security.NonceVerification + + $wc_order_id = wc_get_order_id_by_order_key( $wc_order_key ); + + $wc_order = wc_get_order( $wc_order_id ); + } + + if ( ! $wc_order instanceof WC_Order ) { + return $result; + } + + if ( ! wc_string_to_bool( $wc_order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + return $result; + } + + return false; + } + ); + /** * Disabling the email prompt on Pay for order page for guests. * Should not affect anything in most cases because it is skipped for just created orders (< 10 min). From 88347ece39f9fe8e525f8dc8cc3ef37eaa8f3d64 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Tue, 26 Aug 2025 14:39:24 +0300 Subject: [PATCH 5/6] Refactor --- modules/ppcp-button/src/ButtonModule.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index 1562c7addf..e6d05ca43e 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -237,6 +237,10 @@ static function () use ( $container ) { ); } + private static function is_cross_browser_order( WC_Order $wc_order ): bool { + return wc_string_to_bool( $wc_order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ); + } + private function register_appswitch_crossbrowser_handler( ContainerInterface $container ): void { if ( ! $container->get( 'wcgateway.appswitch-enabled' ) ) { return; @@ -346,7 +350,7 @@ static function ( $allcaps, $cap, $args ) { return $allcaps; } - if ( ! wc_string_to_bool( $wc_order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + if ( ! self::is_cross_browser_order( $wc_order ) ) { return $allcaps; } @@ -391,7 +395,7 @@ static function( $result ) { return $result; } - if ( ! wc_string_to_bool( $wc_order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + if ( ! self::is_cross_browser_order( $wc_order ) ) { return $result; } @@ -420,7 +424,7 @@ static function ( return $email_verification_required; } - if ( ! wc_string_to_bool( $order->get_meta( PayPalGateway::CROSS_BROWSER_APPSWITCH_META_KEY ) ) ) { + if ( ! self::is_cross_browser_order( $order ) ) { return $email_verification_required; } From 58e6f3beb74f6b149b00bf52af36b77a30a4feb8 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Fri, 29 Aug 2025 14:28:41 +0300 Subject: [PATCH 6/6] Fix phpcs --- modules/ppcp-button/src/ButtonModule.php | 6 +++--- .../ppcp-settings/src/Data/Definition/TodosDefinition.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index e6d05ca43e..ec1a440c52 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -375,7 +375,7 @@ static function ( $allcaps, $cap, $args ) { */ add_filter( 'woocommerce_order_received_verify_known_shoppers', - static function( $result ) { + static function ( $result ) { if ( ! is_order_received_page() ) { return $result; } @@ -417,8 +417,8 @@ static function( $result ) { add_filter( 'woocommerce_order_email_verification_required', static function ( - $email_verification_required, - $order + $email_verification_required, + $order ) { if ( ! $order instanceof WC_Order ) { return $email_verification_required; diff --git a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php index deeb7202dc..dcef36b4ca 100644 --- a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php @@ -244,13 +244,13 @@ public function get(): array { ), 'priority' => 14, ), - 'pay_later_messaging_is_auto_enabled' => array( + 'pay_later_messaging_is_auto_enabled' => array( 'title' => __( 'PayPal Pay Later messaging successfully enabled', 'woocommerce-paypal-payments' ), 'description' => __( 'PayPal is now displaying this flexible payment option earlier in the shopping experience. This update was made based on your “Stay Updated” preference and the messaging can be customized or disabled through the Pay Later settings.', 'woocommerce-paypal-payments' ), 'isEligible' => $eligibility_checks['pay_later_messaging_is_auto_enabled'], 'action' => array( - 'type' => 'tab', - 'tab' => 'pay_later_messaging', + 'type' => 'tab', + 'tab' => 'pay_later_messaging', ), 'priority' => 15, ),