$handler * @return mixed */ public function capture_gateway_snapshot( $response, array $handler, WP_REST_Request $request ) { if ( ! $this->is_store_checkout_request( $request ) ) { return $response; } $trace_id = $this->get_trace_id( $request ); if ( '' === $trace_id || ! function_exists( 'WC' ) || ! WC() ) { return $response; } try { CheckoutTraceService::record( [ 'traceId' => $trace_id, 'event' => 'gateway_availability_snapshot', 'step' => $this->get_trace_step( $request ), 'status' => 'info', 'origin' => 'backend', 'gatewayId' => sanitize_key( (string) $request->get_param( 'payment_method' ) ), 'context' => $this->build_gateway_snapshot( $request, $response ), ] ); } catch ( Throwable $exception ) { CheckoutTraceService::record( [ 'traceId' => $trace_id, 'event' => 'gateway_availability_snapshot_failed', 'step' => $this->get_trace_step( $request ), 'status' => 'warning', 'origin' => 'backend', 'message' => $exception->getMessage(), 'context' => [ 'route' => $request->get_route(), ], ] ); } return $response; } /** * @return array */ private function build_gateway_snapshot( WP_REST_Request $request, $response = null ): array { $registered_gateways = []; $available_gateways = []; $payment_gateways = WC()->payment_gateways(); if ( $payment_gateways ) { $registered_gateways = $payment_gateways->payment_gateways(); $available_gateways = $payment_gateways->get_available_payment_gateways(); } $enabled_gateway_ids = $this->get_enabled_gateway_ids( is_array( $registered_gateways ) ? $registered_gateways : [] ); $available_gateway_ids = $this->sanitize_gateway_ids( array_keys( is_array( $available_gateways ) ? $available_gateways : [] ) ); $block_runtime_data = $this->get_active_block_payment_method_data(); $woo_context_resolver = CheckoutPaymentMethodContextResolver::from_checkout_context( is_array( $available_gateways ) ? $available_gateways : [], $block_runtime_data ); $woo_context_snapshot = $woo_context_resolver->context_snapshot(); $woo_context_available_gateway_ids = []; $woo_context_hidden_gateway_ids = []; foreach ( $enabled_gateway_ids as $gateway_id ) { $context = $woo_context_resolver->gateway_context( $gateway_id ); if ( ! empty( $context['available'] ) ) { $woo_context_available_gateway_ids[] = $gateway_id; } else { $woo_context_hidden_gateway_ids[] = $gateway_id; } } $enabled_unavailable_gateway_ids = array_values( array_diff( $enabled_gateway_ids, $available_gateway_ids ) ); $selected_gateway = sanitize_key( (string) $request->get_param( 'payment_method' ) ); $selected_gateway_context = '' !== $selected_gateway ? $woo_context_resolver->gateway_context( $selected_gateway ) : []; $store_api_response_payment_methods = $this->get_response_payment_methods( $response ); return [ 'route' => $request->get_route(), 'selected_gateway' => $selected_gateway, 'selected_gateway_enabled' => '' !== $selected_gateway && in_array( $selected_gateway, $enabled_gateway_ids, true ), 'selected_gateway_available' => '' !== $selected_gateway && in_array( $selected_gateway, $available_gateway_ids, true ), 'selected_gateway_woo_context_available' => '' !== $selected_gateway && ! empty( $selected_gateway_context['available'] ), 'selected_gateway_availability_reason' => '' === $selected_gateway || in_array( $selected_gateway, $available_gateway_ids, true ) ? '' : 'not_in_woocommerce_available_payment_gateways', 'selected_gateway_woo_context' => $selected_gateway_context, 'enabled_gateway_count' => count( $enabled_gateway_ids ), 'available_gateway_count' => count( $available_gateway_ids ), 'available_gateway_ids' => $available_gateway_ids, 'enabled_unavailable_gateway_ids' => $enabled_unavailable_gateway_ids, 'woo_context_available_gateway_count' => count( $woo_context_available_gateway_ids ), 'woo_context_available_gateway_ids' => $woo_context_available_gateway_ids, 'woo_context_hidden_gateway_ids' => $woo_context_hidden_gateway_ids, 'woo_blocks_payment_method_ids' => is_array( $woo_context_snapshot['blockPaymentMethodIds'] ?? null ) ? $woo_context_snapshot['blockPaymentMethodIds'] : [], 'woo_blocks_runtime_gateway_ids' => is_array( $woo_context_snapshot['blockRuntimeGatewayIds'] ?? null ) ? $woo_context_snapshot['blockRuntimeGatewayIds'] : [], 'store_api_response_payment_methods' => $store_api_response_payment_methods, 'store_api_response_payment_method_count' => count( $store_api_response_payment_methods ), 'woo_context_snapshot' => $woo_context_snapshot, 'cart_needs_payment' => $this->cart_needs_payment(), 'cart_needs_shipping' => $this->cart_needs_shipping(), 'cart_item_count' => $this->get_cart_item_count(), 'cart_total' => $this->get_cart_total(), 'chosen_shipping_methods' => $this->get_chosen_shipping_methods(), 'available_payment_filter_count' => $this->count_filter_callbacks( 'woocommerce_available_payment_gateways' ), ]; } /** * @return array> */ private function get_active_block_payment_method_data(): array { if ( ! class_exists( '\Automattic\WooCommerce\Blocks\Package' ) || ! class_exists( '\Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry' ) ) { return []; } try { $container = \Automattic\WooCommerce\Blocks\Package::container(); $registry = $container->get( \Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry::class ); if ( ! is_object( $registry ) || ! method_exists( $registry, 'get_all_registered_script_data' ) ) { return []; } $script_data = $registry->get_all_registered_script_data(); $payment_method_data = $script_data['paymentMethodData'] ?? []; return is_array( $payment_method_data ) ? $payment_method_data : []; } catch ( Throwable $exception ) { return []; } } /** * @param mixed $response * @return array */ private function get_response_payment_methods( $response ): array { if ( is_wp_error( $response ) ) { return []; } if ( is_object( $response ) && method_exists( $response, 'get_data' ) ) { $data = $response->get_data(); } elseif ( is_array( $response ) ) { $data = $response; } else { return []; } if ( ! is_array( $data ) || ! is_array( $data['payment_methods'] ?? null ) ) { return []; } return $this->sanitize_gateway_ids( $data['payment_methods'] ); } /** * @param array $gateways * @return array */ private function get_enabled_gateway_ids( array $gateways ): array { $ids = []; foreach ( $gateways as $gateway_key => $gateway ) { if ( ! is_object( $gateway ) ) { continue; } $is_enabled = isset( $gateway->enabled ) && 'yes' === (string) $gateway->enabled; if ( ! $is_enabled ) { continue; } $ids[] = isset( $gateway->id ) ? (string) $gateway->id : (string) $gateway_key; } return $this->sanitize_gateway_ids( $ids ); } /** * @param array $ids * @return array */ private function sanitize_gateway_ids( array $ids ): array { $sanitized = []; foreach ( $ids as $id ) { $gateway_id = sanitize_key( (string) $id ); if ( '' !== $gateway_id ) { $sanitized[] = $gateway_id; } } return array_values( array_unique( $sanitized ) ); } private function cart_needs_payment(): bool { return isset( WC()->cart ) && is_object( WC()->cart ) && method_exists( WC()->cart, 'needs_payment' ) && (bool) WC()->cart->needs_payment(); } private function cart_needs_shipping(): bool { return isset( WC()->cart ) && is_object( WC()->cart ) && method_exists( WC()->cart, 'needs_shipping' ) && (bool) WC()->cart->needs_shipping(); } private function get_cart_item_count(): int { if ( ! isset( WC()->cart ) || ! is_object( WC()->cart ) || ! method_exists( WC()->cart, 'get_cart_contents_count' ) ) { return 0; } return (int) WC()->cart->get_cart_contents_count(); } private function get_cart_total(): string { if ( ! isset( WC()->cart ) || ! is_object( WC()->cart ) || ! method_exists( WC()->cart, 'get_total' ) ) { return ''; } return sanitize_text_field( (string) WC()->cart->get_total( 'edit' ) ); } /** * @return array */ private function get_chosen_shipping_methods(): array { if ( ! isset( WC()->session ) || ! is_object( WC()->session ) || ! method_exists( WC()->session, 'get' ) ) { return []; } $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); if ( ! is_array( $chosen_methods ) ) { return []; } return array_values( array_filter( array_map( static fn( $method ): string => sanitize_text_field( (string) $method ), array_slice( $chosen_methods, 0, 8 ) ) ) ); } private function count_filter_callbacks( string $hook_name ): int { global $wp_filter; if ( ! isset( $wp_filter[ $hook_name ] ) || ! is_object( $wp_filter[ $hook_name ] ) || ! isset( $wp_filter[ $hook_name ]->callbacks ) || ! is_array( $wp_filter[ $hook_name ]->callbacks ) ) { return 0; } $count = 0; foreach ( $wp_filter[ $hook_name ]->callbacks as $callbacks ) { if ( is_array( $callbacks ) ) { $count += count( $callbacks ); } } return $count; } private function is_store_checkout_request( WP_REST_Request $request ): bool { if ( 'POST' !== strtoupper( $request->get_method() ) ) { return false; } return in_array( $request->get_route(), [ '/wc/store/v1/checkout', '/wc/store/v1/checkout-order', '/wc/store/v1/cart/update-customer', '/wc/store/v1/cart/select-shipping-rate', ], true ); } private function get_trace_step( WP_REST_Request $request ): string { return in_array( $request->get_route(), [ '/wc/store/v1/cart/update-customer', '/wc/store/v1/cart/select-shipping-rate', ], true ) ? 'contact' : 'review'; } private function get_trace_id( WP_REST_Request $request ): string { $trace_id = (string) $request->get_header( 'X-IPS-Checkout-Trace' ); if ( '' === $trace_id ) { $trace_id = (string) $request->get_header( 'X-IPS-Checkout-Trace-Id' ); } if ( '' === $trace_id ) { $trace_id = isset( $_SERVER['HTTP_X_IPS_CHECKOUT_TRACE'] ) ? sanitize_key( wp_unslash( $_SERVER['HTTP_X_IPS_CHECKOUT_TRACE'] ) ) : ''; } return sanitize_key( $trace_id ); } }