gateway = $gateway; $this->order_service = $order_service; } /** * Checks if the attached payment intent was successful for the current order. * * @param WC_Order $order Current order to check. * * @return array|void A successful response in case the attached intent was successful, null if none. */ public function check_payment_intent_attached_to_order_succeeded( WC_Order $order ) { $intent_id = (string) $order->get_meta( '_intent_id', true ); if ( empty( $intent_id ) ) { return; } // We only care about payment intent. $is_payment_intent = 'pi_' === substr( $intent_id, 0, 3 ); if ( ! $is_payment_intent ) { return; } try { $request = Get_Intention::create( $intent_id ); $request->set_hook_args( $order ); /** @var \WC_Payments_API_Abstract_Intention $intent */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort $intent = $request->send(); $intent_status = $intent->get_status(); } catch ( Exception $e ) { Logger::error( 'Failed to fetch attached payment intent: ' . $e ); return; } if ( ! $intent->is_authorized() ) { return; } $intent_meta_order_id_raw = $intent->get_metadata()['order_id'] ?? ''; $intent_meta_order_id = is_numeric( $intent_meta_order_id_raw ) ? intval( $intent_meta_order_id_raw ) : 0; $intent_meta_order_number_raw = $intent->get_metadata()['order_number'] ?? ''; $intent_meta_order_number = is_numeric( $intent_meta_order_number_raw ) ? intval( $intent_meta_order_number_raw ) : 0; $paid_on_woopay = filter_var( $intent->get_metadata()['paid_on_woopay'] ?? false, FILTER_VALIDATE_BOOLEAN ); $is_woopay_order = $order->get_id() === $intent_meta_order_number; if ( ! ( $paid_on_woopay && $is_woopay_order ) && $intent_meta_order_id !== $order->get_id() ) { return; } if ( Intent_Status::SUCCEEDED === $intent_status ) { $this->remove_session_processing_order( $order->get_id() ); } $this->order_service->update_order_status_from_intent( $order, $intent ); $return_url = $this->gateway->get_return_url( $order ); $return_url = add_query_arg( self::FLAG_PREVIOUS_SUCCESSFUL_INTENT, 'yes', $return_url ); return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html#method_get_return_url is passed in. 'result' => 'success', 'redirect' => $return_url, ]; } /** * Checks if the current order has the same content with the session processing order, which was already paid (ex. by a webhook). * * @param WC_Order $current_order Current order in process_payment. * * @return array|void A successful response in case the session processing order was paid, null if none. */ public function check_against_session_processing_order( WC_Order $current_order ) { $session_order_id = $this->get_session_processing_order(); if ( null === $session_order_id ) { return; } $session_order = wc_get_order( $session_order_id ); if ( ! is_a( $session_order, 'WC_Order' ) ) { return; } if ( $current_order->get_cart_hash() !== $session_order->get_cart_hash() ) { return; } if ( ! $session_order->has_status( wc_get_is_paid_statuses() ) ) { return; } if ( ! $current_order->has_status( wc_get_is_pending_statuses() ) ) { return; } if ( $session_order->get_id() === $current_order->get_id() ) { return; } if ( $session_order->get_customer_id() !== $current_order->get_customer_id() ) { return; } $session_order->add_order_note( sprintf( /* translators: order ID integer number */ __( 'WooCommerce Payments: detected and deleted order ID %d, which has duplicate cart content with this order.', 'woocommerce-payments' ), $current_order->get_id() ) ); $current_order->delete(); $this->remove_session_processing_order( $session_order_id ); $return_url = $this->gateway->get_return_url( $session_order ); $return_url = add_query_arg( self::FLAG_PREVIOUS_ORDER_PAID, 'yes', $return_url ); return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html#method_get_return_url is passed in. 'result' => 'success', 'redirect' => $return_url, ]; } /** * Update the processing order ID for the current session. * * @param int $order_id Order ID. * * @return void */ public function maybe_update_session_processing_order( int $order_id ) { if ( WC()->session ) { WC()->session->set( self::SESSION_KEY_PROCESSING_ORDER, $order_id ); } } /** * Remove the provided order ID from the current session if it matches with the ID in the session. * * @param int $order_id Order ID to remove from the session. * * @return void */ public function remove_session_processing_order( int $order_id ) { $current_session_id = $this->get_session_processing_order(); if ( $order_id === $current_session_id && WC()->session ) { WC()->session->set( self::SESSION_KEY_PROCESSING_ORDER, null ); } } /** * Get the processing order ID for the current session. * * @return integer|null Order ID. Null if the value is not set. */ protected function get_session_processing_order() { $session = WC()->session; if ( null === $session ) { return null; } $val = $session->get( self::SESSION_KEY_PROCESSING_ORDER ); return null === $val ? null : absint( $val ); } }