HotFix para manejo de excepciones de timeout para referencias de oxxo y otros errores

This commit is contained in:
server 2025-03-04 09:32:03 +00:00
parent 398dcc462f
commit cd74cafeda
16 changed files with 5016 additions and 205 deletions

0
.gitignore vendored Normal file → Executable file
View File

View File

@ -1,8 +1,7 @@
# SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp
* This plugin sends WhatsApp notifications to clients.
* It only sends SMS to clients having a phone number set in their contacts details.
* [Twilio](https://www.twilio.com/) account is required to access its API.
Este plugin sincroniza los clientes del sitema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes
## Configuration

View File

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

0
comprobantes/Comprobante_Mofles_El_Ruco_Lopez.pdf Normal file → Executable file
View File

0
comprobantes/Comprobante_Mofles_El_Ruco_Lopez.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

0
comprobantes/Comprobante_Águila_Calva_En_el_Nopal.pdf Normal file → Executable file
View File

0
comprobantes/Comprobante_Águila_Calva_En_el_Nopal.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,9 @@
"information": {
"name": "siip-whatsapp-notifications",
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
"description": "Este plugin sincroniza los clientes del sitema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
"url": "https://siip.mx/",
"version": "2.6.6",
"version": "2.6.7",
"unmsVersionCompliancy": {
"min": "2.1.0",
"max": null

View File

@ -53,6 +53,10 @@ abstract class AbstractOxxoOperationsFacade
/*
* Creates a PaymentIntent for OXXO in Stripe for a Customer
* @param array $event_json
* @param int|null $amount
* @return string
* @throws Exception
*/
public function createOxxoPaymentIntent($event_json, $amount = null): string
{
@ -79,29 +83,46 @@ abstract class AbstractOxxoOperationsFacade
'Accept' => 'application/json',
],
'verify' => false,
'timeout' => 5, // Timeout de 5 segundos
]);
$response = $clientGuzzleHttp->request('GET', "clients/" . $clientID);
$arrayClientCRM = json_decode($response->getBody()->getContents(), true);
try {
//$this->logger->info("Obteniendo el cliente de CRM: " . $clientID . PHP_EOL);
$response = $clientGuzzleHttp->request('GET', "clients/" . $clientID);
$arrayClientCRM = json_decode($response->getBody()->getContents(), true);
// Obtener stripeCustomerId
foreach ($arrayClientCRM['attributes'] as $attribute) {
if ($attribute['key'] === 'stripeCustomerId') {
$stripeCustomerId = $attribute['value'];
break;
// Obtener stripeCustomerId
foreach ($arrayClientCRM['attributes'] as $attribute) {
if ($attribute['key'] === 'stripeCustomerId') {
$stripeCustomerId = $attribute['value'];
break;
}
}
}
// Obtener email del cliente
foreach ($arrayClientCRM['contacts'] as $contact) {
if (!empty($contact['email'])) {
$clientEmail = $contact['email'];
break;
// Obtener email del cliente
foreach ($arrayClientCRM['contacts'] as $contact) {
if (!empty($contact['email'])) {
$clientEmail = $contact['email'];
break;
} else {
$clientEmail = 'siip8873@gmail.com';
}
}
} catch (RequestException $e) {
if ($e->getCode() === 404) {
$this->logger->error("Error al obtener el cliente de CRM" . PHP_EOL);
return 'errorGetClientNofFound';
} else {
$clientEmail = 'siip8873@gmail.com';
$this->logger->error("Error al obtener el cliente de CRM" . PHP_EOL);
return 'errorGetClient';
}
}
// Verificar si $amount es null para determinar si es necesario consultar la API
if ($amount === null) {
try {
@ -145,6 +166,13 @@ abstract class AbstractOxxoOperationsFacade
]);
} catch (Exception $e) {
$this->logger->error('Error al crear el payment intent: ' . $e->getMessage() . PHP_EOL);
return 'errorCreatePaymentIntent';
}
try{
// Obtener los nombres del cliente
$firstName = isset($arrayClientCRM['firstName']) ? trim($arrayClientCRM['firstName']) : '';
$lastName = isset($arrayClientCRM['lastName']) ? trim($arrayClientCRM['lastName']) : '';
@ -173,22 +201,30 @@ abstract class AbstractOxxoOperationsFacade
['payment_method' => $paymentMethod->id]
);
$this->logger->info("Se terminó de confirmar el paymentIntent" . PHP_EOL);
if (!empty($paymentIntent->next_action) && isset($paymentIntent->next_action->oxxo_display_details)) {
$oxxoPayment = $paymentIntent->next_action->oxxo_display_details;
$oxxo_reference = $oxxoPayment->number;
$oxxo_receipt_url = $oxxoPayment->hosted_voucher_url;
$this->logger->info("Referencia OXXO: " . $oxxo_reference . PHP_EOL);
$this->logger->info("URL del recibo: " . $oxxo_receipt_url . PHP_EOL);
return $oxxo_receipt_url;
} else {
$this->logger->info("El PaymentIntent no tiene detalles de OXXO disponibles aún. " . PHP_EOL);
$this->logger->info("Estado actual del PaymentIntent: " . $paymentIntent->status . PHP_EOL);
return 'errorPyamentIntentWithoutOxxoDetails';
}catch (Exception $e) {
if($e->getCode() === 408){
$this->logger->error('Error de timeout al confirmar el payment intent: ' . $e->getMessage() . PHP_EOL);
return 'errorTimeoutConfirmPaymentIntent';
}else{
$this->logger->error('Error al confirmar el payment intent: ' . $e->getMessage() . PHP_EOL);
return 'errorConfirmPaymentIntent';
}
} catch (Exception $e) {
$this->logger->error('Error al crear el payment intent: ' . $e->getMessage() . PHP_EOL);
return 'errorCreatePaymentIntent';
}
if (!empty($paymentIntent->next_action) && isset($paymentIntent->next_action->oxxo_display_details)) {
$oxxoPayment = $paymentIntent->next_action->oxxo_display_details;
$oxxo_reference = $oxxoPayment->number;
$oxxo_receipt_url = $oxxoPayment->hosted_voucher_url;
$this->logger->info("Referencia OXXO: " . $oxxo_reference . PHP_EOL);
$this->logger->info("URL del recibo: " . $oxxo_receipt_url . PHP_EOL);
return $oxxo_receipt_url;
} else {
$this->logger->info("El PaymentIntent no tiene detalles de OXXO disponibles aún. " . PHP_EOL);
$this->logger->info("Estado actual del PaymentIntent: " . $paymentIntent->status . PHP_EOL);
return 'errorPyamentIntentWithoutOxxoDetails';
}
} else {
$this->logger->info("Este cliente no tiene adeudos por lo tanto no se puede generar su referencia de OXXO. " . PHP_EOL);

View File

@ -1005,7 +1005,7 @@ class ClientCallBellAPI
'"Domicilio": "' .
(($notificationData->clientData['fullAddress'] == null) ? '📍❌ Sin domicilio' : '📍 ' . $notificationData->clientData['fullAddress']) . '",' .
'"Nombre": "' . sprintf("👤 %s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']) . '",' .
'"URL": "🌐 https://172.16.5.120/crm/client/' . $notificationData->clientId . '",' .
'"URL": "🌐 https://sistema.siip.mx/crm/client/' . $notificationData->clientId . '",' .
'"Saldo Actual": "' . $saldoTexto . '",' .
'"Monto Ultimo Pago": "💲 ' . $payments[0]['amount'] . '",' .
'"Estado": "' .
@ -1027,12 +1027,12 @@ class ClientCallBellAPI
"Cliente" => $notificationData->clientData['id'],
"Domicilio" => ($notificationData->clientData['fullAddress'] == null) ? '📍❌ Sin domicilio' : '📍 ' . $notificationData->clientData['fullAddress'],
"Nombre" => sprintf("👤 %s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']),
"URL" => '🌐 https://172.16.5.120/crm/client/' . $notificationData->clientId,
"URL" => '🌐 https://sistema.siip.mx/crm/client/' . $notificationData->clientId,
"Saldo Actual" => $saldoTexto,
"Monto Ultimo Pago" => '💲 ' . $payments[0]['amount'],
"Estado" => ($notificationData->clientData['hasSuspendedService']) ? '🔴 Suspendido' : '🟢 Activo ',
"Resumen" => $resumenClienteJSON,
"Fecha Ultimo Pago" => '📆💸 ' . $fecha_ultimoPago_ajustada . ' con ' . $payment_method,
"Fecha Ulti-mo Pago" => '📆💸 ' . $fecha_ultimoPago_ajustada . ' con ' . $payment_method,
"Fecha Ultima Actualizacion" => '📆🔄️ ' . $fecha_actual_ajustada,
"Clabe Interbancaria" => $clabeInterbancaria
)
@ -1156,101 +1156,6 @@ class ClientCallBellAPI
}
}
function UploadFileWordpress($fileHandle): string
{
$log = PluginLogManager::create(); //Initialize Logger
// Configuración de conexión FTP
$ftp_server = "siip.mx";
$ftp_username = "siip0001";
$ftp_password = '$spGiT,[wa)n';
//$fileName = $fileNameComprobantePDF;
$remote_file = "/public_html/wp/wp-content/uploads/pdf/Comprobante_de_pago.pdf";
//$file_to_upload = '/home/unms/data/ucrm/ucrm/data/payment_receipts/' . $fileName;
$url = 'http://siip.mx/wp/wp-content/uploads/pdf/Comprobante_de_pago.pdf';
// Conexión FTP
$ftp_conn = ftp_connect($ftp_server) or die("No se pudo conectar al servidor FTP");
$login = ftp_login($ftp_conn, $ftp_username, $ftp_password);
ftp_pasv($ftp_conn, true);
// Verificar conexión y login
if ($ftp_conn && $login) {
$log->appendLog("Conexión FTP exitosa" . PHP_EOL);
// Cargar archivo
if (ftp_fput($ftp_conn, $remote_file, $fileHandle, FTP_BINARY)) {
$log->appendLog("El archivo ha sido cargado exitosamente." . PHP_EOL);
$log->appendLog("La URL es: " . $url . PHP_EOL);
// Cerrar conexión FTP
ftp_close($ftp_conn);
return $url;
} else {
$log->appendLog("Error al cargar el archivo " . PHP_EOL);
}
// Cerrar conexión FTP
ftp_close($ftp_conn);
return '';
} else {
$log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
return '';
}
}
function UploadReceiptToWordpressByServerFileName($fileNamePaymentReceiptServer, $newCustomFileName): string
{
$log = PluginLogManager::create(); //Initialize Logger
// Configuración de conexión FTP
$ftp_server = "siip.mx";
$ftp_username = "siip0001";
$ftp_password = '$spGiT,[wa)n';
$remote_file = "/public_html/wp/wp-content/uploads/pdf/" . $newCustomFileName;
$file_to_upload = __DIR__ . '/../../../../payment_receipts/' . $fileNamePaymentReceiptServer;
$url = 'https://siip.mx/wp/wp-content/uploads/pdf/' . $newCustomFileName;
// Conexión FTP
$ftp_conn = ftp_connect($ftp_server) or die("No se pudo conectar al servidor FTP");
$login = ftp_login($ftp_conn, $ftp_username, $ftp_password);
ftp_pasv($ftp_conn, true);
// Verificar conexión y login
if ($ftp_conn && $login) {
$log->appendLog("Conexión FTP exitosa" . PHP_EOL);
// Cargar archivo
if (ftp_put($ftp_conn, $remote_file, $file_to_upload, FTP_BINARY)) {
$log->appendLog("El archivo ha sido cargado exitosamente." . PHP_EOL);
$log->appendLog("La URL es: " . $url . PHP_EOL);
// Cerrar conexión FTP
ftp_close($ftp_conn);
return $url;
} else {
$log->appendLog("Error al cargar el archivo " . PHP_EOL);
}
// Cerrar conexión FTP
ftp_close($ftp_conn);
return '';
} else {
$log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
return '';
}
}
function UploadReceiptToWordpressByImageFileName($imageFileName): string
{
@ -1288,9 +1193,11 @@ class ClientCallBellAPI
if (ftp_put($ftp_conn, $remote_file, $file_to_upload, FTP_BINARY)) {
$log->appendLog("El archivo ha sido cargado exitosamente." . PHP_EOL);
$log->appendLog("La URL es: " . $url . PHP_EOL);
$this->deleteFilesWordpressExceptLastHundred($log,$ftp_conn,$remote_file);
// Cerrar conexión FTP
//ftp_close($ftp_conn);
//return $url;
ftp_close($ftp_conn);//COMENTAR AQUÍ SI SE BORRAN LOS ARCHIVOS DE WORDPRESS DESCOMENTANDO EL CÓDIGO DE MÁS ABAJO
return $url;//COMENTAR AQUÍ SI SE BORRAN LOS ARCHIVOS DE WORDPRESS DESCOMENTANDO EL CÓDIGO DE MÁS ABAJO
} else {
$log->appendLog("Error al cargar el archivo " . PHP_EOL);
@ -1298,49 +1205,49 @@ class ClientCallBellAPI
return '';
}
//SI SE DECIDE VOLVER A ELIMINAR LOS COMPROBANTES ENTONCES DESCOMENTAR ESTA PARTE DE ABAJO Y COMENTAR LA SECCIÓN DE ARRIBA
// Obtener lista de archivos en la carpeta
$files = ftp_nlist($ftp_conn, $remote_folder);
if (is_array($files)) {
// Eliminar la ruta del directorio de los archivos
$files = array_map(function ($file) use ($remote_folder) {
return str_replace($remote_folder, '', $file);
}, $files);
// $files = ftp_nlist($ftp_conn, $remote_folder);
// if (is_array($files)) {
// // Eliminar la ruta del directorio de los archivos
// $files = array_map(function ($file) use ($remote_folder) {
// return str_replace($remote_folder, '', $file);
// }, $files);
// Obtener fechas de modificación
$filesWithTime = [];
foreach ($files as $file) {
$modifiedTime = ftp_mdtm($ftp_conn, $remote_folder . $file);
if ($modifiedTime != -1) {
$filesWithTime[$file] = $modifiedTime;
}
}
// // Obtener fechas de modificación
// $filesWithTime = [];
// foreach ($files as $file) {
// $modifiedTime = ftp_mdtm($ftp_conn, $remote_folder . $file);
// if ($modifiedTime != -1) {
// $filesWithTime[$file] = $modifiedTime;
// }
// }
// Ordenar archivos por fecha de modificación, más recientes primero
arsort($filesWithTime);
// // Ordenar archivos por fecha de modificación, más recientes primero
// arsort($filesWithTime);
// Obtener los archivos a eliminar (todos menos los 50 más recientes)
$filesToDelete = array_slice(array_keys($filesWithTime), 50);
// // Obtener los archivos a eliminar (todos menos los 50 más recientes)
// $filesToDelete = array_slice(array_keys($filesWithTime), 50);
// // Eliminar archivos antiguos
// foreach ($filesToDelete as $file) {
// if (ftp_delete($ftp_conn, $remote_folder . $file)) {
// $log->appendLog("Comprobante eliminado de Wordpress: " . $file . PHP_EOL);
// } else {
// $log->appendLog('Error al borrar comprobante' . $file . PHP_EOL);
// }
// }
// $log->appendLog("Archivos eliminados" . PHP_EOL);
// ftp_close($ftp_conn);
// return $url;
// } else {
// $log->appendLog("No se pudo obtener la lista de archivos de la carpeta FTP" . PHP_EOL);
// ftp_close($ftp_conn);
// return $url;
// }
// Eliminar archivos antiguos
foreach ($filesToDelete as $file) {
if (ftp_delete($ftp_conn, $remote_folder . $file)) {
$log->appendLog("Comprobante eliminado de Wordpress: " . $file . PHP_EOL);
} else {
$log->appendLog('Error al borrar comprobante' . $file . PHP_EOL);
}
}
$log->appendLog("Archivos eliminados" . PHP_EOL);
ftp_close($ftp_conn);
return $url;
} else {
$log->appendLog("No se pudo obtener la lista de archivos de la carpeta FTP" . PHP_EOL);
ftp_close($ftp_conn);
return $url;
}
// Cerrar conexión FTP
//ftp_close($ftp_conn);
//return '';
} else {
$log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
return '';
@ -1349,7 +1256,50 @@ class ClientCallBellAPI
}
function deleteFilesWordpressExceptLastHundred($log, $ftp_conn, $remote_folder): bool
{
// Obtener lista de archivos en la carpeta
$files = ftp_nlist($ftp_conn, $remote_folder);
if (is_array($files)) {
// Eliminar la ruta del directorio de los archivos
$files = array_map(function ($file) use ($remote_folder) {
return str_replace($remote_folder, '', $file);
}, $files);
// Obtener fechas de modificación
$filesWithTime = [];
foreach ($files as $file) {
$modifiedTime = ftp_mdtm($ftp_conn, $remote_folder . $file);
if ($modifiedTime != -1) {
$filesWithTime[$file] = $modifiedTime;
}
}
// Ordenar archivos por fecha de modificación, más recientes primero
arsort($filesWithTime);
// Obtener los archivos a eliminar (todos menos los 50 más recientes)
$filesToDelete = array_slice(array_keys($filesWithTime), 50);
// Eliminar archivos antiguos
foreach ($filesToDelete as $file) {
if (ftp_delete($ftp_conn, $remote_folder . $file)) {
$log->appendLog("Comprobante eliminado de Wordpress: " . $file . PHP_EOL);
} else {
$log->appendLog('Error al borrar comprobante' . $file . PHP_EOL);
}
}
$log->appendLog("Archivos eliminados" . PHP_EOL);
ftp_close($ftp_conn);
return true;
} else {
$log->appendLog("No se pudo obtener la lista de archivos de la carpeta FTP" . PHP_EOL);
ftp_close($ftp_conn);
return false;
}
}
/**

View File

@ -152,12 +152,12 @@ class Plugin
$this->logger->info('Referencia persnoalizada, Valor del monto: ' . $jsonData['amount'] . PHP_EOL);
$intentos = 0;
do {
if ($intentos > 1) {
sleep(2);
}
// if ($intentos > 1) {
// sleep(2);
// }
$url = $this->pluginOxxoNotifierFacade->createOxxoPaymentIntent($jsonData, $jsonData['amount']);
$intentos++;
} while (strpos($url, 'https') !== 0 && $intentos < 5);
} while (strpos($url, 'https') !== 0 && $intentos < 3); //Mientras la url no contenga https y el número de intentos sea menor a 3
} else {
$intentos = 0;
do {
@ -166,37 +166,47 @@ class Plugin
// }
$url = $this->pluginOxxoNotifierFacade->createOxxoPaymentIntent($jsonData);
$intentos++;
} while (strpos($url, 'https') !== 0 && $intentos < 5);
} while (strpos($url, 'https') !== 0 && $intentos < 3); //Mientras la url no contenga https y el número de intentos sea menor a 3
}
// Crear una respuesta en formato JSON
// $response = [
// 'event' => 'response.siip',
// 'status' => 'success',
// 'url' => $url
// ];
//Si la respuesta no contiene https
if (strpos($url, 'https') !== 0) {
$this->logger->error('Error al crear la referencia de OXXO: ' . $url);
$response = '{' .
'"error": "' . $url . '"' .
'}';
header('Content-Type: application/json');
echo $response;
break;
} else {
$response = '{' .
'"url": "' . $url . '"' .
'}';
$this->logger->debug('Reponse que se envía a CallBell: ' . $response);
// Enviar el encabezado de respuesta como JSON
header('Content-Type: application/json');
$response = '{' .
'"url": "' . $url . '"' .
'}';
// Enviar la respuesta en formato JSON
//echo json_encode($response);
echo $response;
$this->logger->debug('Reponse que se envía a CallBell: ' . $response);
// $json_codificado = json_encode($response);
// if (json_last_error() !== JSON_ERROR_NONE) {
// $this->logger->error('Error en la codificación JSON: ' . json_last_error_msg() . PHP_EOL);
break;
// }
}
//$this->logger->info('Se está enviando esta respuesta: ' . json_encode($json_codificado) . PHP_EOL);
// Enviar el encabezado de respuesta como JSON
header('Content-Type: application/json');
// Enviar la respuesta en formato JSON
//echo json_encode($response);
echo $response;
break;
// $json_codificado = json_encode($response);
// if (json_last_error() !== JSON_ERROR_NONE) {
// $this->logger->error('Error en la codificación JSON: ' . json_last_error_msg() . PHP_EOL);
// }
//$this->logger->info('Se está enviando esta respuesta: ' . json_encode($json_codificado) . PHP_EOL);
// Otros eventos relevantes
}
@ -409,7 +419,7 @@ class Plugin
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
if ($stripeTagExists && !$stripeTagExistsBefore) {
$this->logger->debug('La etiqueta STRIPE se agregó al cliente');
$this->logger->debug('La etiqueta STRIPE se agregará al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, true);
} else {
$this->logger->debug('La etiqueta STRIPE no se agregó al cliente');
@ -450,7 +460,7 @@ class Plugin
} else if ($notification->eventName === 'service.edit') {
$this->logger->debug('Se editó el servicio a un cliente' . PHP_EOL);
$this->notifierFacade->verifyServiceActionToDo($notification);
}else if ($notification->eventName === 'service.suspend') {
} else if ($notification->eventName === 'service.suspend') {
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
$this->notifierFacade->verifyServiceActionToDo($notification);
} else if ($notification->eventName === 'service.suspend_cancel') {
@ -486,6 +496,9 @@ class Plugin
} else if ($notification->eventName === 'invoice.draft_approved') {
$this->logger->debug('Aprobación de Factura' . PHP_EOL);
$this->notifierFacade->verifyInvoiceActionToDo($notification);
} else if ($notification->eventName === 'invoice.delete') {
$this->logger->debug('Eliminación de Factura' . PHP_EOL);
$this->notifierFacade->verifyInvoiceActionToDo($notification);
} else if ($notification->eventName === 'job.add') {
$this->logger->debug('Se ha agregado un nuevo trabajo' . PHP_EOL);

View File

@ -5,7 +5,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'c3824ba827c740086875251761ced485fb46b070',
'reference' => '398dcc462fec4278f6ce4ece52950adf089e35fe',
'name' => 'ucrm-plugins/sms-twilio',
'dev' => false,
),
@ -307,7 +307,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'c3824ba827c740086875251761ced485fb46b070',
'reference' => '398dcc462fec4278f6ce4ece52950adf089e35fe',
'dev_requirement' => false,
),
),