log_repository = new MessageLogRepository(); $this->settings_repository = new SettingsRepository(); } /** * Process SMS fallback candidates. * * @return int Number of fallback SMS messages sent. */ public function process(): int { $candidates = $this->log_repository->get_viber_fallback_candidates( 50 ); if ( empty( $candidates ) ) { PluginLogger::log( 'info', 'fallback_processing_skipped', __( 'No Viber SMS fallback candidates found.', 'inteltele' ), array(), 'ViberFallbackProcessor' ); return 0; } $settings = $this->settings_repository->get_all(); $client = ClientFactory::create( $settings ); $sent = 0; foreach ( $candidates as $candidate ) { if ( StatusMapper::is_viber_success( (string) $candidate->api_status ) ) { continue; } $fallback_message = isset( $candidate->fallback_message ) && '' !== trim( (string) $candidate->fallback_message ) ? (string) $candidate->fallback_message : (string) $candidate->message; $result = $client->send_sms( $fallback_message, array( (string) $candidate->recipient_phone ) ); $data = $result->get_data(); $item = $this->first_item( $data ); $error_code = $result->is_success() ? '' : $this->derive_error_code( $result ); $this->log_repository->insert( array( 'campaign_id' => empty( $candidate->campaign_id ) ? null : (int) $candidate->campaign_id, 'order_id' => empty( $candidate->order_id ) ? null : (int) $candidate->order_id, 'send_type' => (string) $candidate->send_type, 'recipient_phone' => (string) $candidate->recipient_phone, 'recipient_name' => (string) $candidate->recipient_name, 'recipient_role' => (string) $candidate->recipient_role, 'channel' => 'sms', 'scheme' => 'viber_sms', 'message' => $fallback_message, 'api_message_id' => isset( $item['message_id'] ) ? (string) $item['message_id'] : '', 'api_status' => $result->is_success() ? 'OK' : '', 'api_status_code' => $result->get_status_code(), 'error_code' => $error_code, 'api_response' => $this->encode_api_response( $result ), 'status' => $result->is_success() ? 'sent' : 'failed', 'error_reason' => $result->is_success() ? null : $result->get_error(), 'status_updated_at' => current_time( 'mysql' ), ) ); $this->log_repository->mark_sms_fallback_sent( (int) $candidate->id ); if ( $result->is_success() ) { $sent++; } PluginLogger::log( $result->is_success() ? 'info' : 'error', $result->is_success() ? 'fallback_sms_sent' : 'fallback_sms_failed', $result->is_success() ? __( 'SMS fallback accepted by API.', 'inteltele' ) : $result->get_error(), array( 'original_log_id' => (int) $candidate->id, 'phone' => (string) $candidate->recipient_phone, 'api_message_id' => isset( $item['message_id'] ) ? (string) $item['message_id'] : '', 'http_status' => $result->get_status_code(), 'error_code' => $error_code, ), 'ViberFallbackProcessor' ); } return $sent; } /** * Get first response item. * * @param array $data API data. * @return array */ private function first_item( array $data ): array { $items = isset( $data['items'] ) && is_array( $data['items'] ) ? $data['items'] : array(); $item = reset( $items ); return is_array( $item ) ? $item : array(); } /** * Encode API response for diagnostics. * * @param \IntelTele\Api\ApiResult $result API result. */ private function encode_api_response( \IntelTele\Api\ApiResult $result ): string { $raw = $result->get_raw(); if ( is_wp_error( $raw ) ) { $raw = array( 'errors' => $raw->errors, 'data' => $raw->error_data, ); } return (string) wp_json_encode( array( 'http_status' => $result->get_status_code(), 'data' => $result->get_data(), 'error' => $result->get_error(), 'raw' => $raw, ), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); } /** * Derive a stable error code for failed API requests. * * @param \IntelTele\Api\ApiResult $result API result. */ private function derive_error_code( \IntelTele\Api\ApiResult $result ): string { $raw = $result->get_raw(); if ( is_wp_error( $raw ) ) { $codes = $raw->get_error_codes(); return isset( $codes[0] ) ? $this->normalize_error_code( (string) $codes[0] ) : 'wp_error'; } if ( $result->get_status_code() > 0 && ! $result->is_success() ) { return 'http_' . $result->get_status_code(); } return $this->normalize_error_code( $result->get_error() ); } /** * Normalize arbitrary API/WP error text to a searchable code. * * @param string $value Error text. */ private function normalize_error_code( string $value ): string { $value = preg_replace( '/^error:\s*/i', '', $value ); $value = strtolower( trim( (string) $value ) ); $value = preg_replace( '/[^a-z0-9_:-]+/', '_', $value ); $value = trim( (string) $value, '_' ); return '' === $value ? 'unknown_error' : substr( $value, 0, 191 ); } }