Compare commits

...

14 Commits

Author SHA1 Message Date
5425659428 antes de la limpieza y refacorización del código 2025-12-24 02:03:41 -06:00
0e37fd153f feat(stripe-sync): implementar resolución dinámica de métodos de pago y corregir validación API
- Se agregó 'getPaymentMethodIdByName' para buscar automáticamente el ID de "Transferencia bancaria" por nombre, asegurando portabilidad entre servidores UISP.
- Se implementó el manejo del webhook 'customer_cash_balance_transaction.created' para el registro automático de pagos fondeados.
- Fix: Se corrigió error 422 en la API de UCRM forzando el cast de 'clientId' a integer y 'methodId' a string (GUID).
- Se actualizó la documentación (README/CHANGELOG) con instrucciones de configuración de webhooks.
2025-12-23 14:50:57 -06:00
506615e911 Se agrega nuevo campo en los ajustes del plugin para agregar la API KEY del UNMS con el ID 'unmsApiToken' 2025-10-20 15:13:21 -06:00
2e21e09c2c Corrección de bug para poder utilizar la dirección ip del servicio Puppeteer dentro del código para referencias de OXXO PAGO, también se agregó un campo de configuración para el servicio de Puppeteer así como el código para utilizar dicho campo. 2025-08-01 08:47:33 -06:00
a0f286969a Actualización que ajusta los permisos de acceso a la API del CRM y NMS, además se realizaron cambios en el menú de ajustes para agregar el campo del toke de la api de nms y el campo del id del administrador para pagos en linea con Stripe, además se hizo el ajuste para el bug que no permitía realizar intenciones de pago cuando la cantidad trae signo de pesos 2025-07-31 15:23:49 -06:00
c9e2466353 Solucionado el bug que impedía regresar en la respuesta el monto total hacia el bot de CallBell, lo que hacía que mostrara siempre Cantidad cero, esto se realizó en el archivo AbstractOxxoOperationsFacade.php 2025-06-11 11:07:59 -06:00
8d8a1ec648 Modificaciones para enviar referencias de OXXO PAGO por medio de imagen 2025-06-10 02:46:31 -06:00
c73a51bbf2 Se modificó la información para el envío de notificaciones a llos instaladores en el flujo de trabajo para la desasignación de tareas que hacía que no se viera correctamente la información 2025-06-05 12:01:03 -06:00
5374054289 Cambios para quitar la hora de los mensajes que se envían a los clientes, corrección en la función getVaultCredentials para poder enviar el mensaje con la contraseña al instalador 2025-06-02 17:45:28 -06:00
e899945ca3 Versión 2.8.2 Rutina de envio de mensajes con plantillas de utilidad refactorizado, además tipo de pago 'applied_to_payment' en en analisis de webhooks de Stripe 2025-05-27 15:57:43 -06:00
e34b3ec0f8 Versión 2.8.0 README ACTUALIZADO 2025-05-21 21:39:50 -06:00
3a39a53da6 Versión 2.8.0 2025-05-21 21:31:09 -06:00
7cb26fe735 Se agregó una función que permite recuperar una contraseña de antena en función del ID del cliente y enviarla por mensaje de notificación al instalador para tareas que se le asignen, además de modificaron los IDs de plantillas de mensajes y se eleiminó el campo de dirección para la notioficación de tarea o servicio del instalador. 2025-05-10 11:21:43 -06:00
9a3af1f3d4 Version 2.7.1 con la opción de generar clabes interbancarias por medio de una etiqueta con nombre 'CREARCLABESTRIPE' y además permite crear las clabes de manera automática cuando el cliente pasa de potencial a cliente regular, se corrige un error que permitía generar de manera indefinida clabes para un cliente, además también se agregó la eliminación de la etiqueta 'CREARCLABESTRIPE' de manera automática cuando termina de generar una clabe interbancaria y cliente de Sripe y también valida si ya tiene uno creado con anterioridad para descartar solicitudes hechas a clientes que ya posean dichos datos 2025-03-25 07:46:08 -06:00
15 changed files with 4191 additions and 18816 deletions

5
.gitignore vendored
View File

@ -1,5 +1,8 @@
*.pdf
*.log
*.png
*.jpeg
.vscode/
*.zip
*.zip
vouchers_oxxo/
comprobantes/

61
CHANGELOG.md Normal file
View File

@ -0,0 +1,61 @@
# CHANGELOG - siip-whatsapp-notifications
## VERSIÓN 2.9.3 - 23-12-2025
### 🟢 Novedades
1⃣ Resolución dinámica del ID del método de pago ("Transferencia bancaria") mediante consulta a la API de UISP, mejorando la portabilidad del plugin entre distintos servidores.
2⃣ Implementación de registro de pago automático desde Webhook Stripe para eventos de tipo `customer_cash_balance_transaction.created` (Saldo aplicado).
### 🟡 Bugs Resueltos
1⃣ Se corrigió el error de validación de la API de UCRM (422) mediante el cast explícito de `clientId` a integer y el uso de `methodId` como string.
## VERSIÓN 2.9.2
### 🟡 Bugs Resueltos
1⃣ Se solucionó un bug que impedía obtener la contraseñas de la bóveda, ya que el response de la API cambió en la última actualización y la esstructura nueva impedía acceder al dato del password
## VERSIÓN 2.8.8
### 🟡 Bugs Resueltos
1⃣ Se solucionó un bug que impedía al BOT del CallBell mostrar el monto de la referencia de OXXO en el mensaje donde se le entrega el voucher al cliente, para el caso donde el cliente elegía crear su referencia con la CANTIDAD TOTAL.
## VERSIÓN 2.8.7
### 🟢 Novedades
1⃣ Ahora las referencias de **OXXO Pago** han cambiado, en lugar de enviarse la URL o link de pago al cliente será la imagen del código de barras y la información que aparece en el link directamente en el mensaje, de esta manera será más cómodo para el cliente tener la imagen en su chat a tener que abrir una URL o link externo.
### 🔵 Mejoras
1⃣ Se modificaron nodos del bot ***OXXO_BOT*** para poder adaptar esta actualización correctamente.
2⃣ Mejoras en el código fuente del flujo de trabajo para las referencias de OXXO PAGO.
## VERSIÓN 2.8.6
### 🔵 Mejoras
1⃣ Se modificó la información para el envío de notificaciones a llos instaladores en el flujo de trabajo para la desasignación de tareas que hacía que no se viera correctamente la información
## VERSIÓN 2.8.5
### 🔵 Mejoras
1⃣ Se modificó la información para el envío de notificaciones a los clientes para su visita técnica: ahora ya no se envían las horas en que serán las visitas, SOLO LA FECHA.
2⃣ Se crearon nuevas plantillas de tipo utilidad y se adaptaron al flujo de trabajo en el código.
### 🟡 Bugs Resueltos
1⃣ Se soluciono el bug que impedía enviar notificaciones a los instaladores para nuevas tareas.
## VERSIÓN 2.8.2
### 🟡 Bugs Resueltos
1⃣ No se enviaban las notificaciones de las tareas al instalador. Se cambió la plantilla de CallBell o WhatsApp con 3 variables en lugar de 8.
2⃣ Se agregó un nuevo tipo de pago ("applied_to_payment") en las propiedades de los Webhooks recibidos mediante Stripe por concepto de transferencias bancarias. Ya que sólo se revisaba el tipo de pago "funded" y eso hacía que no enviara los comprobantes de pago a los clientes para todos los casos.
## VERSIÓN 2.8.1
### 🟡 Bugs Resueltos
1⃣ No se enviaban las notificaciones de las tareas al instalador.
## VERSIÓN 2.8.0
### 🟢 Novedades
1⃣ Envío de contraseña de antena en el mensaje que se manda al instalador cuando se le asigna una tarea/servicio.
2⃣ Para clientes nuevos o que no tengan el campo personalizado de “Password Antena Cliente” al actualizarlos se les asignará ese campo o cuando se les actualice su servicio.
3⃣ Ahora se puede modificar tantas veces sea necesaria una tarea o servicio como su fecha o el instalador mientras la tarea permanezca en estado “Abiertos” SIN que se envíen notificaciones a los clientes o instaladores.
4⃣ Se agregó un prefijo al título de la tarea/servicio agendado cuando recién se da de alta para identificar que esta no ha sido notificada aún y se pueden realizar ajustes.
### 🔵 Mejoras
1⃣ Se modificó el flujo de trabajo para el envío de notificaciones a los clientes para su visita técnica, ahora son cuatro distintos tipos de flujo, anteriormente tres.
2⃣ Se reemplazaron algunas plantillas de mensajes de CallBell.
3⃣ Se renombró el uso de la etiqueta “CREARCLABESTRIPE” por “CREAR CLABE STRIPE” para un mejor entendimiento de su uso.
### 🟡 Bugs Resueltos
1⃣ Envío de notificaciones a los clientes y a los instaladores al CERRAR una tarea/servicio (Se mandaban mensajes de asignación y de visita del técnico ) haciendo no posible cerrar las tareas.

249
README.md
View File

@ -1,210 +1,73 @@
# 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
# SIIP - WhatsApp Notifications & Stripe Payments for UCRM
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
Este plugin es una solución integral para automatizar la comunicación con clientes y la gestión de pagos en UISP/UCRM. Actúa como puente entre el CRM, Stripe y la plataforma de mensajería CallBell.
## 🚀 Funcionalidades Principales
## Configuration
- **Notificaciones Dinámicas**: Envío automático de mensajes por WhatsApp para facturas, pagos, suspensiones y reactivaciones.
- **Gestión de Agenda (Jobs/Tasks)**: Notificación inteligente a técnicos e instaladores sobre nuevas tareas, reprogramaciones y desasignaciones.
- **Pagos con Stripe & OXXO**: Generación automatizada de referencias de OXXO con captura de pantalla (Puppeteer) y alojamiento en la nube (WordPress).
- **Sincronización CallBell**: Mantenimiento de la información del cliente actualizada en la plataforma de chat CallBell.
- **Resolución Dinámica**: Detección automática de métodos de pago y atributos personalizados para máxima portabilidad.
* Install the plugin into UCRM and enable it. I.e. download the plugin [zip file](https://github.com/Ubiquiti-App/UCRM-plugins/raw/master/plugins/sms-twilio/sms-twilio.zip) and upload it to UCRM in System > Plugins.
* Keep execution period at "don't execute automatically" - the plugin will react to webhook events.
* Set up with data which you obtain from [Twilio Console](https://twilio.com/console):
* Account SID
* Auth Token
* SMS number to send from
Note: there are two sets of credentials available, the default ("LIVE credentials") for actual use and [test credentials](https://www.twilio.com/console/project/settings) for development.
* Customize the texts you wish to send to a client when an event happens
* Each event has its own row
* Empty row means "do not send SMS for this"
* It is possible to replace predefined variables: `%%some.variable%%`, see full list below
* If a variable is not set for a client, it is replaced with an empty string
* Save the configuration
* Enable the plugin
* Add webhook (button next to Public URL)
* Select events about which to notify clients and save the webhook endpoint
---
## Usage
* In UCRM admin, go to System / Webhooks / Endpoints
* Click Test Endpoint
* Go to System / Plugins / 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
* In the log output, you'll see `Webhook test successful.`
## 🏗️ Arquitectura del Sistema
## Variables replaced
El plugin utiliza un patrón de diseño basado en **Facades** para desacoplar la lógica de despacho de los servicios externos.
These are loaded from UCRM API, and reflect the structure returned.
Client variables are replaced always; payment invoice and service only with the applicable events.
### Componentes Clave
### Client variables
- **Despachador Central (`Plugin.php`)**: Gestiona tanto los webhooks internos de UCRM como los externos (Stripe). Controla el estado de las notificaciones de tareas mediante prefijos en los títulos (`[NOTIFICACION-PENDIENTE]`).
- **Fachada de Mensajería (`AbstractMessageNotifierFacade`)**: Contiene la lógica de negocio para decidir qué notificar, cuándo y a quién.
- **Interfaz CallBell (`ClientCallBellAPI`)**: Encapsula las llamadas a la API de CallBell, manejando plantillas y mensajes multimedia.
- **Operaciones OXXO (`AbstractOxxoOperationsFacade`)**: Orquesta el flujo de pago: Stripe (Referencia) -> Puppeteer (Screenshot) -> WordPress (Hosting) -> CallBell (WhatsApp).
* `%%client.id%%` => 20
* `%%client.userIdent%%` => '18'
* `%%client.previousIsp%%` => ''
* `%%client.isLead%%` => false
* `%%client.clientType%%` => 1
* `%%client.companyName%%` => ''
* `%%client.companyRegistrationNumber%%` => ''
* `%%client.companyTaxId%%` => ''
* `%%client.companyWebsite%%` => ''
* `%%client.street1%%` => '2544 Hillview Drive'
* `%%client.street2%%` => ''
* `%%client.city%%` => 'San Jose'
* `%%client.countryId%%` => 249
* `%%client.stateId%%` => 5
* `%%client.zipCode%%` => '95113'
* `%%client.invoiceStreet1%%` => ''
* `%%client.invoiceStreet2%%` => ''
* `%%client.invoiceCity%%` => ''
* `%%client.invoiceStateId%%` => ''
* `%%client.invoiceCountryId%%` => ''
* `%%client.invoiceZipCode%%` => ''
* `%%client.invoiceAddressSameAsContact%%` => true
* `%%client.note%%` => ''
* `%%client.sendInvoiceByPost%%` => false
* `%%client.invoiceMaturityDays%%` => 14
* `%%client.stopServiceDue%%` => true
* `%%client.stopServiceDueDays%%` => 7
* `%%client.organizationId%%` => 1
* `%%client.tax1Id%%` => 1
* `%%client.tax2Id%%` => ''
* `%%client.tax3Id%%` => ''
* `%%client.registrationDate%%` => '2016-04-26 00:00'
* `%%client.companyContactFirstName%%` => ''
* `%%client.companyContactLastName%%` => ''
* `%%client.isActive%%` => false
* `%%client.firstName%%` => 'Tyson'
* `%%client.lastName%%` => 'Doe'
* `%%client.username%%` => 'tyson.doe@example.com'
* `%%client.accountBalance%%` => 0
* `%%client.accountCredit%%` => 0
* `%%client.accountOutstanding%%` => 0
* `%%client.currencyCode%%` => 'USD'
* `%%client.organizationName%%` => 'UBNT ISP'
* `%%client.invitationEmailSentDate%%` => ''
* `%%client.avatarColor%%` => '#e53935'
* `%%client.addressGpsLat%%` => 37.401482000001
* `%%client.addressGpsLon%%` => -121.966545
* `%%client.message%%` => 'This is an example message sent from the Messaging feature.'
---
### Invoice variables
* `%%invoice.id%%` => 4
* `%%invoice.clientId%%` => 20
* `%%invoice.number%%` => '2016050002'
* `%%invoice.createdDate%%` => '2016-05-03 00:00'
* `%%invoice.dueDate%%` => '2016-05-17 00:00'
* `%%invoice.emailSentDate%%` => '2018-08-24 00:00'
* `%%invoice.maturityDays%%` => 14
* `%%invoice.notes%%` => ''
* `%%invoice.adminNotes%%` => ''
* `%%invoice.subtotal%%` => 7.88
* `%%invoice.discount%%` => ''
* `%%invoice.discountLabel%%` => ''
* `%%invoice.total%%` => 7.88
* `%%invoice.amountPaid%%` => 7.88
* `%%invoice.currencyCode%%` => 'USD'
* `%%invoice.status%%` => 3
* `%%invoice.invoiceTemplateId%%` => 1
* `%%invoice.organizationName%%` => 'UBNT ISP'
* `%%invoice.organizationRegistrationNumber%%` => ''
* `%%invoice.organizationTaxId%%` => ''
* `%%invoice.organizationStreet1%%` => '2580 Orchard Parkway'
* `%%invoice.organizationStreet2%%` => ''
* `%%invoice.organizationCity%%` => 'New York'
* `%%invoice.organizationStateId%%` => 1
* `%%invoice.organizationCountryId%%` => 249
* `%%invoice.organizationZipCode%%` => '10017'
* `%%invoice.organizationBankAccountName%%` => ''
* `%%invoice.organizationBankAccountField1%%` => ''
* `%%invoice.organizationBankAccountField2%%` => ''
* `%%invoice.clientFirstName%%` => 'Tyson'
* `%%invoice.clientLastName%%` => 'Doe'
* `%%invoice.clientCompanyName%%` => ''
* `%%invoice.clientCompanyRegistrationNumber%%` => ''
* `%%invoice.clientCompanyTaxId%%` => ''
* `%%invoice.clientStreet1%%` => '685 Third Avenue'
* `%%invoice.clientStreet2%%` => ''
* `%%invoice.clientCity%%` => 'New York'
* `%%invoice.clientCountryId%%` => 249
* `%%invoice.clientStateId%%` => 5
* `%%invoice.clientZipCode%%` => '10017'
* `%%invoice.uncollectible%%` => false
## 🛠️ Requisitos e Integraciones
### Payment variables
* `%%payment.id%%` => 28
* `%%payment.clientId%%` => 20
* `%%payment.invoiceId%%` => ''
* `%%payment.method%%` => 2
* `%%payment.checkNumber%%` => ''
* `%%payment.createdDate%%` => '2018-08-24 11:36'
* `%%payment.amount%%` => 1
* `%%payment.currencyCode%%` => 'USD'
* `%%payment.note%%` => ''
* `%%payment.receiptSentDate%%` => ''
* `%%payment.providerName%%` => ''
* `%%payment.providerPaymentId%%` => ''
* `%%payment.providerPaymentTime%%` => ''
* `%%payment.creditAmount%%` => 0
* `%%payment.applyToInvoicesAutomatically%%` => false
Para un funcionamiento óptimo, el plugin requiere:
### Service variables
* `%%service.id%%` => 23
* `%%service.clientId%%` => 20
* `%%service.status%%` => 1
* `%%service.name%%` => 'Mini'
* `%%service.street1%%` => '622 Hide A Way Road'
* `%%service.street2%%` => ''
* `%%service.city%%` => 'San Jose'
* `%%service.countryId%%` => 249
* `%%service.stateId%%` => 5
* `%%service.zipCode%%` => '95135'
* `%%service.note%%` => ''
* `%%service.addressGpsLat%%` => 37.232849
* `%%service.addressGpsLon%%` => -121.752502
* `%%service.servicePlanId%%` => 1
* `%%service.servicePlanPeriodId%%` => 2
* `%%service.price%%` => 25
* `%%service.hasIndividualPrice%%` => false
* `%%service.totalPrice%%` => 25
* `%%service.currencyCode%%` => 'USD'
* `%%service.invoiceLabel%%` => ''
* `%%service.contractId%%` => ''
* `%%service.contractLengthType%%` => 1
* `%%service.minimumContractLengthMonths%%` => ''
* `%%service.activeFrom%%` => '2016-05-03T00:00:00+0000'
* `%%service.activeTo%%` => ''
* `%%service.contractEndDate%%` => ''
* `%%service.discountType%%` => 0
* `%%service.discountValue%%` => ''
* `%%service.discountInvoiceLabel%%` => ''
* `%%service.discountFrom%%` => ''
* `%%service.discountTo%%` => ''
* `%%service.tax1Id%%` => ''
* `%%service.tax2Id%%` => ''
* `%%service.tax3Id%%` => ''
* `%%service.invoicingStart%%` => '2016-05-03T00:00:00+0000'
* `%%service.invoicingPeriodType%%` => 1
* `%%service.invoicingPeriodStartDay%%` => 1
* `%%service.nextInvoicingDayAdjustment%%` => 0
* `%%service.invoicingProratedSeparately%%` => true
* `%%service.invoicingSeparately%%` => false
* `%%service.sendEmailsAutomatically%%` => false
* `%%service.useCreditAutomatically%%` => true
* `%%service.servicePlanName%%` => 'Mini'
* `%%service.servicePlanPrice%%` => 25
* `%%service.servicePlanPeriod%%` => 3
* `%%service.downloadSpeed%%` => 10
* `%%service.uploadSpeed%%` => 10
* `%%service.hasOutage%%` => true
* `%%service.stopReason%%` => 'Payments overdue'
1. **UISP/UCRM**: Versión compatible con el SDK de Ubiquiti.
2. **Stripe API**: Token y Webhooks configurados para transacciones en tiempo real.
3. **CallBell API**: Token de acceso para el envío de mensajes a través de plantillas de WhatsApp.
4. **Microservicio Puppeteer**: Un contenedor Docker con la API de Puppeteer para capturar vouchers.
5. **Hosting WordPress (FTP)**: Para alojar temporalmente las imágenes de los vouchers enviadas a los clientes.
---
## Developers
* This plugin is MIT-licensed and can be used by developers as a template for integrating with a different messaging solution:
* Create a new plugin based on this one
* Replace the TwilioNotifierFacade and any references to it with a different class which extends AbstractMessageNotifierFacade
* Update libraries in composer.json as needed
* Communicate with the remote system in the sendMessage() function
## 🏷️ Configuración de UCRM
### Contactos y Etiquetas
El plugin selecciona los números de destino basándose en el **Tipo de Contacto** definido en UCRM:
- `WhatsApp`: Recibe notificaciones y actualizaciones de datos.
- `WhatsNotifica`: Recibe solo notificaciones.
- `WhatsActualiza`: Utilizado solo para sincronizar datos con CallBell.
Read more about creating your own plugin in the [Developer documentation](https://github.com/Ubiquiti-App/UCRM-plugins/blob/master/docs/index.md).
### Atributos Personalizados
Es necesario configurar los siguientes atributos en UCRM:
- `stripeCustomerId`: ID del cliente en Stripe.
- `clabeInterbancaria`: CLABE personalizada para transferencias.
- `passwordAntenaCliente`: Almacena la contraseña del equipo del cliente (sincronizada con la bóveda).
---
## 🔄 Flujos de Trabajo Destacados
### 📅 Notificación de Visitas Técnicas
Cuando se asigna o reprograma una tarea:
1. El plugin detecta el cambio en el `assignedUserId` o la fecha.
2. Envía un mensaje al cliente con el nombre del técnico y la fecha (sin hora, por privacidad/logística).
3. Envía un mensaje al técnico con la dirección, ubicación en Google Maps y la contraseña de la antena obtenida de la bóveda de UISP.
### 💳 Registro de Pagos por Transferencia
1. Stripe envía un webhook de saldo aplicado (`customer_cash_balance_transaction.created`).
2. El plugin resuelve dinámicamente el ID del método "Transferencia bancaria".
3. Registra el pago automáticamente en UCRM vinculado al `clientId` en los metadatos.
---
## 📝 Registro de Cambios
Para consultar la evolución del proyecto, ver el archivo [CHANGELOG.md](./CHANGELOG.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@ -1 +1 @@
{"ipserver":"172.16.5.134","apitoken":"6abef18c-783d-4dd0-b530-be6e6a7bbd1d","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","unmsApiToken":null,"hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","cashPaymentMethodId":false,"courtesyPaymentMethodId":false,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"notificationTypeText":false,"installersDataWhatsApp":"{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"Angel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n }\r\n ]\r\n}","debugMode":true,"logging_level":true}
{"ipserver":"172.16.5.134","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"079c28f5-888c-457d-bd7a-0a4202590f75","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"3000","idPaymentAdminCRM":"1180","cashPaymentMethodId":false,"courtesyPaymentMethodId":false,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"notificationTypeText":true,"installersDataWhatsApp":"{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"Ángel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1131,\r\n \"nombre\": \"Gricelda Avalos\",\r\n \"whatsapp\": \"4181817609\"\r\n }\r\n ]\r\n}","debugMode":true,"logging_level":true}

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
"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 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.7.0",
"version": "2.9.2",
"unmsVersionCompliancy": {
"min": "2.1.0",
"max": null
@ -23,10 +23,17 @@
{
"key": "apitoken",
"label": "Token de la API UCRM",
"description": "Token de autenticación para el uso de la API del sistema UISP UCRM. Contiene 36 caracteres, ejemplo: 3d3fa6c9-e268-6e8b-b4d5-aae394d99d7d",
"description": "Token de autenticación necesario para el uso de la API del sistema UISP UCRM, se utiliza para gestionar cualquier información de los clientes. Contiene 64 caracteres y se genera desde el módulo de Ajustes del UISP CRM en la opción de 'Seguridad' y en la sección de 'Claves app'.",
"required": 1,
"type": "text"
},
{
"key": "unmsApiToken",
"label": "Token de la API UNMS",
"description": "Token de autenticación necesario para el uso de la API del sistema UISP UNMS, se utiliza para gestionar información de antenas u otros dispositivos de red. Contiene 34 caracteres y se genera desde el módulo de Ajustes del UISP Network en la opción de 'Usuarios' y en apartado de 'API tokens'.",
"required": 0,
"type": "text"
},
{
"key": "tokencallbell",
"label": "Token de la API de CallBell",
@ -37,17 +44,10 @@
{
"key": "tokenstripe",
"label": "Token de la API de Stripe",
"description": "Token de autenticación para el uso de la API de Stripe que maneja las funciones realacionadas con los pagos en línea. ",
"description": "Token de autenticación para el uso de la API de Stripe que maneja las funciones realacionadas con los pagos en línea (Transferencia y OXXO PAGO). ",
"required": 1,
"type": "text"
},
{
"key": "unmsApiToken",
"label": "Token de la API UNMS",
"description": "Token API creado para este plugin en la seccion Network de UNMS, solo necesario cuando se utiliza UNMS v1",
"required": 0,
"type": "text"
},
{
"key": "hostServerFTP",
"label": "IP o dominio del servidor FTP",
@ -58,14 +58,35 @@
{
"key": "usernameServerFTP",
"label": "Usuario FTP",
"description": "Nombre de usuario para inicio de sesión el servidor FTP",
"description": "Nombre de usuario para inicio de sesión el servidor FTP, necesario para la carga de comprobantes de pago del sistema y su posterior envío",
"required": 1,
"type": "text"
},
{
"key": "passServerFTP",
"label": "Password FTP",
"description": "Contraseña para inicio de sesión en el servidor FTP",
"description": "Contraseña para inicio de sesión en el servidor FTP, necesario para la carga de comprobantes de pago del sistema y su posterior envío",
"required": 1,
"type": "text"
},
{
"key": "ipPuppeteer",
"label": "Dirección IP del servicio de Puppeteer",
"description": "Dirección IP del contenedor docker que ejecuta el servicio de Puppeteer para la generación de comprobantes de pago en formato imagen PNG",
"required": 1,
"type": "text"
},
{
"key": "portPuppeteer",
"label": "Puerto del servicio de Puppeteer",
"description": "Puerto del contenedor docker que ejecuta el servicio de Puppeteer, por defecto es el 3000",
"required": 1,
"type": "text"
},
{
"key": "idPaymentAdminCRM",
"label": "ID del usuario para pagos en línea",
"description": "ID del usuario administrador del CRM asigando para realizar pagos en línea con Stripe. Todos los pagos que llegan desde Stripe se asignan a este usuario. Se recomienda crear un usuario exclusivo para este fin.",
"required": 1,
"type": "text"
},
@ -181,18 +202,6 @@
"label": "Borrar comprobantes Wordpress",
"type": "admin",
"target": "iframe"
},
{
"key": "Reports",
"label": "Generador de Clabes CBM en Stripe",
"type": "admin",
"target": "iframe"
},
{
"key": "Reports",
"label": "Obtener datos del Network de Cliente",
"type": "admin",
"target": "iframe"
}
],
"supportsWebhookEvents": true

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@ use SmsNotifier\Service\SmsNumberProvider;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
/*
@ -60,27 +62,30 @@ abstract class AbstractOxxoOperationsFacade
* @throws \Stripe\Exception\ApiErrorException
* @throws Exception
*/
public function createOxxoPaymentIntent($event_json, $amount = null): array
public function createOxxoPaymentIntent($event_json, $amount = null): array
{
//declarar un array asociativo de strings pero no asignarle nada aun
$arrayOxxoPayment = array();
$integerAmount = $amount;
$this->logger->info("Creando referencia del cliente para OXXO: " . PHP_EOL);
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$configManager = PluginConfigManager::create();
$config = $configManager->loadConfig();
$StripeToken = $config['tokenstripe'];
$IPServer = $config['ipserver'];
$tokenCRM = $config['apitoken'];
$ipPuppeteer = $config['ipPuppeteer'];
$portPuppeteer = $config['portPuppeteer'];
$baseUri = 'https://' . $IPServer . '/crm/api/v1.0/';
$this->ucrmApi = UcrmApi::create();
$currentUserAdmin = $this->ucrmApi->get('users/admins', []);
$clientID = $event_json['client_id'];
$stripeCustomerId = null;
$clientEmail = '';
$clientFullName = '';
$clientGuzzleHttp = new Client([
'base_uri' => $baseUri,
'headers' => [
@ -90,7 +95,7 @@ abstract class AbstractOxxoOperationsFacade
'verify' => false,
'timeout' => 5, // Timeout de 5 segundos
]);
try {
$response = $clientGuzzleHttp->request('GET', "clients/" . $clientID);
$arrayClientCRM = json_decode($response->getBody()->getContents(), true);
@ -107,34 +112,39 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
//timeout
if ($e->getCode() === 408) {
$this->logger->error("Timeout al obtener el cliente en CRM: " . $clientID . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
$arrayOxxoPayment['oxxo_reference'] = '';
$arrayOxxoPayment['url'] = '';
$arrayOxxoPayment['error'] = 'errorTimeoutGetClient';
$arrayOxxoPayment['failDescription'] = 'Timeout al obtener el cliente en CRM: ' . $clientID;
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
$this->logger->error("Error al obtener el cliente en CRM (Error {$e->getCode()}): " . $e->getMessage() . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
$arrayOxxoPayment['oxxo_reference'] = '';
$arrayOxxoPayment['url'] = '';
$arrayOxxoPayment['error'] = 'errorGetClient';
$arrayOxxoPayment['failDescription'] = 'Error al obtener el cliente en CRM: ' . $clientID;
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
// Obtener email del cliente
foreach ($arrayClientCRM['contacts'] as $contact) {
if (!empty($contact['email'])) {
@ -144,7 +154,7 @@ abstract class AbstractOxxoOperationsFacade
$clientEmail = 'siip8873@gmail.com'; // Default
}
}
try {
// Obtener stripeCustomerId
foreach ($arrayClientCRM['attributes'] as $attribute) {
@ -153,7 +163,7 @@ abstract class AbstractOxxoOperationsFacade
break;
}
}
$this->logger->info("Stripe Customer ID obtenido: " . $stripeCustomerId . PHP_EOL);
} catch (Exception $e) {
$this->logger->error("Error al obtener el Customer ID de Stripe (Error {$e->getCode()}): " . $e->getMessage() . PHP_EOL);
@ -164,28 +174,33 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
if ($amount === null) {
$amount = abs($arrayClientCRM['accountOutstanding']);
$amount = abs($arrayClientCRM['accountOutstanding']); //Monto adeudado del cliente
} else {
$this->logger->info("Monto proporcionado directamente: $amount " . PHP_EOL);
//la variable $amount debe ser numérica pero en ocasiones la solicitud puede traer signos o alguna letra ingresada mal por el cliente, se debe analizar para limpiar cualquier caracter que no sea numérico
if(!is_numeric($amount)) {
$amount = preg_replace('/[^\d.]/', '', $amount); // Eliminar todo lo que no sea dígito o punto decimal
}
$this->logger->debug("Monto proporcionado directamente: $amount " . PHP_EOL);
}
if ($amount > 10) {
$amount = intval($amount * 100);
$amountInCents = intval($amount * 100); // Convertir a centavos
try {
$this->logger->info("Creando referencia en Stripe por $amount para el cliente $stripeCustomerId" . PHP_EOL);
$this->logger->debug("Creando referencia en Stripe por $amount para el cliente $stripeCustomerId" . PHP_EOL);
$guzzleClient = new Client([
'timeout' => 5,
'timeout' => 5, // Timeout de 5 segundos
]);
$response = $guzzleClient->post('https://api.stripe.com/v1/payment_intents', [
'auth' => [$StripeToken, ''],
'form_params' => [
'amount' => $amount,
'amount' => $amountInCents,
'currency' => 'mxn',
'payment_method_types' => ['customer_balance', 'card', 'oxxo'],
'description' => 'Pago de servicio de SIIP Internet',
@ -204,9 +219,9 @@ abstract class AbstractOxxoOperationsFacade
],
]
]);
$paymentIntent = json_decode($response->getBody()->getContents(), true);
} catch (\Stripe\Exception\ApiConnectionException $e) {
$this->logger->error("Error de conexión con Stripe (Error {$e->getCode()}): " . $e->getMessage() . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
@ -216,9 +231,10 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = 'Error de conexión con Stripe: ' . $clientID;
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
} catch (\Stripe\Exception\ApiErrorException $e) {
$this->logger->error("Error de la API de Stripe: " . $e->getMessage() . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
@ -228,27 +244,36 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = 'Error de la API de Stripe: ' . $clientID;
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
} catch (Exception $e) {
$this->logger->error("Error inesperado al crear PaymentIntent (Error {$e->getCode()}): " . $e->getMessage() . PHP_EOL);
//si e->getMessage incluye el mensaje: You must provide a customer when creating or updating a PaymentIntent with a `customer_ba (truncated...) declarar una variable y ahi poner "Este cliente no tiene cuenta de Stripe"
if ($e->getMessage() == 'You must provide a customer when creating or updating a PaymentIntent with a `customer_ba (truncated...)') {
$this->logger->error("Este cliente no tiene cuenta de Stripe" . PHP_EOL);
$arrayOxxoPayment['failDescription'] = 'Este cliente no tiene cuenta de Stripe: ' . $clientID;
} else {
$this->logger->error("Error inesperado al crear PaymentIntent: " . $e->getMessage() . PHP_EOL);
$arrayOxxoPayment['failDescription'] = 'Error inesperado al crear PaymentIntent: ' . $clientID;
}
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
$arrayOxxoPayment['oxxo_reference'] = '';
$arrayOxxoPayment['url'] = '';
$arrayOxxoPayment['error'] = 'errorCreatePaymentIntent';
$arrayOxxoPayment['failDescription'] = 'Error inesperado al crear PaymentIntent: ' . $clientID;
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
try {
$firstName = isset($arrayClientCRM['firstName']) ? trim($arrayClientCRM['firstName']) : '';
$lastName = isset($arrayClientCRM['lastName']) ? trim($arrayClientCRM['lastName']) : '';
if (strlen($firstName) < 2 || strlen($lastName) < 2) {
$this->logger->error("Nombre/apellido inválido: ' . $firstName . ' ' . $lastName" . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
@ -258,11 +283,12 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = "Nombre/apellido inválido: ' . $firstName . ' ' . $lastName";
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
$responsePaymentMethod = $guzzleClient->post('https://api.stripe.com/v1/payment_methods', [
'auth' => [$StripeToken, ''],
'form_params' => [
@ -273,18 +299,18 @@ abstract class AbstractOxxoOperationsFacade
],
]
]);
$paymentMethod = json_decode($responsePaymentMethod->getBody()->getContents(), true);
$responseConfirmPaymentIntent = $guzzleClient->post('https://api.stripe.com/v1/payment_intents/' . $paymentIntent['id'] . '/confirm', [
'auth' => [$StripeToken, ''],
'form_params' => [
'payment_method' => $paymentMethod['id'],
]
]);
$paymentIntentConfirm = json_decode($responseConfirmPaymentIntent->getBody()->getContents(), true);
} catch (Exception $e) {
$this->logger->error("Error al confirmar PaymentIntent: " . $e->getMessage() . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
@ -294,27 +320,112 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = 'Error al confirmar PaymentIntent: ' . $e->getMessage();
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
if (!empty($paymentIntentConfirm['next_action']) && isset($paymentIntentConfirm['next_action']['oxxo_display_details'])) {
$oxxoPayment = $paymentIntentConfirm['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);
$this->captureScreenshot($oxxo_receipt_url);
//$this->captureScreenshot($oxxo_receipt_url);
//devolver un array con los campos de url de oxxo, descripción de la falla, clientID y amount
$arrayOxxoPayment['oxxo_reference'] = $oxxo_reference;
$arrayOxxoPayment['url'] = $oxxo_receipt_url;
$arrayOxxoPayment['error'] = '';
$arrayOxxoPayment['failDescription'] = '';
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['failDescription'] = '';
$arrayOxxoPayment['error'] = '';
$arrayOxxoPayment['amount'] = $amount;
$this->logger->info("Referencia OXXO creada correctamente." . PHP_EOL);
// Configuración de la API de tu servicio Docker de Puppeteer
$api_url = 'http://'.$ipPuppeteer.':'.$portPuppeteer.'/screenshot'; // Asegúrate que esta URL sea accesible
// --- Datos para la petición ---
$request_data = [
'url' => $oxxo_receipt_url, // URL del recibo de OXXO
// Opcional: Si quieres recortar la imagen, descomenta y ajusta el 'clip'
'clip' => [
'x' => 325,
'y' => 30,
'width' => 550,
'height' => 550
]
];
// Nombre del archivo donde se guardará la imagen
$clientFullNameWithoutSpaces = str_replace(' ', '_', $clientFullName); // Reemplazar espacios por guiones bajos
$voucherFileName = 'voucher_'.$clientFullNameWithoutSpaces.'_' . time() . '.jpeg'; // Usamos .jpeg por defecto
$output_filename = __DIR__ . '/../../vouchers_oxxo/' . $voucherFileName; // Usamos .jpeg por defecto
$output_filename = realpath(__DIR__ . '/../../vouchers_oxxo') . '/'. $voucherFileName; // Mejor: ruta absoluta y verifica que el directorio exista
//$output_filename = '/path/to/vouchers_oxxo/voucher_' . time() . '.jpeg'; // Mejor aún: ruta absoluta y fija, si es posible
$guzzleClient = new Client([
'timeout' => 5, // Timeout de 5 segundos
]);
// Descargar el recibo de OXXO
try {
// --- Realizar la petición POST ---
$response = $guzzleClient->post($api_url, [
'json' => $request_data, // Guzzle codifica automáticamente el array a JSON y establece Content-Type: application/json
'headers' => [
'Accept' => 'image/jpeg, image/png', // Indicar que esperamos una imagen
],
// Opcional: para depuración si necesitas ver el cuerpo de la respuesta en caso de error
// 'http_errors' => false // Desactiva las excepciones para códigos de estado 4xx/5xx y maneja la respuesta manualmente
]);
// --- Procesar la respuesta ---
$statusCode = $response->getStatusCode();
$contentType = $response->getHeaderLine('Content-Type');
$this->logger->debug("Status Code: " . $statusCode);
$this->logger->debug("Content Type: " . $contentType);
if ($statusCode === 200 && str_contains($contentType, 'image/')) {
// La respuesta es una imagen y el código de estado es 200 OK
$image_content = $response->getBody()->getContents();
// *** VERIFICACIÓN IMPORTANTE: ***
if (file_put_contents($output_filename, $image_content)) {
$this->logger->debug("¡Imagen guardada exitosamente en: " . $output_filename);
$url_file = $this->UploadVoucherToWordpressByImageFileName($voucherFileName); //Carga del comprobante PDF a Wordpress para su posterior envío
$arrayOxxoPayment['voucher_image_url'] = $url_file; // Agregar la URL del archivo de imagen al array
} else {
$this->logger->error("Error: No se pudo guardar la imagen en " . $output_filename);
$this->logger->error("Ruta del archivo: " . $output_filename); // Agregado para depuración
$this->logger->error("Directorio del script: " . __DIR__); // Agregado para depuración
$this->logger->error("Error de PHP: " . error_get_last()['message']); // Agregado para depuración
}
} else {
// No es una imagen o hubo un error en el servicio
$this->logger->debug("Error: La respuesta no es una imagen o el código de estado no es 200 OK.");
$this->logger->debug("Cuerpo de la respuesta: " . $response->getBody()->getContents());
}
} catch (RequestException $e) {
// Manejo de errores de red o del servidor (ej. timeouts, 4xx, 5xx si http_errors no es false)
$this->logger->error("Error en la petición: " . $e->getMessage());
if ($e->hasResponse()) {
$this->logger->error("Cuerpo de la respuesta de error: " . $e->getResponse()->getBody()->getContents());
$this->logger->error("Código de estado HTTP de error: " . $e->getResponse()->getStatusCode());
}
} catch (Exception $e) {
// Otros errores inesperados
$this->logger->error("Error inesperado: " . $e->getMessage());
}
return $arrayOxxoPayment;
} else {
$this->logger->info("El PaymentIntent no tiene detalles de OXXO disponibles. Estado: " . $paymentIntentConfirm['status'] . PHP_EOL);
@ -325,11 +436,12 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = 'El PaymentIntent no tiene detalles de OXXO disponibles. Estado: ' . $paymentIntentConfirm['status'];
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
} else {
$this->logger->info("Este cliente no tiene adeudos." . PHP_EOL);
//devolver un array con los campos del codigo error, descripción de la falla, clientID y amount
@ -339,7 +451,8 @@ abstract class AbstractOxxoOperationsFacade
$arrayOxxoPayment['failDescription'] = 'Este cliente no tiene adeudos.';
$arrayOxxoPayment['clientID'] = $clientID;
$arrayOxxoPayment['clientFullName'] = $clientFullName;
$arrayOxxoPayment['amount'] = $integerAmount;
$arrayOxxoPayment['amount'] = $amount;
$arrayOxxoPayment['voucher_image_url'] = '';
return $arrayOxxoPayment;
}
}
@ -362,7 +475,7 @@ abstract class AbstractOxxoOperationsFacade
function validarEmail($email)
{
$this->logger->info('SE VALIDA EL EMAIL!!! ' . PHP_EOL);
$this->logger->debug('SE VALIDA EL EMAIL!!! ' . PHP_EOL);
// Utilizar la función filter_var con el filtro FILTER_VALIDATE_EMAIL para validar el email
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Si el email es válido, devolver el email
@ -481,37 +594,145 @@ abstract class AbstractOxxoOperationsFacade
}
/**
* Función para capturar una pantalla de una URL usando el servicio Puppeteer.
*
* @param string $url La URL de la página web que se desea capturar.
* @return string La ruta del archivo de la captura de pantalla.
*/
function captureScreenshot($url) {
// URL del servicio Puppeteer
$puppeteerUrl = 'http://172.16.5.134:4000';
// Datos a enviar
$data = http_build_query(['url' => $url]);
function UploadVoucherToWordpressByImageFileName($imageFileName): string
{
// Configurar la solicitud POST
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => $data,
],
];
$log = PluginLogManager::create(); //Initialize Logger
$configManager = PluginConfigManager::create();
$config = $configManager->loadConfig();
// Ejecutar la solicitud
$context = stream_context_create($options);
$response = file_get_contents($puppeteerUrl, false, $context);
// Configuración de conexión FTP
$ftp_server = $config['hostServerFTP'];
$ftp_username = $config['usernameServerFTP'];
$ftp_password = $config['passServerFTP'];
$remote_folder = "/public_html/wp/wp-content/uploads/vouchers_oxxo/";
if ($response === false) {
throw new Exception("Error al comunicarse con el servicio Puppeteer.");
$log->appendLog("Subiendo voucher a worpdpress " . PHP_EOL);
// 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/vouchers_oxxo/" . $imageFileName;
$file_to_upload = __DIR__ . '/../../vouchers_oxxo/' . $imageFileName;
$url = 'https://siip.mx/wp/wp-content/uploads/vouchers_oxxo/' . $imageFileName;
$log->appendLog("file_to_upload: " . $file_to_upload . PHP_EOL);
// 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);
$this->deleteFilesWordpressExceptLastHundred($log, $ftp_conn, $remote_file);
// Cerrar conexión FTP
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);
ftp_close($ftp_conn);
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);
// // 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 $url;
// } else {
// $log->appendLog("No se pudo obtener la lista de archivos de la carpeta FTP" . PHP_EOL);
// ftp_close($ftp_conn);
// return $url;
// }
} else {
$log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
return '';
}
}
return trim($response);
}
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("Voucher eliminado de Wordpress: " . $file . PHP_EOL);
} else {
$log->appendLog('Error al borrar voucher' . $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

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SmsNotifier\Facade;
use Attribute;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use SmsNotifier\Data\NotificationData;
@ -57,11 +58,12 @@ abstract class AbstractStripeOperationsFacade
*/
public function createPaymentIntent($event_json)
{
$this->logger->info("Evento recibido: " . json_encode($event_json) . PHP_EOL);
$this->logger->info("Iniciando creación de PaymentIntent en Stripe." . PHP_EOL);
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$StripeToken = $config['tokenstripe'];
$idPaymentAdmin = $config['idPaymentAdminCRM']; //ID del administrador que crea el PaymentIntent
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada para la API de Stripe
// Asegurarse de que 'customer' esté presente en la estructura del evento
@ -81,13 +83,23 @@ abstract class AbstractStripeOperationsFacade
$this->logger->info("Error: net_amount not found in event data." . PHP_EOL);
return;
}
$amount = $event_json['data']['object']['net_amount'];
//convertir en positivo
if ($event_json['data']['object']['net_amount'] < 0) {
$this->logger->warning("Error: net_amount es negativo." . PHP_EOL);
return;
}
//imprimir la cantidad del PaymentIntent
$this->logger->info("Cantidad del PaymentIntent: " . $amount . PHP_EOL);
try {
// Obtener información del cliente desde Stripe
$stripeQuery = $stripe->customers->retrieve($customerId, []);
$UCRM_clientID = $stripeQuery['metadata']['ucrm_client_id'];
$UCRM_clientID = $stripeQuery['metadata']['ucrm_client_id']; // ID del cliente en Ubiquiti UISP
// Obtener información del administrador actual
$this->ucrmApi = UcrmApi::create();
@ -111,7 +123,7 @@ abstract class AbstractStripeOperationsFacade
'clientId' => $UCRM_clientID, // ID del cliente en Ubiquiti
'createdBy' => 'UCRM',
'paymentType' => 'card.one_time',
'signedInAdminId' => $currentUserAdmin[0]['id'],
'signedInAdminId' => $idPaymentAdmin, // ID del administrador que crea el PaymentIntent
'tipoPago' => 'Transferencia Bancaria'
],
]);
@ -123,6 +135,59 @@ abstract class AbstractStripeOperationsFacade
}
}
public function registerPaymentFromWebhook($event_json)
{
$this->logger->info("Procesando pago funded desde webhook..." . PHP_EOL);
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$StripeToken = $config['tokenstripe'];
$stripe = new \Stripe\StripeClient($StripeToken);
$data = $event_json['data']['object'];
if (isset($data['applied_to_payment']['payment_intent'])) {
$piId = $data['applied_to_payment']['payment_intent'];
try {
$pi = $stripe->paymentIntents->retrieve($piId);
$clientId = $pi->metadata->clientId ?? null;
$amount = abs($data['net_amount']) / 100;
if ($clientId) {
$this->ucrmApi = UcrmApi::create();
// Dynamic lookup for payment method ID
$methodId = null;
$methods = $this->ucrmApi->get('payment-methods');
foreach ($methods as $method) {
if ($method['name'] === 'Transferencia bancaria') {
$methodId = $method['id'];
break;
}
}
if (!$methodId) {
$this->logger->error("Error registrando pago: No se encontró el método 'Transferencia bancaria'");
return;
}
$this->ucrmApi->post('payments', [
'clientId' => (int)$clientId,
'amount' => $amount,
'currencyCode' => strtoupper($pi->currency),
'methodId' => $methodId,
'note' => "Pago via Webhook Stripe (Saldo Aplicado) - PI: $piId",
'createdDate' => date('c'),
]);
$this->logger->info("Pago registrado en UCRM para el cliente $clientId");
} else {
$this->logger->warning("No se encontró clientId en metadata del PaymentIntent $piId");
}
} catch (\Exception $e) {
$this->logger->error("Error registrando pago: " . $e->getMessage());
}
}
}
/*
* Creates the Stripe Customer
@ -136,19 +201,30 @@ abstract class AbstractStripeOperationsFacade
$UCRMAPIToken = $config['apitoken'];
$StripeToken = $config['tokenstripe'];
$baseUri = 'https://' . $IPServer . '/crm/api/v1.0/'; //endpoint de la API REST del CRM
$this->ucrmApi = UcrmApi::create();
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada en modo prueba para la API de Stripe
//$stripe = new \Stripe\StripeClient('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'); //Token de clave privada en modo prodcucción para la API de Stripe
//valor de $notificationData en json
$this->logger->info("Valor de notificationData: " . json_encode($notificationData) . PHP_EOL);
//$this->logger->info("Ya dentro del metodo Create Stripe Client " . PHP_EOL);
//$this->sendWhatsApp('Hola Dany');
//ejemplo de notificationData: {"uuid":"0be6fee6-db1d-4ab5-a52c-2ee87b04315e","changeType":"edit","entity":"client","entityId":170,"message":null,"clientId":170,"eventName":"client.edit","clientData":{"id":170,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"San Luis 34","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"San Luis 34, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-03-24T00:00:00-0600","leadConvertedAt":"2025-03-24T15:01:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Charicuas","lastName":"Quinero","username":null,"contacts":[{"id":176,"clientId":170,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":192,"clientId":170,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_S0T9BJ4dO0p0A3","clientZoneVisible":true},{"id":193,"clientId":170,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180302040274015","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#ef5350","addressGpsLat":22.00854045,"addressGpsLon":-99.0272544,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"serviceData":null,"invoiceData":null,"paymentData":null}
$this->ucrmApi = UcrmApi::create();
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', ['name' => 'STRIPE']);
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
//obtener el id del cliente
$clientId = $notificationData->clientData['id'];
//si en attributes del cliente encuentra el custom field de Stripe Customer ID entonces no se vuelve a crear el cliente en Stripe
$attributes = $notificationData->clientData['attributes'];
$this->logger->info("Valor de attributes: " . json_encode($attributes) . PHP_EOL);
$stripeCustomerId = null;
foreach ($attributes as $attribute) {
if ($attribute['key'] === 'stripeCustomerId') {
$stripeCustomerId = $attribute['value'];
break;
}
}
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']);//Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
@ -171,6 +247,61 @@ abstract class AbstractStripeOperationsFacade
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
if ($stripeCustomerId) {
$this->logger->info("El cliente ya tiene un Stripe Customer ID: " . $stripeCustomerId . PHP_EOL);
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', []);
//ejemplo de respuesta $tagsIds: [{"id":4,"name":"EQUIPO A CREDITO","colorBackground":"#fed74a","colorText":"#444"},{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}]
//obtener el ID de la etiqueta o tag llamada "CREAR CLABE STRIPE" y asiganarla a una variable $tagCrearClabeStripe basandose en el ejemplo de respuesta anterior
$tagCrearClabeStripe = null;
foreach ($tagsIds as $tag) {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$tagCrearClabeStripe = $tag['id'];
// $this->logger->info("ID de la etiqueta 'CREAR CLABE STRIPE': " . $tagCrearClabeStripe . PHP_EOL);
break;
}
}
if (!$tagCrearClabeStripe) {
$this->logger->info("No se encontró la etiqueta 'CREAR CLABE STRIPE'." . PHP_EOL);
return;
}
$this->ucrmApi->patch("clients/$clientId/remove-tag/" . $tagCrearClabeStripe);
$this->logger->info("Se eliminó la etiqueta 'CREAR CLABE STRIPE' del cliente." . PHP_EOL);
}
return;
}
//$this->logger->info("Ya dentro del metodo Create Stripe Client " . PHP_EOL);
//$this->sendWhatsApp('Hola Dany');
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', []);
//ejemplo de respuesta $tagsIds: [{"id":4,"name":"EQUIPO A CREDITO","colorBackground":"#fed74a","colorText":"#444"},{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}]
//obtener el ID de la etiqueta o tag llamada "CREARCLABESTRIPE" y asiganarla a una variable $tagCrearClabeStripe basandose en el ejemplo de respuesta anterior
$tagCrearClabeStripe = null;
foreach ($tagsIds as $tag) { //revisamos los tags del cliente y si encuentra el tag de "CREARCLABESTRIPE" entonces lo asigna a la variable $tagCrearClabeStripe el valor de id
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$tagCrearClabeStripe = $tag['id'];
// $this->logger->info("ID de la etiqueta 'CREAR CLABE STRIPE': " . $tagCrearClabeStripe . PHP_EOL);
break;
}
}
if (!$tagCrearClabeStripe) {
$this->logger->info("No se encontró la etiqueta 'CREAR CLABE STRIPE'." . PHP_EOL);
return;
}
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
$this->ucrmApi->patch("clients/$clientId/remove-tag/" . $tagCrearClabeStripe);
$this->logger->info("Se eliminó la etiqueta 'CREAR CLABE STRIPE' del cliente." . PHP_EOL);
return;
}
@ -189,26 +320,8 @@ abstract class AbstractStripeOperationsFacade
}
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
// Acceder a "isLead" de forma segura
// $isLead = $dataObject->isLead ?? null;
// $this->logger->info("El valor de 'isLead' es: " . ($isLead ? "true" : "false") . PHP_EOL);
// if ($isLead === true) {
// $this->logger->info("El cliente es un lead, no se procesará" . PHP_EOL);
// return;
// } else {
// $this->logger->info("El cliente NO es un lead, se procesará" . PHP_EOL);
// $this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
// }
}
// /**
// * implement in subclass with the specific messaging provider
// * @see TwilioNotifierFacade::sendWhatsApp()
@ -220,6 +333,7 @@ abstract class AbstractStripeOperationsFacade
function createCustomerStripe($notificationData, $stripe, $baseUri, $token): bool
{
$this->ucrmApi = UcrmApi::create();
$this->logger->info("Creando el Customer Stripe" . PHP_EOL);
@ -288,7 +402,7 @@ abstract class AbstractStripeOperationsFacade
//validar si se creo el cliente con la API de Stripe $stripe->customers->create
if (!isset($result['id'])) { //si no existe el ID del cliente en la respuesta de Stripe entonces se manda un mensaje de error
$this->logger->info('Error al crear el cliente en Stripe: ' . json_encode($result) . PHP_EOL);
}
@ -343,22 +457,33 @@ abstract class AbstractStripeOperationsFacade
]
}'; //JSON para hacer patch de los custom fields del cliente en el UISCP CRM, Campo para el Stripe Customer ID y la Clabe interbancaria
// $json_data_patch = '{
// "attributes": [
// {
// "value": "' . $stripe_customer_id . '",
// "customAttributeId": 29
// },
// {
// "value": "' . $clabe . '",
// "customAttributeId": 30
// }
// ]
// }'; //JSON para hacer patch de los custom fields del cliente en el UISCP CRM, Campo para el Stripe Customer ID y la Clabe interbancaria
//valor de $json_data_patch en json
//$this->logger->info("Valor de json_data_patch: " . $json_data_patch . PHP_EOL);
// try{
// $responsepatchCRM= $this->ucrmApi->patch("clients/$ucrm_client_id", [
// "attributes" => [
// [
// "value" => $stripe_customer_id,
// "customAttributeId" => $stripeID
// ],
// [
// "value" => $clabeInterbancaria,
// "customAttributeId" => $clabeInterbancariaID
// ]
// ]
// ]); //aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
// $this->logger->info("Se actualizó el cliente en UCRM con el Stripe Customer ID y la Clabe Interbancaria." . PHP_EOL);
// $this->logger->info(json_encode($responsepatchCRM) . PHP_EOL); //imprimir respuesta del patch de CRM con la clabe y Customer ID Stripe
// }catch (GuzzleException $e) {
// $this->logger->info("Error al hacer el patch al cliente en UCRM: " . $e->getMessage() . PHP_EOL);
// return false; // Return false if patch fails
// }
$clientguzz = new Client(); //instancia de cliente GuzzleHttp para consumir API UISP CRM
try {
//aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
$responseCRM = $clientguzz->patch($baseUri . 'clients/' . $ucrm_client_id, [
'json' => json_decode($json_data_patch, true),
'headers' => [
@ -366,7 +491,7 @@ abstract class AbstractStripeOperationsFacade
'Accept' => 'application/json', // Indica que esperamos una respuesta en formato JSON
],
'verify' => false,
]); //aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
]);
} catch (GuzzleException $error) {
@ -412,4 +537,6 @@ abstract class AbstractStripeOperationsFacade
}
}
}

View File

@ -1,17 +1,13 @@
<?php
namespace SmsNotifier\Facade;
use CURLFile;
use DateTime;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Imagick;
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
use ImagickException;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use Ubnt\UcrmPluginSdk\Service\UcrmOptionsManager;
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
//use SmsNotifier\Service\Logger;
@ -19,15 +15,15 @@ class ClientCallBellAPI
{
/**
* @var
* @var
*/
private $CallBellAPIToken;
/**
* @var
* @var
*/
private $IPServer;
/**
* @var
* @var
*/
private $UCRMAPIToken;
/**
@ -56,7 +52,6 @@ class ClientCallBellAPI
public $ucrmVersion;
public function __construct(
$UCRMAPIToken,
$IPServer,
@ -105,9 +100,6 @@ class ClientCallBellAPI
$campo2 = sprintf("📡 Su servicio está: %s 📍Su dirección es: *%s* ", $estado_service, $domicilio);
$log->appendLog("Valor del campo2 " . $campo2);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\"],\n \"template_uuid\": \"55705f1fe4e24bab80104dc2643fe11c\",\n \"optin_contact\": true\n }";
$log->appendLog("La cadena CURL que se envia es: " . $curl_string);
curl_setopt($ch, CURLOPT_POSTFIELDS, $curl_string);
@ -118,15 +110,21 @@ class ClientCallBellAPI
curl_close($ch);
}
public function sendJobNotificationWhatsAppToClient($clientWhatsAppNumber, $jobNotificationData, $reprogramming = false, $changeInstaller = false): bool
public function sendJobNotificationWhatsAppToClient($clientWhatsAppNumber, $jobNotificationData, $reprogramming, $changeInstaller): bool
{
$log = PluginLogManager::create(); //Initialize Logger
$log->appendLog("Enviando mensaje de trabajo para el cliente" . PHP_EOL);
$jsonJobNotificationData = json_encode($jobNotificationData, true);
$log->appendLog("Datos de la notificación de trabajo: " . $jsonJobNotificationData . PHP_EOL);
$log->appendLog("Debugging: reprogramming = " . var_export($reprogramming, true) . ", changeInstaller = " . var_export($changeInstaller, true) . PHP_EOL);
// --- ¡AÑADE ESTAS LÍNEAS PARA CONVERTIR A BOOLEANO REAL! ---
$reprogramming = filter_var($reprogramming, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$changeInstaller = filter_var($changeInstaller, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
// -----------------------------------------------------------
// Puedes volver a loggear los valores para confirmar la conversión (opcional)
$log->appendLog("DEBUG: Valores después de conversión - Reprogramming: " . var_export($reprogramming, true) . ", ChangeInstaller: " . var_export($changeInstaller, true) . PHP_EOL);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.callbell.eu/v1/messages/send');
@ -137,24 +135,53 @@ class ClientCallBellAPI
'Content-Type: application/json',
]);
$campo1 = sprintf('*%s*', $jobNotificationData['clientFullName']);
$campo2 = sprintf('*#%s*', $jobNotificationData['jobId']);
$campo3 = sprintf('*%s*', $jobNotificationData['date']);
$campo4 = sprintf('*%s*', $jobNotificationData['installerName']);
$campo1 = '👤 ' . sprintf(' *%s*', $jobNotificationData['clientFullName']);
if ($changeInstaller && !$reprogramming) {
$campo2 = sprintf('se ha hecho un cambio de técnico 👷🏻‍♂️🔄 para su visita con el folio *#️⃣%s*', $jobNotificationData['jobId']);
} else {
$campo2 = sprintf('*#️⃣%s*', $jobNotificationData['jobId']);
}
$campo3 = '🗓️ ' . sprintf('%s', $jobNotificationData['date']);
if ($changeInstaller && $reprogramming) {
$campo4 = 'Además se ha hecho un cambio de técnico por el siguiente 👷🏻‍♂️➡️ ' . sprintf('*%s*', $jobNotificationData['installerName']);
} else {
$campo4 = '👷🏻‍♂️➡️ ' . sprintf('*%s*', $jobNotificationData['installerName']);
}
if ($reprogramming && $changeInstaller === false) {
$campo5 = "asegúrese de que alguien esté presente en el domicilio 🏠 para permitir el acceso y confirme su disponibilidad.";
$log->appendLog("DEBUG: Valores antes de la estructura IF - Reprogramming: " . var_export($reprogramming, true) . ", ChangeInstaller: " . var_export($changeInstaller, true) . PHP_EOL);
if ($reprogramming && !$changeInstaller) {
//{{1}}, se ha reprogramado su visita técnica con el folio {{2}}
$campo3 = '🗓️➡️ ' . sprintf('%s', $jobNotificationData['date']);
$campo1_combinado = "Estimado cliente $campo1 se ha reprogramado su visita técnica con folio $campo2";
// Case: true, false
//Enviar notificación de reprogramación al cliente
$log->appendLog("Enviando notificación de reprogramación al cliente, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\"],\n \"template_uuid\": \"70579353773f4de1836d4f9b6bf6074d\",\n \"optin_contact\": true\n }";
}
if ($changeInstaller) {
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1_combinado\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"715eed9d6f2d4d90853f25e296202e00\",\n \"optin_contact\": true\n }";
} else if (!$reprogramming && $changeInstaller) {
// Case: false, true
//Enviar notificación de cambio de instalador
$log->appendLog("Enviando notificación de cambio de instalador al cliente, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\"],\n \"template_uuid\": \"0d57fd210595422caf2f5999642882a3\",\n \"optin_contact\": true\n }";
} else {
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"d684e6fa2ba24593a86c98be1815831d\",\n \"optin_contact\": true\n }";
} else if (!$reprogramming && !$changeInstaller) { // <--- Ahora este else if está correctamente encadenado
// Case: false, false
//Enviar notificación normal de visita técnica al cliente
$log->appendLog("Enviando notificación normal de visita técnica al cliente, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\"],\n \"template_uuid\": \"c0ef8228b50a4d9690a2e87bc11e9ab3\",\n \"optin_contact\": true\n }";
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"07cfbc6e394044608485b530a27177d0\",\n \"optin_contact\": true\n }";
} else if ($reprogramming && $changeInstaller) { // <--- Ahora este else if está correctamente encadenado
// Case: true, true
//Enviar notificación de cambio de instalador y reprogramación al cliente
$log->appendLog("Enviando notificación de cambio de instalador y reprogramación al cliente, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"145885d15323414f978f1e3f249c2ae1\",\n \"optin_contact\": true\n }";
} else {
// Case: true, true (la única combinación restante con booleanos)
$log->appendLog("No se encontró una opción válida para enviar la notificación (reprogramming y changeInstaller son true)." . PHP_EOL);
// Decide qué hacer aquí, ¿Quizás devolver false? ¿O manejar esta combinación?
return false; // O el manejo adecuado para true/true
}
$log->appendLog("La cadena CURL que se envia es: " . $curl_string);
@ -190,7 +217,8 @@ class ClientCallBellAPI
$log->appendLog("Ruta no prevista en la función." . PHP_EOL);
return false;
}
public function sendJobNotificationWhatsAppToInstaller($installerWhatsAppNumber, $jobInstallerNotificationData, $reprogramming = false, $changeInstaller = false): bool
public function sendJobNotificationWhatsAppToInstaller($installerWhatsAppNumber, $jobInstallerNotificationData, $reprogramming, $changeInstaller): bool
{
$log = PluginLogManager::create(); //Initialize Logger
@ -198,6 +226,14 @@ class ClientCallBellAPI
$jsonJobNotificationData = json_encode($jobInstallerNotificationData, true);
$log->appendLog("Datos de la notificación de tarea: " . $jsonJobNotificationData . PHP_EOL);
// --- ¡AÑADE ESTAS LÍNEAS PARA CONVERTIR A BOOLEANO REAL! ---
$reprogramming = filter_var($reprogramming, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$changeInstaller = filter_var($changeInstaller, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
// -----------------------------------------------------------
// Puedes volver a loggear los valores para confirmar la conversión (opcional)
$log->appendLog("DEBUG: Valores después de conversión - Reprogramming: " . var_export($reprogramming, true) . ", ChangeInstaller: " . var_export($changeInstaller, true) . PHP_EOL);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.callbell.eu/v1/messages/send');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@ -207,40 +243,62 @@ class ClientCallBellAPI
'Content-Type: application/json',
]);
$campo1 = $jobInstallerNotificationData['installerName'];
if ($changeInstaller) {
if (!$reprogramming && $changeInstaller) {
$campo1 = $jobInstallerNotificationData['installerName'];
$campo2 = $jobInstallerNotificationData['subjectOfChange'];
$campo3 = sprintf("#%s", $jobInstallerNotificationData['jobId']);
$campo4 = $jobInstallerNotificationData['clientFullName'];
$campo3 = sprintf("#️⃣%s", $jobInstallerNotificationData['jobId']);
$campo4 = sprintf('*👤%s* ', $jobInstallerNotificationData['clientFullName']);
$campo5 = $jobInstallerNotificationData['additionalChangeData'];
} else {
$campo2 = sprintf("*#%s*", $jobInstallerNotificationData['jobId']);
$campo3 = $jobInstallerNotificationData['clientFullName'];
$campo4 = $jobInstallerNotificationData['clientAddress'];
$campo5 = $jobInstallerNotificationData['clientWhatsApp'];
$campo6 = $jobInstallerNotificationData['date'];
$campo7 = $jobInstallerNotificationData['jobDescription'];
$campo8 = $jobInstallerNotificationData['gmapsLocation'];
$campo1 = '👷🏻‍♂️ ' . $jobInstallerNotificationData['installerName'];
$campo2 = sprintf("#️⃣%s", $jobInstallerNotificationData['jobId']);
$campo3 = '👤 *' . $jobInstallerNotificationData['clientFullName'] . '*';
// $campo4 = $jobInstallerNotificationData['clientAddress'];
$campo4 = '☎️ ' . $jobInstallerNotificationData['clientWhatsApp'];
$campo5 = '🗓️ ' . $jobInstallerNotificationData['date'];
// $campo6 = '🛠️ ' . $jobInstallerNotificationData['jobDescription'];
// $campo7 = '📌 ' . $jobInstallerNotificationData['gmapsLocation'];
// $campo8 = '🔐 ' . $passwordAntenaCliente;
}
if ($reprogramming && !$changeInstaller) {
$jobId = sprintf("#️⃣ *%s*", $jobInstallerNotificationData['jobId']);
$clientFullName = sprintf("👤 *%s*", $jobInstallerNotificationData['clientFullName']);
$date = sprintf("🗓️➡️ %s", $jobInstallerNotificationData['date']);
$installerName = sprintf("👷🏻‍♂️ *%s*", $jobInstallerNotificationData['installerName']);
if ($reprogramming && $changeInstaller === false) {
$campo1_combinado = "$installerName se reprogramó una tarea con el folio $jobId para el cliente $clientFullName, la nueva fecha será el $date";
$campo2 = $jobInstallerNotificationData['clientWhatsApp'];
$campo3 = $jobInstallerNotificationData['gmapsLocation'];
$campo4 = $jobInstallerNotificationData['passwordAntenaCliente'];
//Enviar notificación de reprogramación
$log->appendLog("Enviando notificación de reprogramación al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\", \"$campo6\", \"$campo7\", \"$campo8\"],\n \"template_uuid\": \"42152c07c67b468ba68e581c0283e22e\",\n \"optin_contact\": true\n }";
} else if ($changeInstaller) {
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1_combinado\", \"$campo2\", \"$campo3\", \"$campo4\"],\n \"template_uuid\": \"88eeb6420a214fd8870dd28d741021c4\",\n \"optin_contact\": true\n }";
} else if (!$reprogramming && $changeInstaller) {
//Enviar notificación de cambio de instalador
$log->appendLog("Enviando notificación de cambio de instalador al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$log->appendLog("Enviando notificación de cambio de instalador al instalador anterior (desasignación), valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"e1aa2b0fd3884595918f4ac2676acd29\",\n \"optin_contact\": true\n }";
} else if ($reprogramming && $changeInstaller) {
//Enviar notificación de cambio de instalador
$log->appendLog("Enviando notificación de cambio de instalador al instalador y notificación de reprogramación al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\"],\n \"template_uuid\": \"e1aa2b0fd3884595918f4ac2676acd29\",\n \"optin_contact\": true\n }";
} else if (!$reprogramming && !$changeInstaller) {
} else {
//Enviar notificación normal de asignación de tarea
$log->appendLog("Enviando notificación normal de asignación de tarea al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\", \"$campo3\", \"$campo4\", \"$campo5\", \"$campo6\", \"$campo7\", \"$campo8\"],\n \"template_uuid\": \"b6663394265e4bcdb215369aa9ba0f21\",\n \"optin_contact\": true\n }";
//combinar el campo3, campo4, campo5, campo6, campo7 y campo8 en un solo campo con saltos de línea
$jobId = sprintf("#️⃣ *%s*", $jobInstallerNotificationData['jobId']);
$clientFullName = sprintf("👤 *%s*", $jobInstallerNotificationData['clientFullName']);
$date = sprintf("🗓️ %s", $jobInstallerNotificationData['date']);
$installerName = sprintf("👷🏻‍♂️ *%s*", $jobInstallerNotificationData['installerName']);
$campo1_combinado = "$installerName se te ha asignado una tarea con folio $jobId, del cliente $clientFullName, para el $date";
$campo2 = $jobInstallerNotificationData['clientWhatsApp'];
$campo3 = $jobInstallerNotificationData['gmapsLocation'];
$campo4 = $jobInstallerNotificationData['passwordAntenaCliente'];
$log->appendLog("Enviando notificación normal de tarea al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
$curl_string = "{\n \"to\": \"$installerWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1_combinado\", \"$campo2\", \"$campo3\", \"$campo4\"],\n \"template_uuid\": \"88eeb6420a214fd8870dd28d741021c4\",\n \"optin_contact\": true\n }";
}
$log->appendLog("La cadena CURL que se envia es: " . $curl_string);
@ -251,6 +309,9 @@ class ClientCallBellAPI
curl_close($ch);
// Validar la respuesta de Callbell
$jsonResponse = json_decode($response, true);
@ -275,7 +336,6 @@ class ClientCallBellAPI
// Valor de retorno predeterminado en caso de que ninguna condición se cumpla
$log->appendLog("Ruta no prevista en la función." . PHP_EOL);
return false;
}
public function sendPaymentNotificationWhatsApp($clientWhatsAppNumber, $notificationData): bool
@ -292,20 +352,17 @@ class ClientCallBellAPI
//Path base del comprobante de pago
$pdf_payment_path = '';
$this->ucrmApi = UcrmApi::create();
$payments = $this->ucrmApi->get(
'payments/',
[
'clientId' => $notificationData->clientData['id'],
'limit' => 1,
'direction' => 'DESC'
'direction' => 'DESC',
]
);
$payment_id = $payments[0]['id'];
$payment_amount = '$' . $payments[0]['amount'];
//$saldo = '$' . $notificationData->clientData['accountBalance'];
@ -329,14 +386,12 @@ class ClientCallBellAPI
$clientGuzzleHttp = new Client([
'base_uri' => $baseUri,
'headers' => [
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
'Accept' => 'application/pdf', // Indica que esperamos una respuesta en formato JSON
],
'verify' => false,
]);
try {
// Hacer la solicitud GET para obtener el PDF
$response = $clientGuzzleHttp->request('GET', "payments/$payment_id/pdf");
@ -368,20 +423,11 @@ class ClientCallBellAPI
$log->appendLog("El archivo PDF es válido y tiene contenido: $rutaArchivo" . PHP_EOL);
$rutaImagen = __DIR__ . '/../../comprobantes/' . str_replace('.pdf', '.png', $fileNameComprobante);
} catch (\Exception $e) {
$log->appendLog("Error al manejar el comprobante de pago: " . $e->getMessage() . PHP_EOL);
return false;
}
try {
$image = new Imagick();
$image->setResolution(300, 300);
@ -397,16 +443,12 @@ class ClientCallBellAPI
return false;
}
$fileNameComprobanteImage = str_replace('.pdf', '.png', $fileNameComprobante);
$url_file = $this->UploadReceiptToWordpressByImageFileName($fileNameComprobanteImage);//Carga del comprobante PDF a Wordpress para su posterior envío
$url_file = $this->UploadReceiptToWordpressByImageFileName($fileNameComprobanteImage); //Carga del comprobante PDF a Wordpress para su posterior envío
// $url_file = $this->UploadReceiptToWordpressByImageFileName($fileNameComprobante);//Carga del comprobante PDF a Wordpress para su posterior envío
$log->appendLog("Se terminó de subir comprobante a wordpress " . PHP_EOL);
//$log->appendLog("Entrando al metodo sendPaymentNotificationWhatsAp" . PHP_EOL);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.callbell.eu/v1/messages/send');
@ -417,8 +459,6 @@ class ClientCallBellAPI
'Content-Type: application/json',
]);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"document\",\n \"content\": {\n \"text\": \"S/M\",\n \"url\": \"$url_file\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$payment_amount\", \"$saldoTexto\"],\n \"template_uuid\": \"57ead79cebd14902921477922403093b\",\n \"optin_contact\": true\n }";
$log->appendLog("La cadena CURL que se envia es: " . $curl_string);
curl_setopt($ch, CURLOPT_POSTFIELDS, $curl_string);
@ -465,11 +505,10 @@ class ClientCallBellAPI
public function sendTextPaymentNotificationWhatsApp($clientWhatsAppNumber, $notificationData): bool
{
$nombre_cliente = sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']);
$folio = $notificationData->paymentData['id'];
$client_id = $notificationData->clientData['id'];
$fecha_pago = null;
$fecha_pago = '';
$cantidad_pagada = $notificationData->paymentData['amount'];
$metodo_pago = '';
//$invoice_id= null;
@ -480,9 +519,6 @@ class ClientCallBellAPI
$texto_credito = null;
$creditoClienteBase = $notificationData->clientData['accountCredit'];
switch ($notificationData->paymentData['methodId']) {
case '11721cdf-a498-48be-903e-daa67552e4f6':
$metodo_pago = 'Cheque 📄';
@ -517,11 +553,8 @@ class ClientCallBellAPI
default:
$metodo_pago = 'Desconocido, revisar metodos de pago no contemplados';
break;
}
$log = PluginLogManager::create(); //Initialize Logger
$log->appendLog("Eviando comprobante de pago al cliente: " . $notificationData->clientData['id'] . ' con número: ' . $clientWhatsAppNumber . PHP_EOL);
@ -533,14 +566,13 @@ class ClientCallBellAPI
//Path base del comprobante de pago
$pdf_payment_path = '';
$this->ucrmApi = UcrmApi::create();
$payments = $this->ucrmApi->get(
'payments/',
[
'clientId' => $notificationData->clientData['id'],
'limit' => 1,
'direction' => 'DESC'
'direction' => 'DESC',
]
);
@ -549,7 +581,6 @@ class ClientCallBellAPI
$datos_paymentJsonText = json_encode($payments, true);
$log->appendLog("Datos traidos con payment api: " . $datos_paymentJsonText . PHP_EOL);
// $log->appendLog("Check 1" . PHP_EOL);
$paymentDate = new DateTime($fecha_pago);
@ -559,7 +590,6 @@ class ClientCallBellAPI
// Formatear la fecha como "d/m/Y g:ia" (día/mes/año hora:minutos am/pm)
$fecha_pago = $paymentDate->format('d/m/Y g:ia');
$accountBalance = $notificationData->clientData['accountBalance'];
$saldoTexto = '';
@ -581,21 +611,15 @@ class ClientCallBellAPI
if ($creditoClienteBase > 0 && empty($notificationData->paymentData['paymentCovers'])) {
$texto_credito = "La cantidad que sobra de $creditoPorPagoFormateado se ha convertido a crédito";
$log->appendLog("La cantidad que sobra de $creditoPorPagoFormateado se ha convertido a crédito" . PHP_EOL);
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"John Doe\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$folio\", \"$client_id\", \"$fecha_pago\", \"$cantidad_pagadaFormateada\", \"$metodo_pago\", \"$saldoTexto\", \"$texto_credito\"],\n \"template_uuid\": \"4ac9dc060cf746b6ad7f2e8acad355e0\",\n \"optin_contact\": true\n }";
} else {
if ($creditoPorPago > 0) {
$texto_credito = "La cantidad que sobra de $creditoPorPagoFormateado se ha convertido a crédito";
$log->appendLog("La cantidad que sobra de $creditoPorPagoFormateado se ha convertido a crédito" . PHP_EOL);
} else {
$texto_credito = '_________________________';
}
@ -604,7 +628,7 @@ class ClientCallBellAPI
$clientGuzzleHttp = new Client([
'base_uri' => $baseUri,
'headers' => [
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
'Accept' => 'application/pdf', // Indica que esperamos una respuesta en formato JSON
],
'verify' => false,
@ -612,15 +636,13 @@ class ClientCallBellAPI
$log->appendLog("Verificar paymentCovers " . PHP_EOL);
$log->appendLog("payment covers" . json_encode($notificationData->paymentData['paymentCovers']) . PHP_EOL);
if (!empty($notificationData->paymentData['paymentCovers'])) {
$log->appendLog('Datos del payment covers:' . PHP_EOL);
$invoiceIds = ''; // Variable para almacenar los invoiceId
$amounts = ''; // Variable para almacenar los amounts formateados
$invoiceIds = ''; // Variable para almacenar los invoiceId
$amounts = ''; // Variable para almacenar los amounts formateados
foreach ($notificationData->paymentData['paymentCovers'] as $paymentCover) {
$log->appendLog('Invoice ID pagado: ' . $paymentCover['invoiceId'] . ' de esta cantidad: ' . $paymentCover['amount'] . PHP_EOL);
@ -637,7 +659,7 @@ class ClientCallBellAPI
// Eliminar la última coma y el espacio extra usando substr
$invoiceIds = substr($invoiceIds, 0, -2); // Elimina los últimos dos caracteres (coma y espacio)
$amounts = substr($amounts, 0, -2); // Elimina los últimos dos caracteres (coma y espacio)
$amounts = substr($amounts, 0, -2); // Elimina los últimos dos caracteres (coma y espacio)
// Mostrar las cadenas finales
$log->appendLog('Todos los Invoice IDs: ' . $invoiceIds . PHP_EOL);
@ -654,14 +676,10 @@ class ClientCallBellAPI
$log->appendLog('Numero de factura: ' . $responseInvoicesJSON['number'] . PHP_EOL);
$log->appendLog('TOTAL de factura: ' . $responseInvoicesJSON['total'] . PHP_EOL);
$total_factura = $responseInvoicesJSON['total'];
} catch (\Exception $e) {
$log->appendLog("Error con un problema al obtener alguna factura del cliente: " . $e->getMessage() . PHP_EOL);
return false;
}
} else {
$log->appendLog("no hay datos en payment covers" . PHP_EOL);
$invoiceIds = $notificationData->paymentData['id'];
@ -674,13 +692,8 @@ class ClientCallBellAPI
$pagoFacturaFormateado = '$' . $amounts . ' MXN';
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"John Doe\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$folio\", \"$client_id\", \"$fecha_pago\", \"$cantidad_pagadaFormateada\", \"$metodo_pago\", \"$invoiceIds\", \"$total_facturaFormateada\", \"$pagoFacturaFormateado\", \"$saldoTexto\", \"$texto_credito\"],\n \"template_uuid\": \"c1396a6ad3cb4192916d4ac2bfb782a5\",\n \"optin_contact\": true\n }";
}
//$log->appendLog("Entrando al metodo sendPaymentNotificationWhatsAp" . PHP_EOL);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.callbell.eu/v1/messages/send');
@ -691,9 +704,6 @@ class ClientCallBellAPI
'Content-Type: application/json',
]);
//$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"document\",\n \"content\": {\n \"text\": \"S/M\",\n \"url\": \"$url_file\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$payment_amount\", \"$saldo\"],\n \"template_uuid\": \"6c0df98317b44f7b8666375a6cc8454c\",\n \"optin_contact\": true\n }";
// $curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\"\n },\n \"template_values\": [\"$campo1\", \"$campo2\"],\n \"template_uuid\": \"55705f1fe4e24bab80104dc2643fe11c\",\n \"optin_contact\": true\n }";
@ -742,7 +752,6 @@ class ClientCallBellAPI
public function sendOverdueNotificationWhatsApp($clientWhatsAppNumber, $notificationData): void
{
$log = PluginLogManager::create(); //Initialize Logger
// URL base de la API
@ -752,7 +761,6 @@ class ClientCallBellAPI
//Path base del comprobante de pago
//$pdf_payment_path = '';
// $this->ucrmApi = UcrmApi::create();
// $invoices = $this->ucrmApi->get(
// 'invoices/',
@ -786,8 +794,6 @@ class ClientCallBellAPI
// 'verify' => false,
// ]);
// if (empty($notificationData->clientData['contacts'][0]['email'])) {
// $log->appendLog("El cliente no tiene correo" . PHP_EOL);
// } else {
@ -815,9 +821,6 @@ class ClientCallBellAPI
// $log->appendLog("La ruta no es válida o no existe" . PHP_EOL);
// }
$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"SIIP INTERNET\",\n \"type\": \"text\",\n \"content\": {\n \"text\": \"S/M\",\n },\n \"template_values\": [\"$nombre_cliente\", \"$saldo\"],\n \"template_uuid\": \"9e7024c0a61a4c49b382150d26888dc2\",\n \"optin_contact\": true\n }";
//$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"document\",\n \"content\": {\n \"text\": \"S/M\",\n \"url\": \"$url_file\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$payment_amount\", \"$saldo\"],\n \"template_uuid\": \"57ead79cebd14902921477922403093b\",\n \"optin_contact\": true\n }";
//$curl_string = "{\n \"to\": \"$clientWhatsAppNumber\",\n \"from\": \"whatsapp\",\n \"type\": \"document\",\n \"content\": {\n \"text\": \"S/M\",\n \"url\": \"$url_file\"\n },\n \"template_values\": [\"$nombre_cliente\", \"$payment_amount\", \"$saldo\"],\n \"template_uuid\": \"6c0df98317b44f7b8666375a6cc8454c\",\n \"optin_contact\": true\n }";
@ -830,10 +833,8 @@ class ClientCallBellAPI
$log->appendLog("Response del CallBell: " . $response);
curl_close($ch);
}
public function getContactWhatsapp($cellphone_number): string
{
// URL de la API REST
@ -847,7 +848,7 @@ class ClientCallBellAPI
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->CallBellAPIToken,
'Content-Type: application/json'
'Content-Type: application/json',
]);
// Ejecutar la solicitud y obtener la respuesta
@ -867,7 +868,7 @@ class ClientCallBellAPI
{
$log = PluginLogManager::create(); //Initialize Logger
//imprimir notificacionData
//imprimir notificacionData
$log->appendLog("Notificacion data: " . json_encode($notificationData) . PHP_EOL);
//ejemplo de $notificationData: {"uuid":"cad4fa25-176e-4823-9f4c-1421d05d2a31","changeType":"edit","entity":"client","entityId":2,"message":null,"clientId":2,"eventName":"client.edit","clientData":{"id":2,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"31 Chiapas","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Chiapas 31, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":"Cliente espacial, el m\u00e1s chido","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-01-25T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":true,"firstName":"Daniel Humberto","lastName":"Soto Villegas Pollerias2","username":"danydhsv","contacts":[{"id":2,"clientId":2,"email":"dhsv.141089@gmail.com","phone":"5214181878106","name":"Personal","isBilling":false,"isContact":false,"types":[{"id":1000,"name":"WhatsApp"}]},{"id":170,"clientId":2,"email":null,"phone":"5214181817609","name":"Bussiness","isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":112,"clientId":2,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_PetN1dhr4rx0kX","clientZoneVisible":true},{"id":113,"clientId":2,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"0021804341999569810","clientZoneVisible":true},{"id":178,"clientId":2,"customAttributeId":15,"name":"Site","key":"site","value":"0LOCS","clientZoneVisible":false},{"id":179,"clientId":2,"customAttributeId":16,"name":"Antena\/Sectorial","key":"antenaSectorial","value":"Sectorial-4b 172.16.13.16\/24","clientZoneVisible":false}],"accountBalance":1553.33,"accountCredit":1553.33,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":2,"name":"NS EXENTO","colorBackground":"#42a3df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1564209,"addressGpsLon":-100.9384185,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":true,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"serviceData":null,"invoiceData":null,"paymentData":null}
@ -888,8 +889,6 @@ class ClientCallBellAPI
}
}
$log->appendLog("Dentro del proceso del patch: " . PHP_EOL);
$this->ucrmApi = UcrmApi::create();
$payments = $this->ucrmApi->get(
@ -897,7 +896,7 @@ class ClientCallBellAPI
[
'clientId' => $notificationData->clientData['id'],
'limit' => 1,
'direction' => 'DESC'
'direction' => 'DESC',
]
);
@ -909,10 +908,6 @@ class ClientCallBellAPI
//$log->appendLog("Esto es lo que trae la fecha mas reciente de los pagos: " . $notificationData->paymentData[0]['createdDate']. PHP_EOL);
// $log->appendLog("Esto es lo que trae la fecha mas reciente de los pagos opcion 2: " . $payments[0]['createdDate'] . PHP_EOL);
$uuid = $response_getContactCallBell['contact']['uuid'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@ -973,11 +968,8 @@ class ClientCallBellAPI
default:
$payment_method = 'Desconocido, revisar metodos de pago no contemplados';
break;
}
$fecha_actual = new DateTime();
$fecha_actual->modify('-6 hours');
$fecha_actual_ajustada = $fecha_actual->format("d/m/Y H:i");
@ -1020,7 +1012,7 @@ 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']) . '",' .
'"Nombre": "' . sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']) . '",' .
'"URL": "https://sistema.siip.mx/crm/client/' . $notificationData->clientId . '",' .
'"Saldo Actual": "' . $saldoTexto . '",' .
'"Monto Ultimo Pago": "$ ' . $payments[0]['amount'] . '",' .
@ -1033,15 +1025,11 @@ class ClientCallBellAPI
'"Antena/Sectorial": "' . $antenaSectorial . '"' .
'}';
$data_CRM = array(
$data_CRM = [
//"uuid" => $json_responseAPI->contact->uuid,
"name" => sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']),
"custom_fields" => array(
"custom_fields" => [
"Cliente" => $notificationData->clientData['id'],
"Domicilio" => ($notificationData->clientData['fullAddress'] == null) ? '📍❌ Sin domicilio' : '📍 ' . $notificationData->clientData['fullAddress'],
"Nombre" => sprintf("👤 %s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']),
@ -1054,21 +1042,20 @@ class ClientCallBellAPI
"Fecha Ultima Actualizacion" => '📆🔄️ ' . $fecha_actual_ajustada,
"Clabe Interbancaria" => $clabeInterbancaria,
"Site" => $site,
"Antena/Sectorial" => $antenaSectorial
)
);
$log->appendLog("JSON con los datos a actualizar: " . json_encode($data_CRM) . PHP_EOL);
"Antena/Sectorial" => $antenaSectorial,
],
];
$data_CRM2 = array(
"custom_fields" => array(
$log->appendLog("JSON con los datos a actualizar: " . json_encode($data_CRM) . PHP_EOL);
$data_CRM2 = [
"custom_fields" => [
"Resumen" => $resumenClienteJSON,
)
);
],
];
$log->appendLog("JSON con los datos a actualizar del resumen: " . $resumenClienteJSON . PHP_EOL);
if (
$response_getContactCallBell['custom_fields']['Cliente'] != $data_CRM['custom_fields']['Cliente']
|| $response_getContactCallBell['custom_fields']['Domicilio'] != $data_CRM['custom_fields']['Domicilio']
@ -1079,7 +1066,7 @@ class ClientCallBellAPI
|| $response_getContactCallBell['custom_fields']['Fecha Ultimo Pago'] != $data_CRM['custom_fields']['Fecha Ultimo Pago']
|| $response_getContactCallBell['custom_fields']['Monto Ultimo Pago'] != $data_CRM['custom_fields']['Monto Ultimo Pago']
|| $response_getContactCallBell['name'] != $data_CRM['name']
) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
curl_setopt($ch2, CURLOPT_POSTFIELDS, json_encode($data_CRM2));
@ -1089,13 +1076,12 @@ class ClientCallBellAPI
$log->appendLog("Response 2 Patch CallBell: " . $response2 . PHP_EOL);
curl_close($ch);
curl_close($ch2);
// if($fileNameComprobante != null){
// sleep(3);
// $this->deleteFileWordPressAndLocal($fileNameComprobante);
// }
// $json_data_patch = '{
// "attributes": [
// {
@ -1117,7 +1103,6 @@ class ClientCallBellAPI
// 'verify' => false,
// ]); //aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
// } catch (GuzzleException $error) {
// echo "Error al hacer el patch al CRM: " . $error->getMessage() . PHP_EOL;
// //exit();
@ -1129,17 +1114,13 @@ class ClientCallBellAPI
}
}
public function patchServiceStatusWhatsApp($response_getContactCallBell, $notificationData)
{
$log = PluginLogManager::create(); //Initialize Logger
$log->appendLog("Actualizando estado del servicio " . PHP_EOL);
$uuid = $response_getContactCallBell['contact']['uuid'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@ -1154,18 +1135,13 @@ class ClientCallBellAPI
$log->appendLog("Nombre del cliente al que se va a actualizar el estado del servicio: " . $nombre_cliente . PHP_EOL);
$log->appendLog("UUID: " . $uuid . PHP_EOL);
$fecha_actual = new DateTime();
$fecha_actual->modify('-6 hours');
$fecha_actual_ajustada = $fecha_actual->format("d/m/Y H:i");
$data_CRM = array(
$data_CRM = [
"name" => $nombre_cliente,
"custom_fields" => array(
"custom_fields" => [
"Estado" => (
isset($notificationData->serviceData['status'])
&& $notificationData->serviceData['status'] == 3
@ -1173,11 +1149,9 @@ class ClientCallBellAPI
(isset($notificationData->serviceData['status'])
&& $notificationData->serviceData['status'] == 1)
? '🟢 Activo' : '🚫 Finalizado'),
"Fecha Ultima Actualizacion" => '📆🔄️ ' . $fecha_actual_ajustada
)
);
"Fecha Ultima Actualizacion" => '📆🔄️ ' . $fecha_actual_ajustada,
],
];
$log->appendLog("JSON con los datos a actualizar: " . json_encode($data_CRM) . PHP_EOL);
@ -1186,8 +1160,6 @@ class ClientCallBellAPI
$response = curl_exec($ch);
$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
curl_close($ch);
} else {
$log->appendLog("No hay cambios en el estado del servicio que actualizar " . PHP_EOL);
}
@ -1217,7 +1189,6 @@ class ClientCallBellAPI
$log->appendLog("file_to_upload: " . $file_to_upload . PHP_EOL);
// 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);
@ -1231,10 +1202,10 @@ class ClientCallBellAPI
$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);
$this->deleteFilesWordpressExceptLastHundred($log, $ftp_conn, $remote_file);
// Cerrar conexión FTP
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
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);
@ -1284,15 +1255,12 @@ class ClientCallBellAPI
// return $url;
// }
} else {
$log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
return '';
}
}
function deleteFilesWordpressExceptLastHundred($log, $ftp_conn, $remote_folder): bool
{
@ -1335,10 +1303,8 @@ class ClientCallBellAPI
ftp_close($ftp_conn);
return false;
}
}
/**
* /Elimina los archivos del directorio de comprobantes de pago a excepción de los últimos 100 para evitar errores de envío
* @return void
@ -1351,7 +1317,6 @@ class ClientCallBellAPI
if (!is_dir($directory)) {
$log->appendLog('El directorio no existe.');
die("El directorio no existe.");
}
// Obtener la lista de archivos en el directorio
@ -1378,5 +1343,4 @@ class ClientCallBellAPI
$log->appendLog("Hay menos de 100 archivos en el directorio. No se eliminarán archivos." . PHP_EOL);
}
}
}
}

View File

@ -1,14 +1,13 @@
<?php
declare(strict_types=1);
declare (strict_types = 1);
namespace SmsNotifier;
use Psr\Log\LogLevel;
use SmsNotifier\Facade\TwilioNotifierFacade;
use SmsNotifier\Facade\PluginNotifierFacade;
use SmsNotifier\Facade\PluginOxxoNotifierFacade;
use SmsNotifier\Facade\TwilioNotifierFacade;
use SmsNotifier\Factory\NotificationDataFactory;
use SmsNotifier\Service\Logger;
use SmsNotifier\Service\OptionsManager;
@ -56,7 +55,6 @@ class Plugin
*/
protected $ucrmApi;
public function __construct(
Logger $logger,
OptionsManager $optionsManager,
@ -66,13 +64,13 @@ class Plugin
PluginOxxoNotifierFacade $pluginOxxoNotifierFacade,
NotificationDataFactory $notificationDataFactory
) {
$this->logger = $logger;
$this->optionsManager = $optionsManager;
$this->pluginDataValidator = $pluginDataValidator;
$this->notifierFacade = $notifierFacade;
$this->pluginNotifierFacade = $pluginNotifierFacade;
$this->logger = $logger;
$this->optionsManager = $optionsManager;
$this->pluginDataValidator = $pluginDataValidator;
$this->notifierFacade = $notifierFacade;
$this->pluginNotifierFacade = $pluginNotifierFacade;
$this->pluginOxxoNotifierFacade = $pluginOxxoNotifierFacade;
$this->notificationDataFactory = $notificationDataFactory;
$this->notificationDataFactory = $notificationDataFactory;
}
public function run(): void
@ -80,13 +78,13 @@ class Plugin
// $hola = PHP_SAPI;
// $this->logger->info('valor de PHP_SAPI: ' . $hola);
if (PHP_SAPI === 'fpm-fcgi') {
//$this->logger->debug('Whatsapp over HTTP started');
// $this->logger->debug('Whatsapp over HTTP started');
$this->processHttpRequest();
//$this->logger->debug('HTTP request processing ended.');
// $this->logger->debug('HTTP request processing ended.');
} elseif (PHP_SAPI === 'cli') {
//$this->logger->debug('Whatsapp over CLI started');
// $this->logger->debug('Whatsapp over CLI started');
$this->processCli();
//$this->logger->debug('CLI process ended.');
// $this->logger->debug('CLI process ended.');
} else {
throw new \UnexpectedValueException('Unknown PHP_SAPI type: ' . PHP_SAPI);
}
@ -108,12 +106,11 @@ class Plugin
$this->logger->setLogLevelThreshold(LogLevel::DEBUG);
}
$userInput = file_get_contents('php://input'); //se recibe el json del webhook
//imprimir el json del webhook
$this->logger->debug('valor del webhook: ' . $userInput . PHP_EOL);
$userInput = file_get_contents('php://input');
if (!$userInput) {
if (! $userInput) {
$this->logger->warning('no input');
return;
@ -121,7 +118,7 @@ class Plugin
$jsonData = @json_decode($userInput, true, 50);
if (!isset($jsonData['uuid'])) {
if (! isset($jsonData['uuid'])) {
$this->logger->info('No UUID found in the webhook data');
//$this->logger->error('JSON error: ' . json_last_error_msg());
@ -131,25 +128,50 @@ class Plugin
if ($jsonData) {
switch ($jsonData['type']) {
case 'customer_cash_balance_transaction.created':
$this->logger->info('Evento de transfencia al cliente encontrado');
$this->logger->info('Valor del EventJSON: ' . json_encode($jsonData) . PHP_EOL);
$this->pluginNotifierFacade->createPaymentIntent($jsonData);
if ($jsonData['data']['object']['type'] === 'funded') {
$this->logger->info('Evento de transferencia de un cliente recibido: ' . json_encode($jsonData) . PHP_EOL);
$this->pluginNotifierFacade->createPaymentIntent($jsonData);
}
if ($jsonData['data']['object']['type'] === 'applied_to_payment') {
$this->logger->info('Se aplicó el saldo en Stripe de un pago: ' . json_encode($jsonData) . PHP_EOL);
$this->pluginNotifierFacade->registerPaymentFromWebhook($jsonData);
}elseif ($jsonData['data']['object']['type'] === 'unapplied_from_payment'){
//ejemplo de json para transferencia de dinero cancelada: {"id":"evt_1RlEGgEFY1WEUtgR6Bp2DzDP","object":"event","api_version":"2023-10-16","created":1752606717,"data":{"object":{"id":"ccsbtxn_1RlEGfEFY1WEUtgRv8jAUGmE","object":"customer_cash_balance_transaction","created":1752606717,"currency":"mxn","customer":"cus_PetN1dhr4rx0kX","ending_balance":18000,"livemode":false,"net_amount":18000,"type":"unapplied_from_payment","unapplied_from_payment":{"payment_intent":"pi_3RlDPdEFY1WEUtgR1JBgNhTQ"}}},"livemode":false,"pending_webhooks":2,"request":{"id":"req_954mskVBfAI0jn","idempotency_key":"749518f6-baa0-4ae9-99e4-8029a35719aa"},"type":"customer_cash_balance_transaction.created"}
$paymentIntentId = $jsonData['data']['object']['unapplied_from_payment']['payment_intent'];
//Se canceló una transferencia de dinero, imprimir que se canceló y además el monto neto
$this->logger->warning('Evento de transferencia cancelada para el pago: ' . $paymentIntentId . PHP_EOL);
$this->logger->warning('Monto neto de la transferencia cancelada: ' . $jsonData['data']['object']['net_amount'] . PHP_EOL);
}
break;
case 'payout.failed':
$this->logger->info('Evento de transferencia fallida encontrado: ' . json_encode($jsonData) . PHP_EOL);
//imprimir detalles del fallo
$this->logger->info('Detalles del fallo: ' . json_encode($jsonData));
break;
case 'payment_intent.partially_funded':
$this->logger->info('Evento de pago parcialmente financiado encontrado: ' . json_encode($jsonData) . PHP_EOL);
//imprimir detalles del evento o pago
$this->logger->info('Detalles del evento: ' . json_encode($jsonData));
break;
case 'inbound_payment.payment_attempt':
$this->logger->info('Evento de Pago de OXXO recibido');
$this->logger->info('Valor del EventJSON: ' . json_encode($jsonData) . PHP_EOL);
//$this->logger->info('Evento de Pago de OXXO recibido: ' . json_encode($jsonData) . PHP_EOL);
break;
case 'cash_balance.funds_available':
$this->logger->info('Evento de Pago de fondos disponibles recibido: ' . json_encode($jsonData) . PHP_EOL);
break;
case 'energy.alert':
$this->logger->info('Evento de Energía recibido: ' . $jsonData['message'] . PHP_EOL);
break;
case 'oxxo.request':
$this->logger->info('Evento de referencia de oxxo recibido' . PHP_EOL);
$this->logger->info('Evento de solicitud de referencia de oxxo recibido' . PHP_EOL);
// Construir la URL basada en el "client_id"
// $url = "https://siip.mx/wp/wp-content/uploads/img/voucher.png";
if (!empty($jsonData['amount'])) {
$this->logger->info('Referencia persnoalizada, Valor del monto: ' . $jsonData['amount'] . PHP_EOL);
if (! empty($jsonData['amount'])) {
$this->logger->info('Referencia personalizada, Valor del monto: ' . $jsonData['amount'] . PHP_EOL);
$intentos = 0;
do {
// if ($intentos > 1) {
@ -178,12 +200,14 @@ class Plugin
//this->logger->error('Error al crear la referencia de OXXO: ' . $responseOxxo);
//crear un json con variable $response que contenga las claves y valores del array $responseOxxo los cuales son: oxxo_reference, error, failDescription, clientID, clientFullName amount
$response = '{' .
'"url":' . $responseOxxo['url'] . ',' .
'"url": "' . $responseOxxo['url'] . '",' .
'"oxxo_reference": "' . $responseOxxo['oxxo_reference'] . '",' .
'"voucher_image_url": "' . $responseOxxo['voucher_image_url'] . '",' .
'"error": "' . $responseOxxo['error'] . '",' .
'"failDescription": "' . $responseOxxo['failDescription'] . '",' .
'"clientID": "' . $responseOxxo['clientID'] . '",' .
'"clientFullName": "' . $responseOxxo['clientFullName'] . '",' .
'"amount": "' . $responseOxxo['amount'] . '",' .
'"amount": "' . $responseOxxo['amount'] . '"' .
'}';
header('Content-Type: application/json');
@ -195,6 +219,7 @@ class Plugin
$response = '{' .
'"url": "' . $responseOxxo['url'] . '",' .
'"oxxo_reference": "' . $responseOxxo['oxxo_reference'] . '",' .
'"voucher_image_url": "' . $responseOxxo['voucher_image_url'] . '",' .
'"error": "' . $responseOxxo['error'] . '",' .
'"failDescription": "' . $responseOxxo['failDescription'] . '",' .
'"clientID": "' . $responseOxxo['clientID'] . '",' .
@ -206,7 +231,7 @@ class Plugin
// Enviar el encabezado de respuesta como JSON
header('Content-Type: application/json');
// Enviar la respuesta en formato JSON
// Enviar la respuesta en formato JSON
//echo json_encode($response);
echo $response;
@ -223,11 +248,6 @@ class Plugin
// $webhook_string = json_encode($event_json);
// $this->logger->debug("El valor de webhook_string: " . $webhook_string . PHP_EOL);
$notification = $this->notificationDataFactory->getObject($jsonData);
$this->logger->debug('valor el evento recibido por webhook: ' . $notification->eventName . PHP_EOL);
$this->logger->debug('Valor de JSON: ' . json_encode($jsonData) . PHP_EOL);
@ -235,11 +255,14 @@ class Plugin
if ($notification->changeType === 'test') {
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$config = $configManager->loadConfig();
$this->logger->info('Webhook test successful.');
return;
} else if ($notification->changeType === 'paperless.update') {
//imprimir el webhook json
$this->logger->info('Webhook de paperless update: ' . json_encode($jsonData) . PHP_EOL);
}
// if (!$notification->clientId) {
// $this->logger->warning('No client specified, cannot notify them.');
@ -248,7 +271,7 @@ class Plugin
// }
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$config = $configManager->loadConfig();
// the "exportFormat" key must be defined in plugin's manifest file, see the link above
try {
@ -263,11 +286,8 @@ class Plugin
$payment_method_id = $notification->paymentData['methodId'];
//$this->logger->debug('Metodo de pago: ' . $notification->paymentData['methodId'] . PHP_EOL);
$payment_method = '';
switch ($payment_method_id) {
case '11721cdf-a498-48be-903e-daa67552e4f6':
@ -340,15 +360,63 @@ class Plugin
$payment_method = 'Desconocido, revisar metodos de pago no contemplados';
break;
}
} else if ($notification->eventName === 'client.edit') {
$this->logger->debug('Se actualiza a un cliente');
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
//ejemplo de json_data: {"uuid":"aacaf5c5-2bf4-44ea-864f-a24121b453bb","changeType":"edit","entity":"client","entityId":"171","eventName":"client.edit","extraData":{"entity":{"id":171,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-05-21T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Archi","lastName":"Isalas","username":"mainstreamm2@gmail.com","contacts":[{"id":177,"clientId":171,"email":"mainstreamm2@gmail.com","phone":"4181878106","name":null,"isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":198,"clientId":171,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_SM2zH6IsjTz6ol","clientZoneVisible":true},{"id":199,"clientId":171,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180950530868794","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":171,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-05-21T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Archi","lastName":"Isalas","username":"mainstreamm2@gmail.com","contacts":[{"id":177,"clientId":171,"email":"mainstreamm2@gmail.com","phone":"4181878106","name":null,"isBilling":false,"isContact":false,"types":[{"id":1003,"name":"WhatsNotifica"}]}],"attributes":[{"id":198,"clientId":171,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_SM2zH6IsjTz6ol","clientZoneVisible":true},{"id":199,"clientId":171,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180950530868794","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
$clientID = $jsonData['entityId'];
$this->ucrmApi = UcrmApi::create();
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']); //Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
$idPasswordAntenaCliente = null;
$passwordAntenaValue = null;
//ejemplo de $customAttributes: [{"id":1,"name":"ip","key":"ip","attributeType":"client","type":"string","clientZoneVisible":false},{"id":2,"name":"ubntpass","key":"ubntpass","attributeType":"client","type":"string","clientZoneVisible":false},{"id":3,"name":"adminpass","key":"adminpass","attributeType":"client","type":"string","clientZoneVisible":true},{"id":4,"name":"ssid","key":"ssid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":5,"name":"clavessid","key":"clavessid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":6,"name":"clave","key":"clave","attributeType":"client","type":"string","clientZoneVisible":true},{"id":11,"name":"latitud","key":"latitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":12,"name":"longitud","key":"longitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":16,"name":"instalador","key":"instalador","attributeType":"client","type":"string","clientZoneVisible":true},{"id":17,"name":"creado por","key":"creadoPor","attributeType":"client","type":"string","clientZoneVisible":true},{"id":21,"name":"Chat de CallBell","key":"chatDeCallbell","attributeType":"client","type":"string","clientZoneVisible":false},{"id":22,"name":"uuid","key":"uuid","attributeType":"client","type":"string","clientZoneVisible":false},{"id":23,"name":"zona","key":"zona","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":29,"name":"Stripe Customer ID","key":"stripeCustomerId","attributeType":"client","type":"string","clientZoneVisible":true},{"id":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","attributeType":"client","type":"string","clientZoneVisible":true},{"id":31,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":35,"name":"Site","key":"site","attributeType":"client","type":"string","clientZoneVisible":true},{"id":36,"name":"Antena/Sectorial","key":"antenaSectorial","attributeType":"client","type":"string","clientZoneVisible":true},{"id":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","attributeType":"client","type":"string","clientZoneVisible":false}]
// Verificar si se obtuvieron los atributos
if ($customAttributes && is_array($customAttributes)) {
foreach ($customAttributes as $attribute) {
// Verificar si 'name' contiene la palabra 'passwordAntenaCliente' sin distinguir mayúsculas y minúsculas
if (isset($attribute['key']) && stripos($attribute['key'], 'passwordAntenaCliente') !== false) {
$this->logger->info("ID correspondiente a 'passwordAntenaCliente': " . $attribute['id'] . PHP_EOL);
$idPasswordAntenaCliente = $attribute['id'];
}
}
} else {
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
//buscar en los attributes ($jsonData) del cliente el id del atributo personalizado 'passwordAntenaCliente' si no está o si está pero está en blanco se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
foreach ($jsonData['extraData']['entity']['attributes'] as $attribute) {
if ($attribute['customAttributeId'] === $idPasswordAntenaCliente) {
$this->logger->info("El valor de passwordAntenaValue es: " . $attribute['value'] . PHP_EOL);
$passwordAntenaValue = $attribute['value'];
}
}
//si el value de passwordAntenaValue es igual a null o cadena vacía se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
if ($passwordAntenaValue === null || $passwordAntenaValue === '') {
$password = $this->notifierFacade->getVaultCredentials($clientID);
$this->logger->info("El valor de passwordAntenaValue es null o cadena vacía, se manda llamar la función getVaultCredentials" . PHP_EOL);
$this->logger->info("El valor de password es: " . $password . PHP_EOL);
if ($this->notifierFacade->patchClientCustomAttribute($clientID, $idPasswordAntenaCliente, $password)) {
$this->logger->info("Se actualizó el atributo personalizado passwordAntenaCliente con el valor: " . $password . PHP_EOL);
} else {
$this->logger->info("No se pudo actualizar el atributo personalizado passwordAntenaCliente" . PHP_EOL);
}
} else {
$this->logger->info("Ya existe un valor de passwordAntenaValue: " . $passwordAntenaValue . PHP_EOL);
}
//ejemplo de json_data: {"uuid":"17e043a7-03b5-4312-ab81-a7818124a77e","changeType":"edit","entity":"client","entityId":"158","eventName":"client.edit","extraData":{"entity":{"id":158,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":"2025-02-09T03:15:49-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":158,"userIdent":null,"previousIsp":null,"isLead":true,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
// Validar que 'extraData' existe y contiene las claves necesarias
if (
@ -356,31 +424,30 @@ class Plugin
isset($jsonData['extraData']['entity'])
) {
$entityBeforeEdit = $jsonData['extraData']['entityBeforeEdit'];
$entity = $jsonData['extraData']['entity'];
$entity = $jsonData['extraData']['entity'];
$this->logger->debug('Validando claves dentro de entityBeforeEdit y entity');
//$this->logger->debug('Validando claves dentro de entityBeforeEdit y entity');
// Validar si 'isLead' esta en true en entityBeforeEdit y en false en entity
if (array_key_exists('isLead', $entityBeforeEdit) && array_key_exists('isLead', $entity)) {
$this->logger->debug('Los datos entityBeforeEdit y entity contienen el campo isLead');
//$this->logger->debug('Los datos entityBeforeEdit y entity contienen el campo isLead');
$isLeadBefore = $entityBeforeEdit['isLead'];
$isLeadAfter = $entity['isLead'];
$isLeadAfter = $entity['isLead'];
// Comprobar si 'isLead' cambió de true a false
if ($isLeadBefore === true && $isLeadAfter === false) {
$this->logger->debug('El cliente cambió de potencial a cliente');
$this->pluginNotifierFacade->createStripeClient($notification);
//$this->pluginNotifierFacade->createStripeClient($notification); //Se comenta esta línea para que no se cree el cliente en Stripe al convertir el lead a cliente, ya que se hará al agregar la etiqueta 'CREAR CLABE STRIPE'
} else {
$this->logger->debug('El cliente no cambió de potencial a cliente');
}
} else {
$this->logger->warning('El campo isLead no existe en entityBeforeEdit o entity');
}
// buscar si existe la etiqueta 'STRIPE' en entity pero no en entityBeforeEdit
$tags = $jsonData['extraData']['entity']['tags'];
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando claves dentro de entity y entityBeforeEdit');
@ -389,46 +456,42 @@ class Plugin
if (array_key_exists('tags', $jsonData['extraData']['entity']) && array_key_exists('tags', $jsonData['extraData']['entityBeforeEdit'])) {
$this->logger->debug('Los datos entity y entityBeforeEdit contienen el campo tags');
$tags = $jsonData['extraData']['entity']['tags'];
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando si la etiqueta STRIPE existe en entity pero no en entityBeforeEdit');
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
$stripeTagExists = false;
// Comprobar si la etiqueta 'CREAR CLABE STRIPE' existe en 'tags' pero no en 'tagsBefore'
$stripeTagExists = false;
$stripeTagExistsBefore = false;
foreach ($tags as $tag) {
if ($tag['name'] === 'CREARCLABESTRIPE') {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$stripeTagExists = true;
break;
}
}
foreach ($tagsBefore as $tag) {
if ($tag['name'] === 'CREARCLABESTRIPE') {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$stripeTagExistsBefore = true;
break;
}
}
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
if ($stripeTagExists && !$stripeTagExistsBefore) {
$this->logger->debug('La etiqueta STRIPE se agregará al cliente');
if ($stripeTagExists && ! $stripeTagExistsBefore) {
$this->logger->debug('La etiqueta CREAR CLABE STRIPE se agregará al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, true);
} else {
$this->logger->debug('La etiqueta STRIPE no se agregó al cliente');
}
} else {
$this->logger->warning('El campo tags no existe en entity o entityBeforeEdit');
}
} else {
} else {
$this->logger->warning('Los datos entityBeforeEdit o entity no están presentes en extraData');
}
$this->notifierFacade->verifyClientActionToDo($notification);
} else if ($notification->eventName === 'client.add') {
$this->logger->debug('Se agregó un nuevo cliente' . PHP_EOL);
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
@ -453,6 +516,56 @@ class Plugin
} else if ($notification->eventName === 'service.edit') {
$this->logger->debug('Se editó el servicio a un cliente' . PHP_EOL);
$this->notifierFacade->verifyServiceActionToDo($notification);
//ejemplo de json_data: {"uuid":"06d281ca-d78e-4f0a-a282-3a6b77d25da0","changeType":"edit","entity":"service","entityId":"155","eventName":"service.edit","extraData":{"entity":{"id":155,"prepaid":false,"clientId":171,"status":1,"name":"Basico 300","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","note":null,"addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"servicePlanId":6,"servicePlanPeriodId":26,"price":300,"hasIndividualPrice":false,"totalPrice":300,"currencyCode":"MXN","invoiceLabel":null,"contractId":null,"contractLengthType":1,"minimumContractLengthMonths":null,"activeFrom":"2025-05-21T00:00:00-0600","activeTo":null,"contractEndDate":null,"discountType":0,"discountValue":null,"discountInvoiceLabel":"Descuento","discountFrom":null,"discountTo":null,"tax1Id":null,"tax2Id":null,"tax3Id":null,"invoicingStart":"2025-05-21T00:00:00-0600","invoicingPeriodType":1,"invoicingPeriodStartDay":1,"nextInvoicingDayAdjustment":10,"invoicingProratedSeparately":true,"invoicingSeparately":false,"sendEmailsAutomatically":null,"useCreditAutomatically":true,"servicePlanName":"Basico 300","servicePlanPrice":300,"servicePlanPeriod":1,"servicePlanType":"Internet","downloadSpeed":8,"uploadSpeed":8,"hasOutage":false,"unmsClientSiteStatus":null,"fccBlockId":null,"lastInvoicedDate":null,"unmsClientSiteId":"359cb58d-e64f-453a-890e-23d5abb4f116","attributes":[],"addressData":null,"suspensionReasonId":null,"serviceChangeRequestId":null,"setupFeePrice":null,"earlyTerminationFeePrice":null,"downloadSpeedOverride":null,"uploadSpeedOverride":null,"trafficShapingOverrideEnd":null,"trafficShapingOverrideEnabled":false,"servicePlanGroupId":null,"suspensionPeriods":[],"surcharges":[]},"entityBeforeEdit":{"id":155,"prepaid":false,"clientId":171,"status":1,"name":"Basico 300","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","note":null,"addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"servicePlanId":6,"servicePlanPeriodId":26,"price":300,"hasIndividualPrice":false,"totalPrice":300,"currencyCode":"MXN","invoiceLabel":null,"contractId":null,"contractLengthType":1,"minimumContractLengthMonths":null,"activeFrom":"2025-05-21T00:00:00-0600","activeTo":null,"contractEndDate":null,"discountType":0,"discountValue":null,"discountInvoiceLabel":"Descuento","discountFrom":null,"discountTo":null,"tax1Id":null,"tax2Id":null,"tax3Id":null,"invoicingStart":"2025-05-21T00:00:00-0600","invoicingPeriodType":1,"invoicingPeriodStartDay":1,"nextInvoicingDayAdjustment":10,"invoicingProratedSeparately":true,"invoicingSeparately":false,"sendEmailsAutomatically":null,"useCreditAutomatically":true,"servicePlanName":"Basico 300","servicePlanPrice":300,"servicePlanPeriod":1,"servicePlanType":"Internet","downloadSpeed":8,"uploadSpeed":8,"hasOutage":false,"unmsClientSiteStatus":null,"fccBlockId":null,"lastInvoicedDate":null,"unmsClientSiteId":"359cb58d-e64f-453a-890e-23d5abb4f116","attributes":[],"addressData":null,"suspensionReasonId":null,"serviceChangeRequestId":null,"setupFeePrice":null,"earlyTerminationFeePrice":null,"downloadSpeedOverride":null,"uploadSpeedOverride":null,"trafficShapingOverrideEnd":null,"trafficShapingOverrideEnabled":false,"servicePlanGroupId":null,"suspensionPeriods":[],"surcharges":[]}}}
//obtener el clientID y asginarlo a la variable $clientID
$clientID = $jsonData['extraData']['entity']['clientId'];
$this->ucrmApi = UcrmApi::create();
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']); //Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
$idPasswordAntenaCliente = null;
$passwordAntenaValue = null;
//ejemplo de $customAttributes: [{"id":1,"name":"ip","key":"ip","attributeType":"client","type":"string","clientZoneVisible":false},{"id":2,"name":"ubntpass","key":"ubntpass","attributeType":"client","type":"string","clientZoneVisible":false},{"id":3,"name":"adminpass","key":"adminpass","attributeType":"client","type":"string","clientZoneVisible":true},{"id":4,"name":"ssid","key":"ssid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":5,"name":"clavessid","key":"clavessid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":6,"name":"clave","key":"clave","attributeType":"client","type":"string","clientZoneVisible":true},{"id":11,"name":"latitud","key":"latitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":12,"name":"longitud","key":"longitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":16,"name":"instalador","key":"instalador","attributeType":"client","type":"string","clientZoneVisible":true},{"id":17,"name":"creado por","key":"creadoPor","attributeType":"client","type":"string","clientZoneVisible":true},{"id":21,"name":"Chat de CallBell","key":"chatDeCallbell","attributeType":"client","type":"string","clientZoneVisible":false},{"id":22,"name":"uuid","key":"uuid","attributeType":"client","type":"string","clientZoneVisible":false},{"id":23,"name":"zona","key":"zona","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":29,"name":"Stripe Customer ID","key":"stripeCustomerId","attributeType":"client","type":"string","clientZoneVisible":true},{"id":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","attributeType":"client","type":"string","clientZoneVisible":true},{"id":31,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":35,"name":"Site","key":"site","attributeType":"client","type":"string","clientZoneVisible":true},{"id":36,"name":"Antena/Sectorial","key":"antenaSectorial","attributeType":"client","type":"string","clientZoneVisible":true},{"id":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","attributeType":"client","type":"string","clientZoneVisible":false}]
// Verificar si se obtuvieron los atributos
if ($customAttributes && is_array($customAttributes)) {
foreach ($customAttributes as $attribute) {
// Verificar si 'name' contiene la palabra 'passwordAntenaCliente' sin distinguir mayúsculas y minúsculas
if (isset($attribute['key']) && stripos($attribute['key'], 'passwordAntenaCliente') !== false) {
$this->logger->info("ID correspondiente a 'passwordAntenaCliente': " . $attribute['id'] . PHP_EOL);
$idPasswordAntenaCliente = $attribute['id'];
}
}
} else {
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
//buscar en los attributes ($jsonData) del cliente el id del atributo personalizado 'passwordAntenaCliente' si no está o si está pero está en blanco se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
foreach ($jsonData['extraData']['entity']['attributes'] as $attribute) {
if ($attribute['customAttributeId'] === $idPasswordAntenaCliente) {
$this->logger->info("El valor de passwordAntenaValue es: " . $attribute['value'] . PHP_EOL);
$passwordAntenaValue = $attribute['value'];
}
}
//si el value de passwordAntenaValue es igual a null o cadena vacía se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
if ($passwordAntenaValue === null || $passwordAntenaValue === '') {
$password = $this->notifierFacade->getVaultCredentials($clientID);
$this->logger->info("El valor de passwordAntenaValue es null o cadena vacía, se manda llamar la función getVaultCredentials" . PHP_EOL);
$this->logger->info("El valor de password es: " . $password . PHP_EOL);
if ($this->notifierFacade->patchClientCustomAttribute($clientID, $idPasswordAntenaCliente, $password)) {
$this->logger->info("Se actualizó el atributo personalizado passwordAntenaCliente con el valor: " . $password . PHP_EOL);
} else {
$this->logger->info("No se pudo actualizar el atributo personalizado passwordAntenaCliente" . PHP_EOL);
}
} else {
$this->logger->info("Ya existe un valor de passwordAntenaValue: " . $passwordAntenaValue . PHP_EOL);
}
} else if ($notification->eventName === 'service.suspend') {
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
$this->notifierFacade->verifyServiceActionToDo($notification);
@ -494,28 +607,21 @@ class Plugin
$this->notifierFacade->verifyInvoiceActionToDo($notification);
} else if ($notification->eventName === 'job.add') {
$this->logger->debug('Se ha agregado un nuevo trabajo' . PHP_EOL);
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
//Ejemplo de json_data: {"uuid":"434b3da0-984a-4358-a1b6-2a4418bacc49","changeType":"insert","entity":"job","entityId":"38","eventName":"job.add","extraData":{"entity":{"id":38,"title":"Servicio","description":"Revisar Router","assignedUserId":null,"clientId":2,"date":null,"duration":60,"status":0,"address":"31 Chiapas, Dolores Hidalgo Cuna de la Independencia Nacional, 37800, Mexico","gpsLat":null,"gpsLon":null,"attachments":[],"tasks":[]},"entityBeforeEdit":null}}
//Extraer el valor de title en una variable y concatenarle como prefijo la cadena "[SINENVIONOTIFICACION]" por ejemplo: "[NOTIFICACION-PENDIENTE]Servicio"
$title = $jsonData['extraData']['entity']['title'];
$title = '[NOTIFICACION-PENDIENTE]' . $title;
$this->ucrmApi = UcrmApi::create();
$responsePatch = $this->ucrmApi->patch('scheduling/jobs/' . $jsonData['entityId'], [
'title' => $title,
]);
// Verificar que existen tanto 'assignedUserId' como 'entity'
if (isset($jsonData['extraData']['entity']['assignedUserId'])) {
$this->logger->debug('El campo assignedUserId existe en los datos del evento');
$assignedUserId = $jsonData['extraData']['entity']['assignedUserId'];
// Comprobar si 'isLead' es null
if ($assignedUserId === null) {
$this->logger->debug('El campo assignedUserId es null');
$this->pluginNotifierFacade->createStripeClient($notification);
} else {
$this->logger->debug('El campo assignedUserId no es null');
$this->notifierFacade->verifyJobActionToDo($notification);
}
} else {
$this->logger->warning('El campo assignedUserId no existe en los datos del evento');
}
$this->logger->debug('Respuesta de la API al agregar el trabajo: ' . json_encode($responsePatch) . PHP_EOL);
} else if ($notification->eventName === 'job.edit') {
$this->logger->debug('Se actualiza un trabajo' . PHP_EOL);
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
// $this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
// Validar que 'extraData' existe y contiene las claves necesarias
if (
@ -523,39 +629,54 @@ class Plugin
isset($jsonData['extraData']['entity'])
) {
$entityBeforeEdit = $jsonData['extraData']['entityBeforeEdit'];
$entity = $jsonData['extraData']['entity'];
$entity = $jsonData['extraData']['entity'];
$this->logger->debug('Validando claves dentro de entityBeforeEdit y entity');
// Validar que 'assignedUserId' existe en ambas entidades
if (array_key_exists('assignedUserId', $entityBeforeEdit) && array_key_exists('assignedUserId', $entity)) {
$this->logger->debug('Los datos entityBeforeEdit y entity contienen el campo assignedUserId');
// $this->logger->debug('Los datos entityBeforeEdit y entity contienen el campo assignedUserId');
$assignedUserIdBefore = $entityBeforeEdit['assignedUserId'];
$assignedUserIdAfter = $entity['assignedUserId'];
$dateBefore = $entityBeforeEdit['date'];
$dateAfter = $entity['date'];
$statusBefore = $entityBeforeEdit['status'];
$statusAfter = $entity['status'];
$assignedUserIdAfter = $entity['assignedUserId'];
$dateBefore = $entityBeforeEdit['date'];
$dateAfter = $entity['date'];
$statusBefore = $entityBeforeEdit['status'];
$statusAfter = $entity['status'];
$title = $entityBeforeEdit['title'];
$pendingPrefix = '[NOTIFICACION-PENDIENTE]'; // Prefijo para trabajos pendientes de notificación
$currentTitle = $entity['title'] ?? ''; // Obtener el título actual
// Comprobar si 'assignedUserId' cambió
if ($assignedUserIdBefore === null && $assignedUserIdAfter != null) { //Si el campo "assignedUserId" cambió de null a un valor
$this->logger->debug('El instalador cambió de null a un valor');
$this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore === $dateAfter)) {//Si el campo "assignedUserId" cambió de un valor a otro y la fecha no cambió
$this->logger->debug('El instalador cambió y la fecha no cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, true); // Se envía notificación de trabajo reasignado
} else if (($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter) && ($dateBefore != $dateAfter)) {//Si el campo "assignedUserId" no cambió y la fecha cambió
$this->logger->debug('El instalador no cambió y la fecha sí cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, false); // Se envía notificación de reprogramación de trabajo
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore != $dateAfter)) {
$this->logger->debug('El instalador cambió y la fecha sí cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, true); // Se envía notificación de trabajo reasignado
} else if ($assignedUserIdBefore != null && $assignedUserIdAfter === null) { //Si el campo "assignedUserId" cambió de un valor a null
$this->logger->debug('El instalador cambió de un valor a null');
$this->notifierFacade->verifyJobActionToDo($jsonData, null, true); // Se envía notificación de trabajo desasignado
} else {
$this->logger->debug('No hubo cambio en el instalador ni en la fecha');
//Valores de status y su significado: 0 = abierto, 1= En curso, 2 = Cerrado
if ($statusAfter == 1) {
// Comprobar si 'assignedUserId' cambió
// if (($assignedUserIdBefore === null && $assignedUserIdAfter != null) || ($statusBefore == 0 && $statusAfter == 1)) { //Si el campo "assignedUserId" cambió de null a un valor
// $this->logger->debug('El instalador cambió de null a un valor');
// $this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
// }
//Si el campo status cambió de 0 a 1 y title comienza con el prefijo [NOTIFICACION-PENDIENTE]
if (($statusBefore == 0 && $statusAfter == 1) && strpos($currentTitle, $pendingPrefix) !== false) { // Se envía notificación de trabajo asignado
$this->logger->debug('El instalador cambió de null a un valor');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, false);
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore === $dateAfter)) { //Si el campo "assignedUserId" cambió de un valor a otro y la fecha no cambió
$this->logger->debug('No hay reprogramación de trabajo pero si hay cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, true); // Se envía notificación de trabajo reasignado
} else if (($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter) && ($dateBefore != $dateAfter)) { //Si el campo "assignedUserId" no cambió y la fecha cambió
$this->logger->debug('Se reprogramó el trabajo pero no hubo cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, false); // Se envía notificación de reprogramación de trabajo
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore != $dateAfter)) {
$this->logger->debug('Se reprogramó el trabajo y hubo cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, true); // Se envía notificación de trabajo reasignado
} else if ($assignedUserIdBefore != null && $assignedUserIdAfter === null) { //Si el campo "assignedUserId" cambió de un valor a null
$this->logger->debug('El instalador cambió de un valor a null');
$this->notifierFacade->verifyJobActionToDo($jsonData, null, true); // Se envía notificación de trabajo desasignado
} else {
$this->logger->debug('No hubo cambio en el instalador ni en la fecha');
//$this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
}
}
} else {
@ -567,8 +688,6 @@ class Plugin
}
//$this->notifierFacade->update($notification);
} catch (TwilioException $exception) {
$this->logger->error($exception->getMessage());
@ -577,4 +696,4 @@ class Plugin
$this->logger->info($ex->getTraceAsString());
}
}
}
}

View File

@ -1 +1 @@
{"ucrmPublicUrl":"https://venus.siip.mx/crm/","ucrmLocalUrl":"http://localhost/crm/","unmsLocalUrl":"http://unms:8081/nms/","pluginPublicUrl":"https://venus.siip.mx/crm/_plugins/siip-whatsapp-notifications/public.php","pluginAppKey":"5dVI22XIa9NSK0tiDeiQ3SLDBa1UZVT7g5s/tJzi944Bygx89WIHH7I2/niVPiBK","pluginId":16}
{"ucrmPublicUrl":"https://venus.siip.mx/crm/","ucrmLocalUrl":"http://localhost/crm/","unmsLocalUrl":"http://unms-api:8081/nms/","pluginPublicUrl":"https://venus.siip.mx/crm/_plugins/siip-whatsapp-notifications/public.php","pluginAppKey":"5dVI22XIa9NSK0tiDeiQ3SLDBa1UZVT7g5s/tJzi944Bygx89WIHH7I2/niVPiBK","pluginId":16}

View File

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