), )); $response = curl_exec($curl); curl_close($curl); // Decodificamos la respuesta JSON $data = json_decode($response, true); // Accedemos al valor de 'access_token de donatello' $access_token = $data['access_token']; // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $request_url); // //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // curl_setopt($ch, CURLOPT_POST, true); // curl_setopt($ch, CURLOPT_POSTFIELDS, $post); // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // #JFA: Cambiamos el método de Autenticación a OAuth // curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Zoho-oauthtoken ' . $access_token)); // $r = curl_exec($ch); // curl_close ($ch); try { $result = uploadPdfWithRetries($request_url, $post, $access_token); $j_array = array('code' => "200", "message" => "Proceso de creacion de CFDI fue exitoso", 'file_upload' => $result); $Resultado = json_encode($j_array); echo $Resultado; return; } catch (Exception $e) { error_log($e->getMessage()); $j_array = array('code' => "500", "message" => "Error no pudo añadirse el PDF", 'Error' => $e->getMessage()); $Resultado = json_encode($j_array); echo $Resultado; return; } #== Se elimina los archivos de trabajo //unlink($NomArchCFDI); //unlink($NomArchPDF1); $j_array = array('code' => "200", "message" => "Proceso de creacion de CFDI fue exitoso", 'file_upload' => $NomArchPDF); $Resultado = json_encode($j_array); echo $Resultado; return; ### 14. FUNCIONES DEL MÓDULO ######################################################### # 14.1 Función que integra los nodos al archivo .XML y forma la "Cadena original". function cargaAtt(&$nodo, $attr){ global $xml, $cadena_original; $quitar = array('sello'=>1,'noCertificado'=>1,'certificado'=>1); foreach ($attr as $key => $val){ $val = preg_replace('/\s\s+/', ' ', $val); $val = trim($val); if (strlen($val)>0){ $val = utf8_encode(str_replace("|","/",$val)); $nodo->setAttribute($key,$val); if (!isset($quitar[$key])) if (substr($key,0,3) != "xml" && substr($key,0,4) != "xsi:") $cadena_original .= $val . "|"; } } } # 14.2 Funciónes que da formato al "Importe total" como lo requiere el SAT para ser integrado al código QR. function ProcesImpTot($ImpTot){ $ArrayImpTot = explode(".", $ImpTot); $NumEnt = $ArrayImpTot[0]; $NumDec = ProcesDecFac($ArrayImpTot[1]); return $NumEnt.".".$NumDec; } function ProcesDecFac($Num){ $FolDec = ""; if ($Num < 10){$FolDec = "00000".$Num;} if ($Num > 9 and $Num < 100){$FolDec = $Num."0000";} if ($Num > 99 and $Num < 1000){$FolDec = $Num."000";} if ($Num > 999 and $Num < 10000){$FolDec = $Num."00";} if ($Num > 9999 and $Num < 100000){$FolDec = $Num."0";} return $FolDec; } function getZohoCreatorRecord($appOwner, $appLinkName, $reportLinkName, $recordId, $access_token, $maxRetries = 3) { $base_url = 'https://www.zohoapis.com/creator/v2.1'; $request_url = "$base_url/data/$appOwner/$appLinkName/report/$reportLinkName/$recordId"; $headers = [ "Authorization: Zoho-oauthtoken $access_token", "Accept: application/json" ]; $retryCount = 0; $waitSeconds = 1; while ($retryCount < $maxRetries) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $request_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($response); if ($httpCode === 200 && isset($data->data)) { return $data->data; } $errorCode = $data->code ?? 'N/A'; $errorMsg = $data->message ?? 'Respuesta inválida'; if ($httpCode === 401 || $errorCode === 1030) { return json_encode([ 'code' => '401', 'message' => 'ERROR - Token de acceso expirado o inválido para Zoho Creator' ]); } if ($errorCode === 3190) { return json_encode([ 'code' => '404', 'message' => "ERROR - No se encontró el registro con ID $recordId en el reporte $reportLinkName" ]); } if ($httpCode === 429 || $errorCode === 2955) { sleep($waitSeconds); $waitSeconds *= 2; $retryCount++; continue; } if (!isset($data->data)) { sleep($waitSeconds); $retryCount++; continue; } return json_encode([ 'code' => '500', 'message' => "ERROR - Fallo inesperado en Zoho Creator. HTTP $httpCode - Código $errorCode - $errorMsg" ]); } return json_encode([ 'code' => '408', 'message' => "ERROR - Tiempo de espera agotado tras $maxRetries intentos para obtener el registro $recordId" ]); } /** * Sube un PDF a Zoho Creator con reintentos y back-off exponencial. * * @param string $request_url URL de la API de Creator para el upload. * @param array $post Array con 'filename' y 'file'. * @param string $access_token Token OAuth válido. * @param int $maxRetries Número máximo de intentos (por defecto 5). * @param int $baseDelay Retardo base en segundos (por defecto 1). * * @return string Respuesta cruda del servidor en caso de éxito. * @throws Exception Si se agotan los reintentos o hay errores de cURL/HTTP. */ function uploadPdfWithRetries($request_url, array $post, $access_token, $maxRetries = 5, $baseDelay = 1) { $errors = []; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $request_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => [ 'Authorization: Zoho-oauthtoken ' . $access_token ], // CURLOPT_SSL_VERIFYPEER => false, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $errNo = curl_errno($ch); $errMsg = curl_error($ch); curl_close($ch); // Intento: siempre parseamos la respuesta para ver si hay error de API $apiError = false; $body = $response; // 1) Si hubo fallo de cURL o HTTP fuera de 2xx, es error if ($response === false || $httpCode < 200 || $httpCode >= 300) { $apiError = true; $errors[] = sprintf( 'Intento %d/%d: HTTP %d, cURL[%d]: %s', $attempt, $maxRetries, $httpCode, $errNo, $errMsg ); } else { // 2) HTTP OK, pero comprobamos Zoho-level $json = json_decode($body, true); if (json_last_error() === JSON_ERROR_NONE && isset($json['code']) && $json['code'] != 3000) { $apiError = true; $errors[] = sprintf( 'Intento %d/%d: Zoho API error code %d: %s', $attempt, $maxRetries, $json['code'], $json['message'] ?? '(sin mensaje)' ); } } // 3) Si no hubo **ningún** error, devolvemos (respuesta + todos los errores previos) if (!$apiError) { return [ 'response' => $body, 'errors' => $errors ]; } // 4) Si hubo error, esperamos back-off y reintentamos $delay = $baseDelay * (2 ** ($attempt - 1)); sleep($delay); } // Si llegamos aquí, todos los intentos fallaron $errorText = implode("\n", $errors); throw new Exception( "No se pudo subir el PDF tras {$maxRetries} intentos. Errores:\n{$errorText}" ); }