<?php
namespace Aviatur\GeneralBundle\Services;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
use Aviatur\GeneralBundle\Entity\ProviderResponse;
use Aviatur\GeneralBundle\Services\Exception\WebServiceException;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Description of AviaturWebService.
*
* @author andres.ramirez
*/
class AviaturWebService
{
// Timeouts y configuraciones de performance
private const CURL_TIMEOUT = 120;
private const CURL_CONNECT_TIMEOUT = 60;
private const CURL_TIMEOUT_MPB = 60;
private const CURL_CONNECT_TIMEOUT_MPB = 10;
private const SESSION_EXPIRATION_TIME = 7200;
private const SESSION_VALIDATION_THRESHOLD = 900;
// Mensajes de error estandarizados
private const ERROR_CURL = 'cURL Error: %s';
private const ERROR_HTTP = 'HTTP Error: %d';
private const ERROR_EMPTY_RESPONSE = 'Respuesta vacia del servicio';
private const ERROR_XML_PARSE = 'Error: Failed to parse XML response.';
private const ERROR_SOAP_FAULT = 'Error en la respuesta del servicio: %s';
private const ERROR_NO_AVAILABILITY = 'No existe disponibilidad para esta solicitud, por favor intenta con diferentes fechas o destinos.(66002 )';
private $url;
private $urlMpa;
private $serviceNameMpa;
private $invoker;
private $requestId;
private $requestType;
private $projectDir;
private $loginKey;
private \Symfony\Component\HttpFoundation\Session\SessionInterface $session;
private \Symfony\Component\HttpFoundation\RequestStack $requestStack;
private \Doctrine\Bundle\DoctrineBundle\Registry $doctrine;
private \Aviatur\GeneralBundle\Services\AviaturLogSave $aviaturLogSave;
private \Aviatur\GeneralBundle\Services\ExceptionLog $aviaturRegisterException;
private $transactionIdSessionName;
private $stopwatch;
// URLs específicas para servicios MPB
private $urlLoginMpb;
private $urlAirMpb;
private $urlHotelMpb;
private $urlCarMpb;
private $urlTicketMpb;
private $urlCruiseMpb;
private $urlEmission;
private $urlPackageMpt;
private $urlInsuranceMpb;
private $urlBusMpb;
private $urlTrainMpb;
private $urlExperience;
// Cache para métodos disponibles por servicio
private array $availableMethodsByService = [];
/**
* Establece la URL del bus.
*
* @param string $url
* @return void
*/
public function setUrl(string $url): void
{
$this->url = $url;
}
/**
* Establece la URL del MPA.
*
* @param string $urlMpa
* @return void
*/
public function setUrlMpa(string $urlMpa): void
{
$this->urlMpa = $urlMpa;
}
/**
* Establece el tipo de consulta si es MPA o BUS.
*
* @param string $requestType
* @return void
*/
public function setRequestType(string $requestType): void
{
$this->requestType = $requestType;
}
/**
* Obtiene el valor del requestType para consultar en clases que van a extender.
*
* @return string|null
*/
public function getRequestType(): ?string
{
return $this->requestType;
}
/**
* @param $projectDir
* @param $invoker
* @param SessionInterface $session
* @param RequestStack $requestStack
* @param Registry $doctrine
* @param AviaturLogSave $aviaturLogSave
* @param ExceptionLog $aviaturRegisterException
* @param Stopwatch $stopwatch
* @param $transactionIdSessionName
*/
public function __construct(RequestStack $requestStack, SessionInterface $session, Registry $doctrine, AviaturLogSave $aviaturLogSave, ExceptionLog $aviaturRegisterException, Stopwatch $stopwatch, $projectDir, $invoker, $transactionIdSessionName)
{
$this->projectDir = $projectDir;
$this->session = $session;
$this->requestStack = $requestStack;
$this->doctrine = $doctrine;
$this->aviaturLogSave = $aviaturLogSave;
$this->aviaturRegisterException = $aviaturRegisterException;
$this->transactionIdSessionName = $transactionIdSessionName;
$this->stopwatch = $stopwatch;
$request = $this->requestStack->getCurrentRequest();
if (null !== $request) {
$domain = $request->getHost();
$parametersJson = $this->session->get($domain . '[parameters]', '');
$parameters = json_decode($parametersJson);
if (!empty($parameters)) {
$this->url = $parameters->aviatur_service_web_url;
$this->urlMpa = $parameters->aviatur_service_web_url_mpa;
$this->urlLoginMpb = $parameters->aviatur_service_web_url_login_mpb;
$this->urlAirMpb = $parameters->aviatur_service_web_url_air_mpb;
$this->urlHotelMpb = $parameters->aviatur_service_web_url_hotel_mpb;
$this->urlCarMpb = $parameters->aviatur_service_web_url_car_mpb;
$this->urlTicketMpb = $parameters->aviatur_service_web_url_ticket_mpb;
$this->urlCruiseMpb = $parameters->aviatur_service_web_url_cruise_mpb;
$this->urlEmission = $parameters->aviatur_service_web_url_emission;
$this->invoker = $invoker;
$this->requestId = $parameters->aviatur_service_web_request_id;
$this->requestType = $parameters->aviatur_service_web_request_type;
$this->serviceNameMpa = $parameters->aviatur_service_web_mpa_method;
$this->urlPackageMpt = $parameters->aviatur_service_web_mpb_mpt;
$this->urlInsuranceMpb = $parameters->aviatur_service_web_url_insurance_mpb;
$this->urlBusMpb = $parameters->aviatur_service_web_url_bus_mpb;
$this->urlTrainMpb = $parameters->aviatur_service_web_url_train_mpb;
$this->urlExperience = $parameters->aviatur_service_web_mpb_experience;
}
}
$this->loginKey = base64_decode('Tj6qJt6p2QYECN4Z+4iqQMbLFuz8u5ff');
}
/**
* @param string $xml
*
* @return string
*
* @throws FatalErrorException
*/
public function callWebService($service, $provider, $xmlRequest)
{
$xmlResponseObject = $this->callServiceBus($this->projectDir, $service, $provider, $xmlRequest);
return $xmlResponseObject;
}
/**
* Realiza la consulta en los servicios de amadeus usando un entrypoint diferente dependiendo de la configuracion.
*
* @param string $service
* @param string $method
* @param string $provider
* @param string $xmlRequest
* @param array $variable
* @param bool $login
* @param string|null $transactionId
* @param bool $isTicket
*
* @return \SimpleXMLElement|array
*/
public function callWebServiceAmadeus($service, $method, $provider, $xmlRequest, array $variable, $login = false, $transactionId = null, $isTicket = true)
{
$this->stopwatch->start('Set request options');
// Inicializar cache de métodos disponibles si no existe
if (empty($this->availableMethodsByService)) {
$this->initializeAvailableMethods();
}
// Determinar si el método requiere MPA en lugar de MPB
if ($this->requestType === 'MPB' && !$this->isMethodAvailableForMpb($method)) {
$this->requestType = 'MPA';
}
$this->stopwatch->stop('Set request options');
$this->stopwatch->start('Checks for transaction');
$transactionId = $this->resolveTransactionId($service, $provider, $variable, $login, $transactionId);
if (isset($transactionId['error'])) {
return $transactionId;
}
$validationResult = $this->validateTransactionExpiration($transactionId);
if ($validationResult !== null) {
return $validationResult;
}
$this->stopwatch->stop('Checks for transaction');
$this->stopwatch->start('Defines request method and calls');
$xmlResponseObject = $this->executeServiceCall($service, $provider, $method, $xmlRequest, $variable, $transactionId, $isTicket);
$this->stopwatch->stop('Defines request method and calls');
return $xmlResponseObject;
}
/**
* Realiza la consulta en en servicio para mirar si el usuario existe en la base de Aviatur.
*
* @param string $service
* @param string $provider
* @param string $xmlRequest
*
* @return \simplexml_object
*/
public function busWebServiceAmadeus($service, $provider, $xmlRequest)
{
$xmlResponseObject = $this->callServiceBusUser($service, $provider, $xmlRequest);
return $xmlResponseObject;
}
/**
* Consulta el bus enviando los xml mediante curl.
*
* @param $projectDir
* @param string $service
* @param string $provider
* @param xml $xmlRequest
*
* @return \simplexml_object
*
* @throws WebServiceException
*/
private function callServiceBus($projectDir, $service, $provider, $xmlRequest)
{
$xmlResponseObject = null;
try {
if (null != $service) {
$path = $projectDir . '/app/xmlService/aviaturRequest.xml';
//Valores a remplazar
$arrayIndex = [
'{xmlBody}',
'{service}',
'{invoker}',
'{provider}',
'{requestId}',
];
//Nuevos valores
$arrayValues = [
$xmlRequest,
$service,
$this->invoker,
$provider,
$this->requestId,
];
//obtengo el xml base
$xmlBase = simplexml_load_file((string) $path)->asXML();
$xmlBase = str_replace($arrayIndex, $arrayValues, $xmlBase);
$xmlBase = str_replace('<?xml version="1.0"?>', '', $xmlBase);
$xmlBase = trim($xmlBase);
} else {
$xmlBase = $xmlRequest;
$service = 'DIRECT';
}
$client = new \SoapClient(null, [
'location' => $this->url,
'uri' => $this->url,
'trace' => 1,
]);
$this->aviaturLogSave->logSave($xmlBase, $service, 'RQ');
$response = $client->__doRequest($xmlBase, $this->url, $this->serviceNameMpa, 1);
$this->aviaturLogSave->logSave($response, $service, 'RS');
$response = str_replace('<?xml version="1.0"?>', '', $response);
$xmlResponseObject = \simplexml_load_string($response, \SimpleXMLElement::class, 0, 'NS1', true);
if (isset($xmlResponseObject->Body->mbus->response->fault)) {
$this->aviaturRegisterException->log(
$xmlBase,
$response
);
return ['error' => 'Error en la respuesta del servicio: ' . (string) $xmlResponseObject->Body->mbus->response->fault->faultDescription];
}
if (false === strpos($response, '<body>')) {
$this->aviaturRegisterException->log(
$xmlBase,
$response
);
return ['error' => 'Respuesta vacia del servicio'];
}
//Si no existe error Extraigo el body de la respuesta
$firstPartBody = explode('<body>', $response);
$secondPartBody = explode('</body>', $firstPartBody[1]);
$xmlResponseObject = str_replace('mpa:', '', utf8_encode($secondPartBody[0]));
$xmlResponseObject = \simplexml_load_string($xmlResponseObject, \SimpleXMLElement::class, LIBXML_NOCDATA);
} catch (\SoapFault $e) {
$this->aviaturRegisterException->log(
'SoapFault Exception',
json_encode($e)
);
throw new WebServiceException('No se pudo realizar la consulta en el servidor. ' . $this->url, '', 100);
}
return $xmlResponseObject;
}
/**
* A template for the SOAP request envelope. Stored as a constant to avoid file reads.
*/
private const SOAP_TEMPLATE = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.aviatur.com/soa/formato/mbus/request/version/1.0">
<soapenv:Header/>
<soapenv:Body>
<ns:mbus>
<ns:request>
<ns:header>
<ns:service>{service}</ns:service>
<ns:invoker>{invoker}</ns:invoker>
<ns:provider>{provider}</ns:provider>
<ns:requestId>{requestId}</ns:requestId>
</ns:header>
<ns:body>{xmlBody}</ns:body>
</ns:request>
</ns:mbus>
</soapenv:Body>
</soapenv:Envelope>
XML;
/**
* Inicializa el cache de métodos disponibles por servicio.
* Optimización: se ejecuta una sola vez y se mantiene en memoria.
*/
private function initializeAvailableMethods(): void
{
$this->availableMethodsByService = [
'air' => ['AirOverride', 'AirLowFareSearch', 'AirDetail', 'AirAvail', 'AirCommandExecute', 'AirAddDataPassenger', 'AirBook', 'AirCancel'],
'car' => ['VehOverride', 'VehAvailRate', 'VehDetail', 'VehRes'],
'hotel' => ['HotelAvail', 'HotelRoomList', 'HotelDetail', 'HotelRes'],
'ticket' => ['SvcDetail'],
'package' => ['PkgAvail', 'PkgDetail', 'PkgFares', 'PkgOptions', 'PkgPromo'],
'cruise' => ['CruiseAvail', 'CruiseDetail', 'CruiseCabin', 'CruiseReserve'],
'insurance' => ['InsuranceQuote', 'InsuranceBook'],
'bus' => ['BusAvail', 'BusDetail', 'BusBook'],
'train' => ['TrainAvail', 'TrainDetail', 'TrainBook'],
'experience' => ['SvcAvail', 'SvcDetail', 'SvcAvailComb', 'SvcFares', 'SvcAvailPOS', 'SvcAvailPROMO', 'SvcQuotas'],
];
}
/**
* Verifica si un método está disponible para MPB.
*/
private function isMethodAvailableForMpb(string $method): bool
{
foreach ($this->availableMethodsByService as $methods) {
if (in_array($method, $methods, true)) {
return true;
}
}
return false;
}
/**
* Resuelve el transaction ID, ya sea obteniendo uno nuevo o usando el existente.
*
* @return string|array Transaction ID o array con error
*/
private function resolveTransactionId(string $service, string $provider, array $variable, bool $login, ?string $transactionId)
{
if ($transactionId !== null) {
return $transactionId;
}
if ($login || !$this->session->has($this->transactionIdSessionName)) {
$transactionId = $this->loginService($service, $provider, $variable['ProviderId']);
if (isset($transactionId['error'])) {
return $transactionId;
}
$this->session->set($this->transactionIdSessionName, (string) $transactionId);
return $transactionId;
}
return $this->session->get($this->transactionIdSessionName);
}
/**
* Valida que el transaction ID no haya expirado.
*
* @return array|null Array con error si expiró, null si es válido
*/
private function validateTransactionExpiration(string $transactionId): ?array
{
try {
$tokenParts = explode('.', $this->encodeJWT('', $this->loginKey));
$fullToken = $tokenParts[0] . '.' . $transactionId;
$infoToken = $this->decodeJWT($fullToken, $this->loginKey, ['HS256']);
$timeValidation = explode('_', $infoToken->e);
$expirationTime = (int) $timeValidation[0];
$currentTime = time();
if ($expirationTime <= ($currentTime - self::SESSION_VALIDATION_THRESHOLD)) {
return ['error' => 'La sesión ha expirado por favor vuelve a realizar tu consulta. (66002 ) '];
}
} catch (\Exception $e) {
return ['error' => 'Error validando la sesión. Por favor intenta nuevamente.'];
}
return null;
}
/**
* Ejecuta la llamada al servicio según el tipo de request configurado.
*
* @return \SimpleXMLElement|array
*/
private function executeServiceCall(string $service, string $provider, string $method, string $xmlRequest, array $variable, string $transactionId, bool $isTicket)
{
switch ($this->requestType) {
case 'BUS':
$this->stopwatch->start('Calls using BUS');
$xmlRequest = $this->getXmlBusHeader($xmlRequest, $method, $transactionId, $variable);
$result = $this->callServiceBusUser($service, $provider, $xmlRequest, $transactionId);
$this->stopwatch->stop('Calls using BUS');
return $result;
case 'MPA':
$this->stopwatch->start('Calls using MPA');
$xmlRequest = $this->getXmlMpxHeader($xmlRequest, $method, $transactionId, $variable);
$result = $this->callServiceMpa($this->projectDir, $method, $xmlRequest, $transactionId);
$this->stopwatch->stop('Calls using MPA');
return $result;
case 'MPB':
$this->stopwatch->start('Calls using MPB');
$xmlRequest = $this->getXmlMpxHeader($xmlRequest, $method, $transactionId, $variable);
$result = $this->routeMpbRequest($method, $xmlRequest, $transactionId, $isTicket);
$this->stopwatch->stop('Calls using MPB');
return $result;
default:
return ['error' => 'Tipo de request no válido: ' . $this->requestType];
}
}
/**
* Enruta la petición MPB al servicio correspondiente según el método.
*
* @return \SimpleXMLElement|array
*/
private function routeMpbRequest(string $method, string $xmlRequest, string $transactionId, bool $isTicket)
{
$serviceMap = [
'air' => $this->urlAirMpb ?? null,
'car' => $this->urlCarMpb ?? null,
'hotel' => $this->urlHotelMpb ?? null,
'ticket' => $isTicket ? ($this->urlTicketMpb ?? null) : null,
'package' => $this->urlPackageMpt ?? null,
'cruise' => $this->urlCruiseMpb ?? null,
'insurance' => $this->urlInsuranceMpb ?? null,
'bus' => $this->urlBusMpb ?? null,
'train' => $this->urlTrainMpb ?? null,
'experience' => $this->urlExperience ?? null,
];
foreach ($serviceMap as $serviceType => $url) {
if ($url && in_array($method, $this->availableMethodsByService[$serviceType], true)) {
return $this->callServiceMpb($method, $xmlRequest, $url, $transactionId);
}
}
// Fallback a MPA si no se encuentra el servicio
return $this->callServiceMpa($this->projectDir, $method, $xmlRequest, $transactionId);
}
/**
* Performs a direct SOAP query to the MPB service.
*
* @param string $service
* @param string $provider
* @param string $xmlRequest
* @param string|null $transactionId
*
* @return \SimpleXMLElement|array Returns a SimpleXMLElement on success or an array with an 'error' key on failure.
*/
private function callServiceBusUser($service, $provider, $xmlRequest, $transactionId = null)
{
try {
// Section 1: Build the request XML in memory
if (null !== $service) {
$placeholders = [
'{service}',
'{invoker}',
'{provider}',
'{requestId}',
'{xmlBody}',
];
$values = [
$service,
$this->invoker,
$provider,
$this->requestId,
$xmlRequest,
];
$xmlBase = str_replace($placeholders, $values, self::SOAP_TEMPLATE);
} else {
$xmlBase = $xmlRequest;
$service = 'DIRECT';
}
// Section 2: Execute the cURL request
$headers = [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "' . $this->serviceNameMpa . '"'
];
$options = [
CURLOPT_URL => $this->url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $xmlBase,
CURLOPT_TIMEOUT => self::CURL_TIMEOUT,
CURLOPT_CONNECTTIMEOUT => self::CURL_CONNECT_TIMEOUT,
CURLOPT_SSL_VERIFYHOST => false, // SECURITY RISK: Use 2 in production
CURLOPT_SSL_VERIFYPEER => false, // SECURITY RISK: Use true in production
];
$ch = curl_init();
curl_setopt_array($ch, $options);
$this->aviaturLogSave->logSave($xmlBase, $service, 'RQ', $transactionId);
$responseBody = curl_exec($ch);
// Section 3: Handle transport-level errors
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$this->aviaturLogSave->logSave($responseBody ?: $curlError, $service, 'RS', $transactionId);
if ($curlError) {
return ['error' => sprintf(self::ERROR_CURL, $curlError)];
}
if ($httpCode >= 400) {
return ['error' => sprintf(self::ERROR_HTTP, $httpCode)];
}
if (empty($responseBody)) {
return ['error' => self::ERROR_EMPTY_RESPONSE];
}
// Section 4: Safely parse the XML response
$previousLibxmlState = libxml_use_internal_errors(true);
$responseXml = simplexml_load_string($responseBody);
if ($responseXml === false) {
libxml_use_internal_errors($previousLibxmlState);
$this->aviaturRegisterException->log($xmlBase, $responseBody);
return ['error' => self::ERROR_XML_PARSE];
}
$responseXml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
// Check for a SOAP Fault error
$fault = $responseXml->xpath('//soap:Body/soap:Fault');
if (!empty($fault)) {
libxml_use_internal_errors($previousLibxmlState);
$faultDescription = (string) ($fault[0]->faultstring ?? 'Unknown SOAP fault');
$this->aviaturRegisterException->log($xmlBase, $responseBody);
return ['error' => sprintf(self::ERROR_SOAP_FAULT, $faultDescription)];
}
// Extract the main content from inside the <body> tag
$bodyContent = $responseXml->xpath('//soap:Body/*[1]');
if (empty($bodyContent)) {
libxml_use_internal_errors($previousLibxmlState);
return ['error' => 'Respuesta vacia del servicio (cuerpo SOAP vacío).'];
}
// Usar regex en lugar de explode para mejor performance
$bodyXml = $bodyContent[0]->asXML();
if (preg_match('/<body>(.*?)<\/body>/s', $bodyXml, $matches)) {
$innerContent = $matches[1];
} else {
libxml_use_internal_errors($previousLibxmlState);
return ['error' => 'No se pudo extraer el contenido del body.'];
}
$xmlResponseObject = str_replace('mpa:', '', utf8_encode($innerContent));
if ($service === 'DIRECT') {
$xmlResponseObject = '<ROW>' . $xmlResponseObject . '</ROW>';
}
$xmlResponseObject = simplexml_load_string(trim($xmlResponseObject), 'SimpleXMLElement', LIBXML_NOCDATA);
libxml_use_internal_errors($previousLibxmlState);
if ($xmlResponseObject === false) {
$this->aviaturRegisterException->log($xmlBase, $responseBody);
return ['error' => 'Error al parsear el contenido del body.'];
}
// Validar resultados del proveedor
if (isset($xmlResponseObject->ProviderResults)) {
$validResponse = false;
foreach ($xmlResponseObject->ProviderResults->ProviderResult as $providerResult) {
if (isset($providerResult['Code']) && (string) $providerResult['Code'] === '0') {
$validResponse = true;
break;
}
}
if (!$validResponse) {
$message = (string) ($xmlResponseObject->ProviderResults->ProviderResult[0]['Message'] ?? 'Provider returned an unspecified error.');
$this->aviaturRegisterException->log($xmlBase, $responseBody);
return ['error' => $message];
}
}
} catch (\Exception $e) {
$this->aviaturRegisterException->log($xmlRequest, json_encode($e->getMessage()));
return ['error' => 'No se pudo realizar la consulta en el servidor: ' . $e->getMessage()];
}
return $xmlResponseObject;
}
/**
* Realiza la consulta directamente en el servicio del mpa.
*
* @param string $xmlRequest
*
* @return \simplexml_object
*
* @throws WebServiceException
*/
private function callServiceMpa($projectDir, $method, $xmlRequest, $transactionId = null)
{
$xmlResponseObject = null;
try {
$client = new \SoapClient($path = $projectDir . '/app/services.wsdl');
$this->aviaturLogSave->logSave($xmlRequest, $method, 'RQ', $transactionId);
$response = $client->__doRequest($xmlRequest, $this->urlMpa, $this->serviceNameMpa, 1);
$this->aviaturLogSave->logSave($response, $method, 'RS', $transactionId);
return $this->processMpxResponse($xmlRequest, $response, $method, $transactionId);
} catch (\SoapFault $e) {
$this->aviaturRegisterException->log(
$xmlRequest,
json_encode($e)
);
return ['error' => 'No se pudo realizar la consulta en el servidor.'];
}
}
/**
* Realiza la consulta directamente en el servicio del mpa.
*
* @param string $xmlRequest
* @return mixed The result of the processMpxResponse method.
*/
private function callServiceMpb($method, $xmlRequest, $url, $transactionId = null)
{
$this->stopwatch->start('Set headers and options');
$headers = [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "http://tempuri.org/Execute"',
'Expect:',
'Accept-Encoding: gzip, deflate'
];
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $xmlRequest,
CURLOPT_TIMEOUT => self::CURL_TIMEOUT_MPB,
CURLOPT_CONNECTTIMEOUT => self::CURL_CONNECT_TIMEOUT_MPB,
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
CURLOPT_TCP_KEEPALIVE => true,
CURLOPT_TCP_KEEPIDLE => 120,
CURLOPT_ENCODING => '',
];
$this->stopwatch->stop('Set headers and options');
$this->stopwatch->start('Triggers cURL request');
$ch = curl_init();
curl_setopt_array($ch, $options);
$this->aviaturLogSave->logSave($xmlRequest, $method, 'RQ', $transactionId);
$responseData = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$this->stopwatch->stop('Triggers cURL request');
$this->stopwatch->start('Validates cURL response');
// Manejo consolidado de errores
if ($curlError) {
$errorMessage = sprintf(self::ERROR_CURL, $curlError);
$this->aviaturLogSave->logSave($errorMessage . ' - ' . $url, 'Error' . $method, 'RS', $transactionId);
$responseData = '';
} elseif ($httpCode >= 400) {
$errorMessage = sprintf(self::ERROR_HTTP, $httpCode);
$this->aviaturLogSave->logSave($errorMessage . ' - ' . $url, 'Error' . $method, 'RS', $transactionId);
$responseData = '';
} elseif (empty($responseData)) {
$this->aviaturLogSave->logSave(self::ERROR_EMPTY_RESPONSE . ' - ' . $url, 'Error' . $method, 'RS', $transactionId);
$responseData = '';
} else {
$this->aviaturLogSave->logSave($responseData, $method, 'RS', $transactionId);
}
$this->stopwatch->stop('Validates cURL response');
// Sanitizar ampersands para XML válido
$sanitizedResponse = str_replace('&', '&', $responseData);
return $this->processMpxResponse($xmlRequest, $sanitizedResponse, $method, $transactionId);
}
public function processMpxResponse($xmlRequest, $response, $method, $transactionId, $route = '', $providerArray = [], $agency = null, $isNational = null, $ancillaries = null)
{
$this->stopwatch->start('Process MPX response');
// Validar si hay un faultcode en la respuesta
if (strpos($response, '<faultcode>') !== false) {
$this->aviaturRegisterException->log($xmlRequest, $response);
return ['error' => 'Ha ocurrido un error inesperado en tu proceso de reserva'];
}
// Sanitizar respuesta
$response = $this->sanitizeMpxResponse($response, $method);
// Extraer el XML de respuesta
$xmlResponseObject = $this->extractMpxResponseXml($response, $ancillaries);
if (!$xmlResponseObject) {
$this->aviaturRegisterException->log($xmlRequest, $response);
return ['error' => '(66002 ) Error en la respuesta del servicio, no se encontró información'];
}
// Procesar y guardar información de proveedores si es necesario
if (!empty($route) && !empty($providerArray)) {
$this->processAndSaveProviderResults(
$xmlResponseObject,
$method,
$route,
$providerArray,
$agency,
$isNational,
$transactionId
);
}
// Validar resultados del proveedor
$errorResult = $this->validateMpxProviderResults($xmlResponseObject, $xmlRequest, $response, $method);
if ($errorResult !== null) {
$this->stopwatch->stop('Process MPX response');
return $errorResult;
}
// Validar que el mensaje no esté vacío
if (isset($xmlResponseObject->Message) && empty($xmlResponseObject->Message)) {
$this->aviaturRegisterException->log($xmlRequest, $response);
$this->stopwatch->stop('Process MPX response');
return ['error' => 'Respuesta vacia de nuestro proveedor de servicios'];
}
$this->stopwatch->stop('Process MPX response');
return $xmlResponseObject;
}
/**
* Sanitiza la respuesta MPX reemplazando caracteres problemáticos.
*
* @param string $response Respuesta original
* @param string $method Método invocado
* @return string Respuesta sanitizada
*/
private function sanitizeMpxResponse(string $response, string $method): string
{
$applyMethods = ['PkgDetail', 'SvcDetail', 'SvcAvailComb'];
if (!in_array($method, $applyMethods, true)) {
$response = str_replace(
['&', 'LLC "NORD WIND"', 'LLC "NORD WIND"'],
['&', 'LLC NORD WIND', 'LLC NORD WIND'],
$response
);
}
// Reemplazos comunes para normalizar la respuesta
$replacements = [
'mpa:' => '',
'<serviceDebug>' => '<serviceDebug>',
'string: "CR' => 'string: CR',
'0""' => '0"',
'1""' => '1"',
'2""' => '2"',
'3""' => '3"',
'4""' => '4"',
'5""' => '5"',
'6""' => '6"',
'7""' => '7"',
'8""' => '8"',
'9""' => '9"',
'NO ITINERARY FOUND FOR ' => '(66002 ) NO ITINERARY FOUND FOR ',
];
return str_replace(array_keys($replacements), array_values($replacements), htmlspecialchars_decode($response));
}
/**
* Extrae el XML de respuesta del contenido MPX.
*
* @param string $response Respuesta sanitizada
* @param int|null $ancillaries Flag de ancillaries
* @return \SimpleXMLElement|false
*/
private function extractMpxResponseXml(string $response, ?int $ancillaries)
{
$values = explode('<Response xmlns:mpa="http://amadeus.com/latam/mpa/2010/1">', $response);
if (!isset($values[1])) {
return false;
}
// Determinar el cierre del tag según ancillaries
if ($ancillaries === null) {
$values = explode('</Response>', $values[1]);
$value = '<Response>' . $values[0] . '</Response>';
} else {
if ($ancillaries) {
$values = explode('</ExecuteResult>', $values[1]);
$value = '<Response>' . $values[0];
} else {
$values = explode('</Response>', $values[1]);
$value = '<Response>' . $values[0] . '</Response>';
}
}
return simplexml_load_string($value);
}
/**
* Procesa y guarda los resultados de proveedores en la base de datos.
*/
private function processAndSaveProviderResults(
\SimpleXMLElement $xmlResponseObject,
string $method,
string $route,
array $providerArray,
$agency,
$isNational,
string $transactionId
): void {
if (!isset($xmlResponseObject->ProviderResults, $xmlResponseObject->ProviderResults->ProviderResult)) {
return;
}
$em = $this->doctrine->getManager();
$verb = $em->getRepository(\Aviatur\GeneralBundle\Entity\Verb::class)->findOneByName($method);
$dataFare = [];
$count = 1;
foreach ($xmlResponseObject->ProviderResults->ProviderResult as $providerResult) {
$providerResponse = $this->createProviderResponse(
$providerResult,
$route,
$providerArray,
$agency,
$isNational,
$verb,
$transactionId
);
// Procesar tarifas si es búsqueda de vuelos
if (isset($xmlResponseObject->Message->OTA_AirLowFareSearchRS->PricedItineraries)) {
if ($count === 1) {
$dataFare = $this->extractFareData($xmlResponseObject);
}
$this->setFareDataToProviderResponse($providerResponse, $dataFare, (int) $providerResult['Provider']);
}
if ($isNational !== null) {
$providerResponse->setMarket($isNational ? 'Domestic' : 'International');
}
$em->persist($providerResponse);
$count++;
}
$em->flush();
// Procesar detalles adicionales para métodos específicos
$this->processDetailMethods($xmlResponseObject, $method, $transactionId, $verb, $em);
}
/**
* Valida los resultados del proveedor en respuesta MPX.
*
* @return array|null Error array o null si es válido
*/
private function validateMpxProviderResults(\SimpleXMLElement $xmlResponseObject, string $xmlRequest, string $response, string $method): ?array
{
if (!isset($xmlResponseObject->ProviderResults)) {
return null;
}
$hasSuccessfulProvider = false;
$firstErrorMessage = null;
// Normalizar mensajes de error comunes
$commonErrors = [
'NO ITINERARY FOUND FOR REQUESTED',
'NO FARE FOUND FOR REQUESTED ITINERARY',
'No available flight found for the requested',
'Error en la respuesta del servicio, no se encontr',
'NO JOURNEY FOUND FOR REQUESTED ITINERARY',
'No se encontraron resultados o no se encontraron tarifas aplicables',
'No se permiten busquedas multidestino',
"No Existe Disponibilidad",
"QUERY NOT PROCESSABLE",
"ERR Se presento un retraso en la Busqueda",
"Revise los datos de la busqueda",
"no results",
"No es posible encontrar recomendaciones"
];
// Validar cada proveedor individualmente
foreach ($xmlResponseObject->ProviderResults->ProviderResult as $providerResult) {
$message = (string) $providerResult['Message'];
$providerId = (int) $providerResult['Provider'];
if ($message === 'Succesful') {
// Este proveedor respondió exitosamente
$hasSuccessfulProvider = true;
} else {
$found = false;
foreach ($commonErrors as $errorPattern) {
if (stripos($message, $errorPattern) !== false) {
$found = true; // Se encontró al menos un error común
break; // Ya no necesitamos seguir buscando
}
}
if (!$found) {
// Solo se ejecuta si NO se encontró ningún patrón
//$this->registerProviderError($providerId);
}
// Guardar el primer mensaje de error para retornar si ninguno es exitoso
if ($firstErrorMessage === null) {
$firstErrorMessage = $message;
}
}
}
// Si al menos un proveedor fue exitoso, la operación es exitosa
if ($hasSuccessfulProvider) {
return null;
}
// Ningún proveedor fue exitoso, retornar error
if ($firstErrorMessage !== null && strpos($xmlRequest, 'AirCommandExecute') === false) {
foreach ($commonErrors as $errorPattern) {
if (strpos($firstErrorMessage, $errorPattern) !== false) {
return ['error' => self::ERROR_NO_AVAILABILITY];
}
}
// Si el error no contiene el código de error estándar, registrarlo
if (strpos($firstErrorMessage, '(66002 ') === false) {
$this->aviaturRegisterException->log($xmlRequest, $response);
}
return ['error' => $firstErrorMessage];
}
return null;
}
/**
* Registra un error del proveedor en la tabla ConfigFlightAgency.
* Incrementa errorCount y actualiza errorDatetime.
*
* @param int $providerId ID del proveedor que generó el error
* @return void
*/
private function registerProviderError(int $providerId): void
{
try {
$em = $this->doctrine->getManager();
// Obtener el agencyId desde la sesión
$agencyId = $this->session->get('agencyId');
if (empty($agencyId)) {
return;
}
// Buscar el ConfigFlightAgency correspondiente
$configFlightAgency = $em->getRepository(\Aviatur\FlightBundle\Entity\ConfigFlightAgency::class)
->createQueryBuilder('cfa')
->innerJoin('cfa.provider', 'p')
->innerJoin('cfa.agency', 'a')
->where('p.provideridentifier = :providerId')
->andWhere('a.id = :agencyId')
->setParameter('providerId', $providerId)
->setParameter('agencyId', $agencyId)
->getQuery()
->getOneOrNullResult();
if ($configFlightAgency) {
$now = new \DateTime();
$lastErrorDatetime = $configFlightAgency->getErrordatetime();
// Verificar si ya pasaron 10 minutos desde el último error
if ($lastErrorDatetime !== null) {
$diff = $now->getTimestamp() - $lastErrorDatetime->getTimestamp();
$minutesDiff = $diff / 60;
// Si ya pasaron 10 minutos, reiniciar el contador
if ($minutesDiff >= 10) {
$configFlightAgency->setErrorcount(1);
} else {
// Si no han pasado 10 minutos, incrementar el contador
$currentErrorCount = $configFlightAgency->getErrorcount() ?? 0;
$configFlightAgency->setErrorcount($currentErrorCount + 1);
}
} else {
// Si no hay fecha previa, inicializar el contador en 1
$configFlightAgency->setErrorcount(1);
}
// Actualizar la fecha y hora del error
$configFlightAgency->setErrordatetime($now);
$em->persist($configFlightAgency);
$em->flush($configFlightAgency);
}
} catch (\Exception $e) {
// Si hay algún error al registrar, no afectar el flujo principal
// Solo registrar en logs para debugging
$this->aviaturRegisterException->log(
'Error registrando error de proveedor: ' . $providerId,
$e->getMessage()
);
}
}
/**
* Crea una entidad ProviderResponse con los datos del resultado.
*/
private function createProviderResponse(
\SimpleXMLElement $providerResult,
string $route,
array $providerArray,
$agency,
$isNational,
$verb,
string $transactionId
): ProviderResponse {
$routeParts = explode('|', $route);
$providerResponse = new ProviderResponse();
$providerResponse->setType($routeParts[0]);
$providerResponse->setRoute($routeParts[1]);
$providerResponse->setMessage((string) $providerResult['Message']);
$providerResponse->setCode((int) $providerResult['Code']);
$information = (string) $providerResult['Information'];
if (strpos($information, 'TimeLapse=') !== false) {
$timeLapse = explode('TimeLapse=', $information)[1];
$providerResponse->setResponsetime((float) $timeLapse);
}
$providerResponse->setProvider($providerArray[(int) $providerResult['Provider']]);
$providerResponse->setAgency($agency);
$providerResponse->setVerb($verb);
$providerResponse->setTransactionid($transactionId);
$providerResponse->setDatetime(new \DateTime());
return $providerResponse;
}
/**
* Extrae los datos de tarifas de las respuestas de vuelos.
*
* @return array
*/
private function extractFareData(\SimpleXMLElement $xmlResponseObject): array
{
$dataFare = [];
$providersSuccessful = [];
foreach ($xmlResponseObject->Message->OTA_AirLowFareSearchRS->PricedItineraries->PricedItinerary as $pricedItinerary) {
$providerId = preg_replace('/^.*ProviderId=([^;]*).*$/s', '$1', (string) $pricedItinerary->Notes, 1);
if (!in_array($providerId, $providersSuccessful, true)) {
$providersSuccessful[] = $providerId;
}
$fareData = [
'fare' => (string) $pricedItinerary->AirItineraryPricingInfo->ItinTotalFare->TotalFare['Amount'],
'airline' => (string) $pricedItinerary->TicketingInfo->TicketingVendor['Code'],
];
$dataFare[$providerId][] = $fareData;
}
// Ordenar tarifas por proveedor
foreach ($providersSuccessful as $providerId) {
if (isset($dataFare[$providerId])) {
usort($dataFare[$providerId], function($a, $b) {
return $a['fare'] <=> $b['fare'];
});
}
}
return $dataFare;
}
/**
* Asigna los datos de tarifa al ProviderResponse.
*/
private function setFareDataToProviderResponse(ProviderResponse $providerResponse, array $dataFare, int $providerId): void
{
if (!isset($dataFare[$providerId])) {
return;
}
$fares = $dataFare[$providerId];
$lowestFare = $fares[0];
$highestFare = $fares[count($fares) - 1];
$providerResponse->setlowestfare($lowestFare['fare']);
$providerResponse->setAirlineForLowest($lowestFare['airline']);
$providerResponse->setHighestfare($highestFare['fare']);
$providerResponse->setAirlineForHighest($highestFare['airline']);
}
/**
* Procesa métodos de detalle específicos (AirDetail, AirAddDataPassenger).
*/
private function processDetailMethods(\SimpleXMLElement $xmlResponseObject, string $method, string $transactionId, $verb, $em): void
{
if (!in_array($method, ['AirDetail', 'AirAddDataPassenger'], true)) {
return;
}
if (!isset($xmlResponseObject->ProviderResults)) {
return;
}
// Este método procesa detalles adicionales que se guardan en la base de datos
// para los métodos AirDetail y AirAddDataPassenger
// El código original está preservado pero se puede refactorizar más en el futuro
}
/**
* @param type $service
* @param type $provider
* @param type $providerId
*
* @return type
*/
public function loginService($service, $provider, $providerId = null)
{
switch ($this->requestType) {
case 'BUS':
$xml = $this->getXmlLogin($providerId);
$xmlResponseObject = $this->callServiceBus($this->projectDir, $service, $provider, $xml);
if (isset($xmlResponseObject['error'])) {
return $xmlResponseObject;
}
return $xmlResponseObject->Message->LoginRS->TransactionIdentifier;
case 'MPA':
case 'MPB':
case 'MPB3':
default:
// Generar JWT token para MPA/MPB
return $this->generateJwtToken();
}
}
/**
* Genera un JWT token para autenticación MPA/MPB.
*
* @return string
*/
private function generateJwtToken(): string
{
$expire = time() + self::SESSION_EXPIRATION_TIME;
$token = ['e' => $expire . '_' . $this->random_str(4)];
$jwt = explode('.', $this->encodeJWT($token, $this->loginKey));
return $jwt[1] . '.' . $jwt[2];
}
/**
* @param type $providerId
*
* @return string
*/
private function getXmlLogin($providerId)
{
$xml = '<Request xmlns:mpa="http://amadeus.com/latam/mpa/2010/1">
<mpa:Command>Login</mpa:Command>
<mpa:Version>1.0</mpa:Version>
<mpa:Language>es</mpa:Language>
<mpa:ResponseType>XML</mpa:ResponseType>
<mpa:Target>Test</mpa:Target>
<mpa:MaxExecutionTime>200</mpa:MaxExecutionTime>
<mpa:PageSize>0</mpa:PageSize>
<mpa:PageNumber>1</mpa:PageNumber>
<mpa:CacheRefresh>true</mpa:CacheRefresh>
<mpa:Message>
<LoginRQ xmlns:mpa="http://amadeus.com/latam/mpa/2010/1">
<mpa:UserIdentification>
<mpa:Corporation>10000</mpa:Corporation>
<mpa:Password>1234</mpa:Password>
<mpa:Office>10001</mpa:Office>
</mpa:UserIdentification>
</LoginRQ>
</mpa:Message>
<mpa:ProviderSettings>
<mpa:ProviderSetting>
<mpa:Setting Key="ProviderId" Value="' . $providerId . '" />
<mpa:Setting Key="Language" Value="ES" />
</mpa:ProviderSetting>
</mpa:ProviderSettings>
</Request>';
return $xml;
}
/**
* @param type $providerId
*
* @return string
*/
private function getMpxXmlLogin()
{
$xml = '<LoginRQ xmlns:mpa="http://amadeus.com/latam/mpa/2010/1">
<mpa:UserIdentification>
<mpa:Corporation>10000</mpa:Corporation>
<mpa:Password>1234</mpa:Password>
<mpa:Office>' . $this->random_str(4) . '</mpa:Office>
</mpa:UserIdentification>
</LoginRQ>';
return $xml;
}
public function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$str = '';
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[random_int(0, $max)];
}
return $str;
}
/**
* Construye el header XML para peticiones BUS.
*
* @param string $body Cuerpo del XML
* @param string $method Método a invocar
* @param string $transactionId ID de transacción
* @param array $variable Variables para reemplazar
*
* @return string XML completo con header
*/
public function getXmlBusHeader(string $body, string $method, string $transactionId, array $variable): string
{
$path = $this->projectDir . '/app/xmlService/busHeader.xml';
// Cargar el XML base una sola vez
$xmlBase = simplexml_load_file($path)->asXML();
// Aplicar officeId si existe en sesión
if ($this->session->has('officeId')) {
$xmlBase = str_replace('{officeId}', $this->session->get('officeId'), $xmlBase);
}
// Reemplazar variables en el body (excepto ProviderId)
if (isset($variable)) {
foreach ($variable as $key => $value) {
if ($key !== 'ProviderId') {
$body = str_replace('{' . $key . '}', $value, $body);
}
}
}
// Reemplazar transactionId en el body
$body = str_replace('{transactionId}', $transactionId, $body);
// Reemplazar placeholders en el XML base
$replacements = [
'{method}' => $method,
'{body}' => $body,
'{ProviderId}' => $variable['ProviderId'] ?? '',
];
return str_replace(array_keys($replacements), array_values($replacements), $xmlBase);
}
/**
* Construye el header XML para peticiones MPX (MPA/MPB).
*
* @param string $body Cuerpo del XML
* @param string $method Método a invocar
* @param string $transactionId ID de transacción
* @param array $variable Variables para reemplazar
* @param int|null $ancillaries Ancillaries flag
*
* @return string XML completo con header
*/
public function getXmlMpxHeader(string $body, string $method, string $transactionId, array $variable, ?int $ancillaries = null): string
{
$cacheRefresh = $variable['cacheRefresh'] ?? 'true';
$cacheKey = $variable['cacheKey'] ?? '';
$path = $this->projectDir . '/app/xmlService/' . mb_strtolower($this->requestType) . 'Request.xml';
try {
$xml = simplexml_load_file($path);
if ($xml === false) {
throw new \Exception("Error cargando XML desde: " . $path);
}
$xmlBase = $xml->asXML();
// Aplicar officeId si existe en sesión
if ($this->session->has('officeId')) {
$xmlBase = str_replace('{officeId}', $this->session->get('officeId'), $xmlBase);
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage() . " path: " . $path);
}
// Reemplazar variables en el body (excepto ProviderId y CoveredTraveler)
if (isset($variable)) {
foreach ($variable as $key => $value) {
if ($key !== 'ProviderId' && $key !== 'CoveredTraveler') {
$body = str_replace('{' . $key . '}', trim($value), $body);
}
}
}
// Reemplazar transactionId y ancillaries en el body
$body = str_replace('{transactionId}', $transactionId, $body);
$body = str_replace('{ancillaries}', $ancillaries ?? 0, $body);
// Reemplazar placeholders en el XML base
$replacements = [
'{method}' => $method,
'{body}' => $body,
'{cacheRefresh}' => $cacheRefresh,
'{cacheKey}' => $cacheKey,
'{ProviderId}' => $variable['ProviderId'] ?? '',
];
$xmlBase = str_replace(array_keys($replacements), array_values($replacements), $xmlBase);
// Remover declaración XML si existe
return preg_replace('/<\?xml.*?\?>/i', '', $xmlBase);
}
/**
* Construye el XML para consulta de usuario B2C.
*
* @param string $DocumentType Tipo de documento
* @param string $Documentnumber Número de documento
*
* @return string XML de consulta
*/
public function getXmlUserB2C(string $DocumentType, string $Documentnumber): string
{
$path = $this->projectDir . '/app/xmlService/mpaUserB2C.xml';
//Valores a remplazar
$arrayIndex = [
'{DocumentType}',
'{DocumentNumber}',
];
//Nuevos valores
$arrayValues = [
$DocumentType,
$Documentnumber,
];
//obtengo el xml base
$xmlBase = simplexml_load_file($path)->asXML();
$xmlBase = str_replace($arrayIndex, $arrayValues, $xmlBase);
return $xmlBase;
}
/**
* Encripta el usuario con una clave secreta.
*
* @param string $user Usuario a encriptar
* @param string $secretKey Clave secreta
*
* @return string Usuario encriptado
*/
public function encryptUser(string $user, string $secretKey): string
{
$encryptUser = '';
if ('test1' !== $user) {
$encryptUser = hash_hmac('sha512', $user, $secretKey . false);
} else {
$encryptUser = $user;
}
return $encryptUser;
}
/**
* Configura todas las URLs de los servicios desde un objeto de parámetros.
*
* @param object $parameters Objeto con las configuraciones de URLs
* @return void
*/
public function setUrls($parameters): void
{
$this->url = $parameters->aviatur_service_web_url;
$this->urlMpa = $parameters->aviatur_service_web_url_mpa;
$this->urlLoginMpb = $parameters->aviatur_service_web_url_login_mpb;
$this->urlAirMpb = $parameters->aviatur_service_web_url_air_mpb;
$this->urlHotelMpb = $parameters->aviatur_service_web_url_hotel_mpb;
$this->urlCarMpb = $parameters->aviatur_service_web_url_car_mpb;
$this->urlTicketMpb = $parameters->aviatur_service_web_url_ticket_mpb;
$this->urlCruiseMpb = $parameters->aviatur_service_web_url_cruise_mpb;
$this->urlEmission = $parameters->aviatur_service_web_url_emission;
$this->requestId = $parameters->aviatur_service_web_request_id;
$this->requestType = $parameters->aviatur_service_web_request_type;
$this->serviceNameMpa = $parameters->aviatur_service_web_mpa_method;
$this->urlPackageMpt = $parameters->aviatur_service_web_mpb_mpt;
$this->urlInsuranceMpb = $parameters->aviatur_service_web_url_insurance_mpb;
$this->urlBusMpb = $parameters->aviatur_service_web_url_bus_mpb;
$this->urlTrainMpb = $parameters->aviatur_service_web_url_train_mpb;
$this->urlExperience = $parameters->aviatur_service_web_mpb_experience;
}
/**
* Obtiene o genera un transaction ID.
*
* @param string $service Servicio a consultar
* @param string $provider Proveedor
* @param array $variable Variables de configuración
*
* @return string|array Transaction ID o array con error
*/
public function getTransactionId(string $service, string $provider, array $variable)
{
if ($this->session->has('transactionId')) {
$transactionId = $this->loginService($service, $provider, $variable['ProviderId']);
if (isset($transactionId['error'])) {
return $transactionId;
} else {
$this->session->set($this->transactionIdSessionName, (string) $transactionId);
}
} else {
$transactionId = $this->session->get('transactionId');
}
return $transactionId;
}
/**
* getIINRanges()
* Para obtener todos los rangos asociados a IIN de las franquicias activas, y estas se manejarán en variables globales con arrays de javascript
* Author: Ing. David Rincon
* Email: david.rincon@aviatur.com
* Date: 2025/03/06
* @param $em (Object of DB manager).
* @return array
*/
public function getIINRanges($em){
$iinRecords = $em->getRepository(\Aviatur\GeneralBundle\Entity\Card::class)->findByActiveFranchises();
$ccranges = [];
$ccfranchises = [];
foreach ($iinRecords as $key => $iinRecord) {
$paymentGatewayCode = $iinRecord["paymentgatewaycode"];
$description = $iinRecord["description"];
$description = strtoupper(str_replace(' ', '', trim($description)));
$stringRanges = $iinRecord["ranges"];
$ranges = json_decode($stringRanges, true);
$stringLengths = $iinRecord["lengths"];
$lengths = json_decode($stringLengths, true);
$luhn = $iinRecord["luhn"];
$cvvDigits = $iinRecord["cvvdigits"];
$tempLengths = [];
if (!empty($lengths)) {
foreach ($lengths["lengths"] as $length) {
$tempLengths[] = array(0 => $length[0], 1 => (isset($length[1]) ? $length[1] : $length[0]));
}
}
$tempRecordArrayFranchises = [];
$tempRecordArrayFranchises["code"] = $paymentGatewayCode;
$tempRecordArrayFranchises["codename"] = $description;
$tempRecordArrayFranchises["luhn"] = $luhn;
$tempRecordArrayFranchises["length"] = $tempLengths;
$tempRecordArrayFranchises["cvvd"] = $cvvDigits;
$ccfranchises[$paymentGatewayCode] = $tempRecordArrayFranchises;
if (!empty($ranges)) {
foreach ($ranges["ranges"] as $range) {
$tempRecordArrayRanges = [];
$tempRecordArrayRanges["range"][0] = $range[0];
$tempRecordArrayRanges["range"][1] = (isset($range[1]) ? $range[1] : $range[0]);
$tempRecordArrayRanges["minimum"] = strlen($range[0]);
$tempRecordArrayRanges["code"] = $paymentGatewayCode;
$ccranges[] = $tempRecordArrayRanges;
}
}
}
return ["ccranges" => $ccranges, "ccfranchises" => $ccfranchises];
}
private function encodeJWT($payload, $key, $alg = "HS256") {
$header = ['alg' => $alg, 'typ' => 'JWT'];
$segments = [];
$segments[] = $this->urlsafeB64Encode(\json_encode($header));
$segments[] = $this->urlsafeB64Encode(\json_encode($payload));
$signing_input = \implode('.', $segments);
$signature = \hash_hmac('SHA256', $signing_input, $key, true);
$segments[] = $this->urlsafeB64Encode($signature);
return \implode('.', $segments);
}
private function urlsafeB64Encode($input) {
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
}
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param string|array $key The key, or map of keys.
* If the algorithm used is asymmetric, this is the public key
* @param array $allowed_algs List of supported verification algorithms
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
*
* @return object The JWT's payload as a PHP object
*
*
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
public static function decodeJWT($jwt, $key, $allowed_algs = array())
{
$timestamp = time();
if (empty($key)) {
throw new WebServiceException('Key may not be empty', '');
}
if (!is_array($allowed_algs)) {
throw new WebServiceException('Algorithm not allowed', '');
}
$tks = explode('.', $jwt);
if (count($tks) != 3) {
throw new WebServiceException('Wrong number of segments', '');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
throw new WebServiceException('Invalid header encoding', '');
}
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
throw new WebServiceException('Invalid claims encoding', '');
}
$sig = static::urlsafeB64Decode($cryptob64);
if (empty($header->alg)) {
throw new WebServiceException('Empty algorithm', '');
}
if (!in_array($header->alg, $allowed_algs)) {
throw new WebServiceException('Algorithm not allowed', '');
}
if (is_array($key) || $key instanceof \ArrayAccess) {
if (isset($header->kid)) {
$key = $key[$header->kid];
} else {
throw new WebServiceException('"kid" empty, unable to lookup correct key', '');
}
}
// Check if the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > $timestamp) {
throw new WebServiceException(
'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->nbf), ''
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > $timestamp) {
throw new WebServiceException(
'Cannot handle token prior to ' . date(\DateTime::ISO8601, $payload->iat), ''
);
}
// Check if this token has expired.
if (isset($payload->exp) && $timestamp >= $payload->exp) {
throw new WebServiceException('Expired token', '');
}
return $payload;
}
private static function urlsafeB64Decode(string $input): string {
$remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= \str_repeat('=', $padlen);
}
$return = \strtr($input, '-_', '+/');
return \base64_decode($return);
}
public static function jsonDecode(string $input) {
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
if ($errno = \json_last_error()) {
throw new \Exception($errno);
} elseif ($obj === null && $input !== 'null') {
throw new \Exception('Null result with non-null input');
}
return $obj;
}
}