get_trace_id(); if ( '' === $trace_id ) { return; } $payment_data = is_array( $payment_context->payment_data ?? null ) ? $payment_context->payment_data : []; $order = is_object( $payment_context->order ?? null ) ? $payment_context->order : null; $this->active_trace_id = $trace_id; $this->active_gateway_id = (string) ( $payment_context->payment_method ?? '' ); $this->active_order_id = $order && method_exists( $order, 'get_id' ) ? (int) $order->get_id() : 0; $this->processing_started = true; $this->processing_completed = false; $this->persist_order_trace_context( $order, $payment_data ); CheckoutTraceService::record( [ 'traceId' => $trace_id, 'event' => 'payment_processing_start', 'step' => 'review', 'status' => 'start', 'origin' => 'backend', 'gatewayId' => $this->active_gateway_id, 'context' => [ 'order_id' => $this->active_order_id, 'order_status_before' => $order && method_exists( $order, 'get_status' ) ? (string) $order->get_status() : '', 'payment_result_status_initial' => (string) ( $payment_result->status ?? '' ), 'payment_data_keys' => array_slice( array_map( 'sanitize_key', array_keys( $payment_data ) ), 0, 20 ), 'payment_data_count' => count( $payment_data ), 'has_card_token' => isset( $payment_data['cardToken'] ) || isset( $payment_data['cardtoken'] ), 'has_payment_method' => isset( $payment_data['payment_method'] ), 'has_selected_upe_payment_type' => isset( $payment_data['selected_upe_payment_type'] ) || isset( $payment_data['wc_stripe_selected_upe_payment_type'] ), 'has_deferred_intent' => isset( $payment_data['wc-stripe-is-deferred-intent'] ), 'has_payment_method_title' => isset( $payment_data['payment_method_title'] ), 'has_components_ready' => isset( $payment_data['componentsReady'] ) || isset( $payment_data['components_ready'] ), 'has_token_created' => isset( $payment_data['tokenCreated'] ) || isset( $payment_data['token_created'] ), 'notice_error_count_before' => function_exists( 'wc_notice_count' ) ? (int) wc_notice_count( 'error' ) : 0, 'is_ssl' => function_exists( 'is_ssl' ) ? (bool) is_ssl() : false, 'environment_type' => function_exists( 'wp_get_environment_type' ) ? (string) wp_get_environment_type() : '', 'is_local_host' => $this->is_local_host(), 'site_host' => (string) wp_parse_url( home_url(), PHP_URL_HOST ), 'request_host' => isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '', ], ] ); } public function capture_processing_complete( PaymentContext $payment_context, PaymentResult $payment_result ): void { if ( ! $this->processing_started || '' === $this->active_trace_id ) { return; } $payment_details = is_array( $payment_result->payment_details ?? null ) ? $payment_result->payment_details : []; $error_notices = $this->get_error_notice_messages(); $order = is_object( $payment_context->order ?? null ) ? $payment_context->order : null; $this->processing_completed = true; CheckoutTraceService::record( [ 'traceId' => $this->active_trace_id, 'event' => 'payment_processing_complete', 'step' => 'review', 'status' => in_array( (string) ( $payment_result->status ?? '' ), [ 'error', 'failure' ], true ) ? 'error' : 'success', 'origin' => 'backend', 'gatewayId' => $this->active_gateway_id, 'message' => implode( ' | ', $error_notices ), 'context' => [ 'order_id' => $this->active_order_id, 'order_status_after' => $order && method_exists( $order, 'get_status' ) ? (string) $order->get_status() : '', 'payment_result_status' => (string) ( $payment_result->status ?? '' ), 'payment_result_detail_keys' => array_slice( array_map( 'sanitize_key', array_keys( $payment_details ) ), 0, 20 ), 'has_redirect_url' => ! empty( $payment_result->redirect_url ), 'has_verification_endpoint' => isset( $payment_details['verification_endpoint'] ) && '' !== (string) $payment_details['verification_endpoint'], 'has_payment_intent_secret' => isset( $payment_details['payment_intent_secret'] ) && '' !== (string) $payment_details['payment_intent_secret'], 'has_setup_intent_secret' => isset( $payment_details['setup_intent_secret'] ) && '' !== (string) $payment_details['setup_intent_secret'], 'notice_error_count_after' => function_exists( 'wc_notice_count' ) ? (int) wc_notice_count( 'error' ) : 0, 'error_notices' => $error_notices, ], ] ); } public function capture_processing_shutdown(): void { if ( ! $this->processing_started || $this->processing_completed || '' === $this->active_trace_id ) { return; } $last_error = error_get_last(); $error_notices = $this->get_error_notice_messages(); CheckoutTraceService::record( [ 'traceId' => $this->active_trace_id, 'event' => 'payment_processing_aborted', 'step' => 'review', 'status' => 'error', 'origin' => 'backend', 'gatewayId' => $this->active_gateway_id, 'message' => $this->build_shutdown_message( $last_error, $error_notices ), 'context' => [ 'order_id' => $this->active_order_id, 'notice_error_count_shutdown' => function_exists( 'wc_notice_count' ) ? (int) wc_notice_count( 'error' ) : 0, 'error_notices' => $error_notices, 'shutdown_error_type' => is_array( $last_error ) ? (int) ( $last_error['type'] ?? 0 ) : 0, 'shutdown_error_file' => is_array( $last_error ) ? sanitize_text_field( wp_basename( (string) ( $last_error['file'] ?? '' ) ) ) : '', 'shutdown_error_line' => is_array( $last_error ) ? (int) ( $last_error['line'] ?? 0 ) : 0, ], ] ); } /** * @param mixed $order * @param array $payment_data */ private function persist_order_trace_context( $order, array $payment_data ): void { if ( ! is_object( $order ) || ! method_exists( $order, 'update_meta_data' ) || ! method_exists( $order, 'save' ) ) { return; } $gateway_id = sanitize_key( $this->active_gateway_id ); if ( '' === $gateway_id ) { return; } $provider_key = $this->resolve_provider_key( $gateway_id, $payment_data ); $verified_gateway_ids = $this->resolve_verified_gateway_ids( $gateway_id, $payment_data ); $verified_integration_ids = $this->resolve_verified_integration_ids( $gateway_id, $payment_data ); $verified_wallet_ids = $this->resolve_verified_wallet_ids( $gateway_id, $payment_data ); $order->update_meta_data( self::ORDER_META_TRACE_ID, $this->active_trace_id ); $order->update_meta_data( self::ORDER_META_TRACE_GATEWAY_ID, $gateway_id ); if ( '' !== $provider_key ) { $order->update_meta_data( self::ORDER_META_TRACE_PROVIDER_KEY, $provider_key ); } if ( ! empty( $verified_gateway_ids ) ) { $order->update_meta_data( self::ORDER_META_VERIFIED_GATEWAY_IDS, array_values( $verified_gateway_ids ) ); } if ( ! empty( $verified_integration_ids ) ) { $order->update_meta_data( self::ORDER_META_VERIFIED_INTEGRATION_IDS, array_values( $verified_integration_ids ) ); } if ( ! empty( $verified_wallet_ids ) ) { $order->update_meta_data( self::ORDER_META_VERIFIED_WALLET_IDS, array_values( $verified_wallet_ids ) ); } $order->save(); } /** * @param array $payment_data * @return array */ private function resolve_verified_gateway_ids( string $gateway_id, array $payment_data ): array { $ids = []; $express_payment_type = $this->resolve_express_payment_type( $payment_data ); $is_woopayments_wallet = 'woocommerce_payments' === $gateway_id && in_array( $express_payment_type, [ 'apple_pay', 'google_pay' ], true ); $is_amazon_express = 'amazon_payments_advanced' === $gateway_id && ( $this->read_payment_data_flag( $payment_data, 'ips_amazon_pay_portal' ) || '' !== $this->read_payment_data_value( $payment_data, 'amazoncheckoutsessionid' ) ); if ( $is_woopayments_wallet ) { $ids[ 'woocommerce_payments_' . $express_payment_type ] = true; } elseif ( $is_amazon_express ) { $ids['amazon_payments_advanced_express'] = true; } elseif ( '' !== $gateway_id ) { $ids[ $gateway_id ] = true; } return array_keys( $ids ); } /** * @param array $payment_data * @return array */ private function resolve_verified_integration_ids( string $gateway_id, array $payment_data ): array { $ids = []; $express_payment_type = $this->resolve_express_payment_type( $payment_data ); if ( $this->is_stripe_express_wallet( $gateway_id, $payment_data ) ) { $ids['stripe_express_wallets'] = true; } elseif ( 'woocommerce_payments' === $gateway_id && in_array( $express_payment_type, [ 'apple_pay', 'google_pay', 'amazon_pay' ], true ) ) { $ids['woopayments_express_wallets'] = true; } elseif ( 'amazon_payments_advanced' === $gateway_id && ( $this->read_payment_data_flag( $payment_data, 'ips_amazon_pay_portal' ) || '' !== $this->read_payment_data_value( $payment_data, 'amazoncheckoutsessionid' ) ) ) { $ids['amazon_pay_express'] = true; } return array_keys( $ids ); } /** * @param array $payment_data * @return array */ private function resolve_verified_wallet_ids( string $gateway_id, array $payment_data ): array { $ids = []; $express_payment_type = $this->resolve_express_payment_type( $payment_data ); if ( $this->is_stripe_express_wallet( $gateway_id, $payment_data ) ) { $wallet_id = $this->resolve_stripe_express_wallet_id( $express_payment_type ); if ( '' !== $wallet_id ) { $ids[ $wallet_id ] = true; } } elseif ( 'woocommerce_payments' === $gateway_id && in_array( $express_payment_type, [ 'apple_pay', 'google_pay', 'amazon_pay' ], true ) ) { $ids[ 'woocommerce_payments_' . $express_payment_type ] = true; } elseif ( 'amazon_payments_advanced' === $gateway_id && ( $this->read_payment_data_flag( $payment_data, 'ips_amazon_pay_portal' ) || '' !== $this->read_payment_data_value( $payment_data, 'amazoncheckoutsessionid' ) ) ) { $ids['amazon_pay'] = true; } return array_keys( $ids ); } /** * @param array $payment_data */ private function resolve_provider_key( string $gateway_id, array $payment_data ): string { if ( in_array( $gateway_id, [ 'amazon_payments_advanced', 'amazon_payments_advanced_express' ], true ) || $this->read_payment_data_flag( $payment_data, 'ips_amazon_pay_portal' ) || '' !== $this->read_payment_data_value( $payment_data, 'amazoncheckoutsessionid' ) ) { return 'amazon-pay'; } if ( 'woocommerce_payments' === $gateway_id && '' !== $this->resolve_express_payment_type( $payment_data ) ) { return 'woopayments-wallet'; } if ( $this->is_stripe_express_wallet( $gateway_id, $payment_data ) ) { return 'stripe-express'; } return ''; } /** * @param array $payment_data */ private function is_stripe_express_wallet( string $gateway_id, array $payment_data ): bool { return 'stripe' === $gateway_id && in_array( $this->resolve_express_payment_type( $payment_data ), [ 'amazon_pay', 'apple_pay', 'google_pay', 'link' ], true ); } private function resolve_stripe_express_wallet_id( string $express_payment_type ): string { switch ( $express_payment_type ) { case 'amazon_pay': return 'stripe_amazon_pay'; case 'apple_pay': return 'stripe_apple_pay'; case 'google_pay': return 'stripe_google_pay'; case 'link': return 'stripe_link'; default: return ''; } } /** * @param array $payment_data */ private function resolve_express_payment_type( array $payment_data ): string { $express_payment_type = sanitize_key( $this->read_payment_data_value( $payment_data, 'express_payment_type' ) ); if ( '' !== $express_payment_type ) { return $express_payment_type; } $express_payment_type = sanitize_key( $this->read_payment_data_value( $payment_data, 'expresspaymenttype' ) ); return '' !== $express_payment_type ? $express_payment_type : sanitize_key( $this->read_payment_data_value( $payment_data, 'express_checkout_type' ) ); } /** * @param array $payment_data */ private function read_payment_data_flag( array $payment_data, string $key ): bool { return in_array( strtolower( $this->read_payment_data_value( $payment_data, $key ) ), [ '1', 'true', 'yes', 'on' ], true ); } /** * @param array $payment_data */ private function read_payment_data_value( array $payment_data, string $key ): string { $target_key = sanitize_key( $key ); foreach ( $payment_data as $data_key => $value ) { if ( is_string( $data_key ) && sanitize_key( $data_key ) === $target_key ) { return $this->stringify_payment_data_value( $value ); } if ( is_array( $value ) ) { $nested_key = sanitize_key( (string) ( $value['key'] ?? $value['name'] ?? '' ) ); if ( $nested_key === $target_key ) { return $this->stringify_payment_data_value( $value['value'] ?? '' ); } } } return ''; } /** * @param mixed $value */ private function stringify_payment_data_value( $value ): string { if ( is_bool( $value ) ) { return $value ? 'true' : 'false'; } if ( is_scalar( $value ) ) { return sanitize_text_field( (string) $value ); } return ''; } private function get_trace_id(): string { return isset( $_SERVER['HTTP_X_IPS_CHECKOUT_TRACE'] ) ? sanitize_key( sanitize_key( wp_unslash( $_SERVER['HTTP_X_IPS_CHECKOUT_TRACE'] ) ) ) : ''; } /** * @return array */ private function get_error_notice_messages(): array { if ( ! function_exists( 'wc_get_notices' ) ) { return []; } $error_notices = wc_get_notices( 'error' ); if ( ! is_array( $error_notices ) ) { return []; } $messages = []; foreach ( array_slice( $error_notices, 0, 4 ) as $error_notice ) { $message = ''; if ( is_array( $error_notice ) ) { $message = wp_strip_all_tags( (string) ( $error_notice['notice'] ?? '' ) ); } $message = sanitize_text_field( $message ); if ( '' !== $message ) { $messages[] = $message; } } return $messages; } /** * @param array|null $last_error * @param array $error_notices */ private function build_shutdown_message( ?array $last_error, array $error_notices ): string { if ( ! empty( $error_notices ) ) { return implode( ' | ', $error_notices ); } if ( is_array( $last_error ) && ! empty( $last_error['message'] ) ) { return sanitize_text_field( (string) $last_error['message'] ); } return 'Payment processing aborted before completion.'; } private function is_local_host(): bool { $host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) : ''; if ( '' === $host ) { return false; } return in_array( $host, [ 'localhost', '127.0.0.1', '::1' ], true ) || '.local' === substr( $host, -6 ) || false !== strpos( $host, 'localhost:' ); } }