PluginProbe
SuperFrete / trunk
SuperFrete vtrunk
trunk 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4
superfrete / api / Http / Request.php

Request.php in SuperFrete trunk, at api/Http/Request.php

497 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SuperFrete_API\Http;
4
5 use SuperFrete_API\Helpers\Logger;
6
7 class Request {
8
9 private $api_url;
10 private $api_token;
11
12 /**
13 * Construtor para inicializar as configurações da API.
14 */
15 public function __construct() {
16 // Set API URL based on environment
17 $use_dev_env = get_option('superfrete_sandbox_mode') === 'yes';
18
19 if ($use_dev_env) {
20 $this->api_url = 'https://sandbox.superfrete.com/';
21 $this->api_token = get_option('superfrete_api_token_sandbox');
22 } else {
23 $this->api_url = 'https://api.superfrete.com/';
24 $this->api_token = get_option('superfrete_api_token');
25 }
26 }
27
28 /**
29 * Método genérico para chamadas à API do SuperFrete.
30 */
31 public function call_superfrete_api($endpoint, $method = 'GET', $payload = [], $retorno = false) {
32
33 // Enhanced debug logging
34 $environment = (strpos($this->api_url, 'sandbox') !== false || strpos($this->api_url, 'dev') !== false) ? 'SANDBOX/DEV' : 'PRODUCTION';
35 $full_url = $this->api_url . $endpoint;
36 $token_preview = !empty($this->api_token) ? substr($this->api_token, 0, 8) . '...' . substr($this->api_token, -4) : 'EMPTY';
37
38 Logger::log('SuperFrete', "API CALL [{$environment}]: {$method} {$full_url}");
39 Logger::log('SuperFrete', "TOKEN USADO [{$environment}]: {$token_preview}");
40
41 if (empty($this->api_token)) {
42 Logger::log('SuperFrete', 'API token is empty - cannot make API call');
43 return false;
44 }
45
46 // Check if proxy is configured and force proxy mode is enabled
47 $proxy_url = get_option('superfrete_proxy_url');
48
49 // Set default proxy URL if not configured
50 if (empty($proxy_url)) {
51 $proxy_url = 'https://api.dev.superintegrador.superfrete.com/headless/proxy/superfrete';
52 Logger::log('SuperFrete', "Using default proxy URL: {$proxy_url}");
53 }
54
55 $force_proxy = get_option('superfrete_force_proxy', 'no') === 'yes';
56
57 if (!empty($proxy_url) && $force_proxy) {
58 Logger::log('SuperFrete', "Force proxy mode enabled - using proxy directly");
59 return $this->call_via_proxy($proxy_url, $endpoint, $method, $payload);
60 }
61
62 try {
63 $headers = [
64 'Content-Type' => 'application/json',
65 'Accept' => 'application/json',
66 'Authorization' => 'Bearer ' . $this->api_token,
67 'User-Agent' => 'WooCommerce SuperFrete Plugin (github.com/superfrete/woocommerce)',
68 'Platform' => 'Woocommerce SuperFrete',
69 ];
70
71 $params = [
72 'headers' => $headers,
73 'method' => $method,
74 'timeout' => 30, // Increased timeout to 30 seconds
75 'sslverify' => false, // Skip SSL verification for faster connection
76 'redirection' => 5,
77 'httpversion' => '1.1',
78 ];
79
80 // LiteSpeed server detected - add additional timeout parameters
81 if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false) {
82 // LiteSpeed often ignores standard timeout - try cURL-specific options
83 $params['timeout'] = 35; // Slightly higher for LiteSpeed
84 $params['user-agent'] = 'WordPress/' . get_bloginfo('version') . '; SuperFrete Plugin';
85
86 // Add stream context options for alternative transport
87 $params['stream_context'] = stream_context_create([
88 'http' => [
89 'timeout' => 35,
90 'user_agent' => 'WordPress/' . get_bloginfo('version') . '; SuperFrete Plugin',
91 ]
92 ]);
93 }
94
95 if ($method === 'POST' && !empty($payload)) {
96 $params['body'] = wp_json_encode($payload);
97 Logger::debug('API Payload: ' . wp_json_encode($payload), 'Request');
98 }
99
100 // Force timeout to prevent hosting overrides
101 $timeout_filter = function() { return 30; };
102 $args_filter = function($args) {
103 if (isset($args['headers']['Authorization']) && strpos($args['headers']['Authorization'], 'Bearer') === 0) {
104 $args['timeout'] = 30;
105 }
106 return $args;
107 };
108
109 add_filter('http_request_timeout', $timeout_filter);
110 add_filter('http_request_args', $args_filter);
111
112 $max_attempts = 3;
113 $attempt = 1;
114 $response = null;
115
116 while ($attempt <= $max_attempts) {
117 $start_time = microtime(true);
118 $response = ($method === 'POST') ? wp_remote_post($this->api_url . $endpoint, $params) : wp_remote_get($this->api_url . $endpoint, $params);
119 $end_time = microtime(true);
120
121 // If successful or not a timeout, break
122 if (!is_wp_error($response) || strpos($response->get_error_message(), 'timeout') === false) {
123 break;
124 }
125
126 // Log retry attempt
127 $error_msg = $response->get_error_message();
128 Logger::log('SuperFrete', "Attempt {$attempt}/{$max_attempts} failed: {$error_msg}");
129
130 $attempt++;
131 if ($attempt <= $max_attempts) {
132 // Wait before retry (exponential backoff)
133 $wait_seconds = pow(2, $attempt - 1);
134 Logger::log('SuperFrete', "Retrying in {$wait_seconds} seconds...");
135 sleep($wait_seconds);
136 }
137 }
138
139 // Remove filters to not affect other plugins
140 remove_filter('http_request_timeout', $timeout_filter);
141 remove_filter('http_request_args', $args_filter);
142 $request_time = round(($end_time - $start_time) * 1000, 2);
143
144 Logger::debug('API Request Time: ' . $request_time . ' ms', 'Request');
145
146 // Check for WP errors first (timeout, connection issues, etc.)
147 if (is_wp_error($response)) {
148 $error_code = $response->get_error_code();
149 $error_message = $response->get_error_message();
150
151 // Collect diagnostic information
152 $diagnostics = [
153 'error_code' => $error_code,
154 'error_message' => $error_message,
155 'endpoint' => $endpoint,
156 'method' => $method,
157 'request_time_ms' => round(($end_time - $start_time) * 1000, 2),
158 'configured_timeout' => $params['timeout'] ?? 'unknown',
159 'api_url' => $this->api_url,
160 'environment' => $environment,
161 'wp_version' => get_bloginfo('version'),
162 'php_version' => PHP_VERSION,
163 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
164 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
165 'server_ip' => $_SERVER['SERVER_ADDR'] ?? 'unknown',
166 'client_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
167 'host' => $_SERVER['HTTP_HOST'] ?? 'unknown',
168 ];
169
170 // Check if it's a timeout specifically
171 if (strpos($error_message, 'timeout') !== false || strpos($error_message, 'timed out') !== false) {
172 $diagnostics['timeout_type'] = 'connection_timeout';
173
174 // Check for hosting-specific indicators
175 if (strpos($_SERVER['SERVER_SOFTWARE'] ?? '', 'nginx') !== false) {
176 $diagnostics['server_type'] = 'nginx';
177 } elseif (strpos($_SERVER['SERVER_SOFTWARE'] ?? '', 'Apache') !== false) {
178 $diagnostics['server_type'] = 'apache';
179 }
180
181 // Check for common hosting providers
182 $host_indicators = [
183 'hostgator' => strpos($_SERVER['HTTP_HOST'] ?? '', 'hostgator') !== false,
184 'godaddy' => strpos($_SERVER['HTTP_HOST'] ?? '', 'secureserver') !== false,
185 'bluehost' => strpos($_SERVER['HTTP_HOST'] ?? '', 'bluehost') !== false,
186 'siteground' => strpos($_SERVER['HTTP_HOST'] ?? '', 'siteground') !== false,
187 'wpengine' => strpos($_SERVER['HTTP_HOST'] ?? '', 'wpengine') !== false,
188 ];
189 $diagnostics['hosting_indicators'] = array_filter($host_indicators);
190
191 // Check PHP configurations that might affect timeouts
192 $diagnostics['php_config'] = [
193 'max_execution_time' => ini_get('max_execution_time'),
194 'default_socket_timeout' => ini_get('default_socket_timeout'),
195 'curl_available' => function_exists('curl_init'),
196 'openssl_version' => OPENSSL_VERSION_TEXT ?? 'unknown',
197 ];
198
199 // Test basic connectivity
200 $diagnostics['connectivity_test'] = [
201 'can_resolve_dns' => gethostbyname('api.superfrete.com') !== 'api.superfrete.com',
202 'superfrete_ip' => gethostbyname('api.superfrete.com'),
203 ];
204 }
205
206 $diagnostic_json = wp_json_encode($diagnostics, JSON_PRETTY_PRINT);
207
208 Logger::log("TIMEOUT DIAGNOSTICS:\n" . $diagnostic_json, 'ERROR');
209 Logger::debug('TIMEOUT DIAGNOSTICS: ' . $diagnostic_json, 'Request');
210
211 // Also log the original error message for backwards compatibility
212 Logger::log('SuperFrete', "WP Error na API ({$endpoint}): " . $error_message);
213
214 // Check if this is a timeout and we have a proxy available for fallback
215 if ((strpos($error_message, 'timeout') !== false || strpos($error_message, 'timed out') !== false) && !empty($proxy_url)) {
216 Logger::log('SuperFrete', "TIMEOUT DETECTED - Attempting automatic proxy fallback");
217
218 $proxy_result = $this->call_via_proxy($proxy_url, $endpoint, $method, $payload);
219 if ($proxy_result !== false) {
220 Logger::log('SuperFrete', "PROXY FALLBACK SUCCESSFUL - Enabling proxy for future requests");
221
222 // Enable force proxy mode to avoid future timeouts
223 update_option('superfrete_force_proxy', 'yes');
224
225 return $proxy_result;
226 } else {
227 Logger::log('SuperFrete', "PROXY FALLBACK ALSO FAILED");
228 }
229 }
230
231 return false;
232 }
233
234 $status_code = wp_remote_retrieve_response_code($response);
235 $raw_body = wp_remote_retrieve_body($response);
236
237 // Debug logging
238 Logger::debug('API Response: Status = ' . $status_code, 'Request');
239 Logger::debug('API Response: Body = ' . substr($raw_body, 0, 500) . (strlen($raw_body) > 500 ? '...' : ''), 'Request');
240
241 // Check for HTTP errors
242 if (!in_array($status_code, [200, 201, 204])) {
243 $error_msg = "ERRO NA API ({$endpoint}): CÓDIGO {$status_code}";
244
245 // Special handling for 401 errors
246 if ($status_code == 401) {
247 $error_msg .= " - NÃO AUTENTICADO!";
248 Logger::log('SuperFrete', $error_msg);
249 Logger::log('SuperFrete', "DETALHES [{$environment}]: URL={$full_url}, TOKEN={$token_preview}");
250 Logger::log('SuperFrete', "RESPOSTA: " . (strlen($raw_body) > 200 ? substr($raw_body, 0, 200) . '...' : $raw_body));
251 } else {
252 Logger::log('SuperFrete', $error_msg . "\nDETALHES: " . (strlen($raw_body) > 200 ? substr($raw_body, 0, 200) . '...' : ($raw_body ?: 'SEM DETALHES')));
253 }
254
255 return false;
256 }
257
258 // Handle empty responses (common for DELETE operations)
259 if (empty($raw_body) && $status_code == 204) {
260 Logger::log('SuperFrete', "API call successful ({$endpoint}) - No content returned (HTTP {$status_code})");
261 return true; // Success for DELETE operations
262 }
263
264 $body = json_decode($raw_body, true);
265
266 // Check for JSON decode errors only if there's content to decode
267 if (!empty($raw_body) && json_last_error() !== JSON_ERROR_NONE) {
268 Logger::log("JSON decode error na API ({$endpoint}): " . json_last_error_msg() . " - Raw response: " . substr($raw_body, 0, 200), 'ERROR');
269 return false;
270 }
271
272 // Check for API-level errors
273 if (isset($body['success']) && $body['success'] === false) {
274 $error_message = isset($body['message']) ? $body['message'] : 'Erro desconhecido';
275 $errors = $this->extract_api_errors($body);
276 Logger::log("API Error ({$endpoint}): {$error_message}\nDetalhes: {$errors}", 'ERROR');
277 return false;
278 }
279
280 Logger::log('SuperFrete', "API call successful ({$endpoint}) - Time: {$request_time}ms");
281 return $body;
282
283 } catch (Exception $exc) {
284 Logger::log("Exception na API ({$endpoint}): " . $exc->getMessage(), 'ERROR');
285 return false;
286 }
287 }
288
289 /**
290 * Extract error details from API response
291 */
292 private function extract_api_errors($body) {
293 $errors = [];
294
295 if (isset($body['errors'])) {
296 foreach ($body['errors'] as $field => $field_errors) {
297 if (is_array($field_errors)) {
298 $errors[] = $field . ': ' . implode(', ', $field_errors);
299 } else {
300 $errors[] = $field . ': ' . $field_errors;
301 }
302 }
303 } elseif (isset($body['error'])) {
304 if (is_array($body['error'])) {
305 foreach ($body['error'] as $error) {
306 if (is_array($error)) {
307 $errors[] = implode(', ', $error);
308 } else {
309 $errors[] = $error;
310 }
311 }
312 } else {
313 $errors[] = $body['error'];
314 }
315 }
316
317 return empty($errors) ? 'Sem detalhes' : implode('; ', $errors);
318 }
319
320 /**
321 * Register webhook with SuperFrete API
322 */
323 public function register_webhook($webhook_url, $events = ['order.posted', 'order.delivered'])
324 {
325 Logger::log('SuperFrete', 'Iniciando registro de webhook...');
326 Logger::log('SuperFrete', "Token sendo usado: " . (empty($this->api_token) ? 'VAZIO' : 'Presente'));
327 Logger::log('SuperFrete', "URL da API: " . $this->api_url);
328
329 // First, check for existing webhooks and clean them up
330 Logger::log('SuperFrete', 'Verificando webhooks existentes...');
331 $existing_webhooks = $this->list_webhooks();
332
333 if ($existing_webhooks && is_array($existing_webhooks)) {
334 Logger::log('SuperFrete', 'Encontrados ' . count($existing_webhooks) . ' webhooks existentes');
335
336 foreach ($existing_webhooks as $webhook) {
337 if (isset($webhook['id'])) {
338 Logger::log('SuperFrete', 'Removendo webhook existente ID: ' . $webhook['id']);
339 $this->delete_webhook($webhook['id'], false); // Don't clear options during cleanup
340 }
341 }
342 } else {
343 Logger::log('SuperFrete', 'Nenhum webhook existente encontrado');
344 }
345
346 // Now register the new webhook
347 $payload = [
348 'name' => 'WooCommerce SuperFrete Plugin Webhook',
349 'url' => $webhook_url,
350 'events' => $events
351 ];
352
353 Logger::log('SuperFrete', 'Registrando novo webhook: ' . wp_json_encode($payload));
354
355 $response = $this->call_superfrete_api('/api/v0/webhook', 'POST', $payload, true);
356
357 if ($response && isset($response['secret_token'])) {
358 // Store webhook secret for signature verification
359 update_option('superfrete_webhook_secret', $response['secret_token']);
360 update_option('superfrete_webhook_registered', 'yes');
361 update_option('superfrete_webhook_url', $webhook_url);
362 update_option('superfrete_webhook_id', $response['id'] ?? '');
363
364 Logger::log('SuperFrete', 'Webhook registrado com sucesso. ID: ' . ($response['id'] ?? 'N/A'));
365 return $response;
366 }
367
368 Logger::log('SuperFrete', 'Falha ao registrar webhook: ' . wp_json_encode($response));
369 update_option('superfrete_webhook_registered', 'no');
370 return false;
371 }
372
373 /**
374 * Update existing webhook
375 */
376 public function update_webhook($webhook_id, $webhook_url, $events = ['order.posted', 'order.delivered'])
377 {
378 $payload = [
379 'name' => 'WooCommerce SuperFrete Plugin Webhook',
380 'url' => $webhook_url,
381 'events' => $events
382 ];
383
384 Logger::log('SuperFrete', 'Atualizando webhook ID: ' . $webhook_id);
385
386 $response = $this->call_superfrete_api('/api/v0/webhook/' . $webhook_id, 'PUT', $payload, true);
387
388 if ($response) {
389 update_option('superfrete_webhook_url', $webhook_url);
390 Logger::log('SuperFrete', 'Webhook atualizado com sucesso');
391 return $response;
392 }
393
394 Logger::log('SuperFrete', 'Falha ao atualizar webhook: ' . wp_json_encode($response));
395 return false;
396 }
397
398 /**
399 * Delete webhook from SuperFrete
400 */
401 public function delete_webhook($webhook_id, $clear_options = true)
402 {
403 Logger::log('SuperFrete', 'Removendo webhook ID: ' . $webhook_id);
404
405 $response = $this->call_superfrete_api('/api/v0/webhook/' . $webhook_id, 'DELETE', [], true);
406
407 if ($response !== false) {
408 if ($clear_options) {
409 update_option('superfrete_webhook_registered', 'no');
410 update_option('superfrete_webhook_url', '');
411 update_option('superfrete_webhook_id', '');
412 }
413 Logger::log('SuperFrete', 'Webhook removido com sucesso');
414 return true;
415 }
416
417 Logger::log('SuperFrete', 'Falha ao remover webhook: ' . wp_json_encode($response));
418 return false;
419 }
420
421 /**
422 * List registered webhooks
423 */
424 public function list_webhooks()
425 {
426 Logger::log('SuperFrete', 'Listando webhooks registrados');
427
428 $response = $this->call_superfrete_api('/api/v0/webhook', 'GET', [], true);
429
430 if ($response) {
431 Logger::log('SuperFrete', 'Webhooks listados: ' . wp_json_encode($response));
432 return $response;
433 }
434
435 Logger::log('SuperFrete', 'Falha ao listar webhooks');
436 return false;
437 }
438
439 /**
440 * Make API call via proxy to work around hosting timeouts
441 */
442 private function call_via_proxy($proxy_url, $endpoint, $method, $payload = []) {
443 Logger::log('SuperFrete', "Using proxy for API call: {$proxy_url}");
444
445 $proxy_payload = [
446 'endpoint' => $endpoint,
447 'method' => $method,
448 'headers' => [
449 'Authorization' => 'Bearer ' . $this->api_token,
450 ]
451 ];
452
453 if (!empty($payload)) {
454 $proxy_payload['body'] = $payload;
455 }
456
457 $params = [
458 'headers' => [
459 'Content-Type' => 'application/json',
460 'Accept' => 'application/json',
461 ],
462 'method' => 'POST',
463 'body' => wp_json_encode($proxy_payload),
464 'timeout' => 65, // Slightly higher than proxy timeout
465 ];
466
467 $start_time = microtime(true);
468 $response = wp_remote_post($proxy_url, $params);
469 $end_time = microtime(true);
470 $request_time = round(($end_time - $start_time) * 1000, 2);
471
472 Logger::log('SuperFrete', "Proxy request time: {$request_time} ms");
473
474 if (is_wp_error($response)) {
475 Logger::log('SuperFrete', 'Proxy request failed: ' . $response->get_error_message());
476 return false;
477 }
478
479 $status_code = wp_remote_retrieve_response_code($response);
480 $body = wp_remote_retrieve_body($response);
481 $data = json_decode($body, true);
482
483 if ($status_code !== 200) {
484 Logger::log('SuperFrete', "Proxy returned error: {$status_code} - {$body}");
485 return false;
486 }
487
488 if (!$data || !$data['success']) {
489 Logger::log('SuperFrete', 'Proxy request unsuccessful: ' . wp_json_encode($data));
490 return false;
491 }
492
493 Logger::log('SuperFrete', "Proxy call successful - Duration: {$data['duration']}");
494 return $data['data'];
495 }
496 }
497