is_custom_session_order_received_page() ) { add_filter( 'woocommerce_persistent_cart_enabled', '__return_false' ); add_filter( 'woocommerce_cart_session_initialize', '__return_false' ); add_action( 'woocommerce_before_cart_emptied', [ $this, 'save_old_cart_data_for_restore' ] ); add_action( 'woocommerce_cart_emptied', [ $this, 'restore_old_cart_data' ] ); } } /** * Saves an instance of the current cart, so it can be restored later. * Used on the "order received" page for orders placed with the PRB. The "order received" page empties the cart, otherwise. * * @return void */ public function save_old_cart_data_for_restore() { $this->cart_clone = clone WC()->cart; } /** * Restores the cart saved previously. * * @return void */ public function restore_old_cart_data() { if ( ! $this->cart_clone ) { return; } WC()->cart->cart_contents = $this->cart_clone->cart_contents; WC()->cart->removed_cart_contents = $this->cart_clone->removed_cart_contents; WC()->cart->applied_coupons = $this->cart_clone->applied_coupons; $this->cart_clone = null; } /** * Ensuring that the return URL for the "order received" page contains a query string parameter * that can later be identified to ensure we don't clear the cart. * This function is only executed when we're using the custom session handler on Store API requests. * * @param string $return_url The URL for the "Order received" page. * * @return string */ public function store_api_order_received_return_url( $return_url ) { return add_query_arg( self::$prevent_empty_cart_parameter, '1', $return_url ); } /** * Check if the $_SERVER global has order received URL slug in its 'REQUEST_URI' value - just like `wcs_is_order_received_page`. * * Similar to WooCommerce's is_custom_session_order_received_page(), but can be used before the $wp's query vars are setup, which is essential * when preventing the cart from being emptied on the "order received" page, if the order has been placed with WooPayments GooglePay/ApplePay on the product page. * * @return bool **/ private function is_custom_session_order_received_page() { // ignoring because we're not storing the value anywhere, just checking its existence. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash return ( false !== strpos( $_SERVER['REQUEST_URI'], 'order-received' ) ) && ( false !== strpos( $_SERVER['REQUEST_URI'], self::$prevent_empty_cart_parameter ) ); } /** * Generates a session token for the response headers. * * @return string */ protected function get_session_token() { return JsonWebToken::create( [ 'session_id' => WC()->session->session_id, 'exp' => time() + intval( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) ), 'iss' => 'woopayments/product-page', ], '@' . wp_salt() ); } /** * Adding the session key to the Store API response, to ensure the session can be retrieved later. * * @param mixed $response Response to replace the requested version with. * @param \WP_REST_Server $server Server instance. * @param \WP_REST_Request $request Request used to generate the response. * * @return mixed */ public function store_api_headers( $response, $server, $request ) { if ( ! \WC_Payments_Utils::is_store_api_request() ) { return $response; } $nonce = $request->get_header( 'X-WooPayments-Tokenized-Cart-Session-Nonce' ); if ( ! wp_verify_nonce( $nonce, 'woopayments_tokenized_cart_session_nonce' ) ) { return $response; } $response->header( 'X-WooPayments-Tokenized-Cart-Session', $this->get_session_token() ); return $response; } /** * This filter is used to add a custom session handler before processing Store API request callbacks. * This is only necessary because the Store API SessionHandler currently doesn't provide an `init_session_cookie` method. * * @param string $default_session_handler The default session handler class name. * * @return string The session handler class name. */ public function add_payment_request_store_api_session_handler( $default_session_handler ) { $nonce = wc_clean( wp_unslash( $_SERVER['HTTP_X_WOOPAYMENTS_TOKENIZED_CART_SESSION_NONCE'] ?? null ) ); if ( ! wp_verify_nonce( $nonce, 'woopayments_tokenized_cart_session_nonce' ) ) { return $default_session_handler; } if ( ! \WC_Payments_Utils::is_store_api_request() ) { return $default_session_handler; } if ( ! class_exists( JsonWebToken::class ) ) { return $default_session_handler; } // checking if the token is valid, if it's provided. // there can also be a case where the token is not provided, but we should still use the custom session handler. $cart_token = wc_clean( wp_unslash( $_SERVER['HTTP_X_WOOPAYMENTS_TOKENIZED_CART_SESSION'] ?? null ) ); if ( $cart_token && ! JsonWebToken::validate( $cart_token, '@' . wp_salt() ) ) { return $default_session_handler; } // ensures cart contents aren't merged across different sessions for the same customer. add_filter( 'woocommerce_persistent_cart_enabled', '__return_false' ); // when an order is placed via the Store API on product pages, we need to slightly modify the "order received" URL. add_filter( 'woocommerce_get_return_url', [ $this, 'store_api_order_received_return_url' ] ); require_once WCPAY_ABSPATH . '/includes/class-wc-payments-payment-request-session-handler.php'; return WC_Payments_Payment_Request_Session_Handler::class; } }