corregido doble payment intent y reenvio de comprobantes
This commit is contained in:
parent
d2ce14a7e3
commit
24c32f6334
@ -1 +1 @@
|
|||||||
{"ipserver":"venus.siip.mx","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":"4100","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":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\": \"Á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,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo","logging_level":true}
|
{"ipserver":"venus.siip.mx","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":"4100","idPaymentAdminCRM":"1180","cashPaymentMethodId":true,"courtesyPaymentMethodId":true,"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\": \"Á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,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo","logging_level":true,"oxxoPayPaymentMethodId":true,"creditDebitCardPaymentMethodId":true}
|
||||||
12715
data/plugin.log
12715
data/plugin.log
File diff suppressed because one or more lines are too long
@ -189,6 +189,20 @@
|
|||||||
"required": 0,
|
"required": 0,
|
||||||
"type": "checkbox"
|
"type": "checkbox"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "oxxoPayPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de OXXO Pay",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es OXXO Pay",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "creditDebitCardPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de Tarjeta de Crédito/Débito (Genérico)",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es Tarjeta de Crédito/Débito (Genérico)",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "notificationTypeText",
|
"key": "notificationTypeText",
|
||||||
"label": "Envío de Comprobante por medio de plantilla de texto",
|
"label": "Envío de Comprobante por medio de plantilla de texto",
|
||||||
|
|||||||
@ -21,27 +21,29 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
protected $ucrmApi;
|
protected $ucrmApi;
|
||||||
private $systemAttributesCache = null;
|
private $systemAttributesCache = null;
|
||||||
|
|
||||||
public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber) {
|
public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber)
|
||||||
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->messageTextFactory = $messageTextFactory;
|
$this->messageTextFactory = $messageTextFactory;
|
||||||
$this->clientPhoneNumber = $clientPhoneNumber;
|
$this->clientPhoneNumber = $clientPhoneNumber;
|
||||||
|
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$ipServer = $config['ipserver'] ?? 'localhost';
|
$ipServer = $config['ipserver'] ?? 'localhost';
|
||||||
$apiUrl = "https://$ipServer/crm/api/v1.0/";
|
$apiUrl = "https://$ipServer/crm/api/v1.0/";
|
||||||
|
|
||||||
$client = new Client([
|
$client = new Client([
|
||||||
'base_uri' => $apiUrl,
|
'base_uri' => $apiUrl,
|
||||||
'verify' => false,
|
'verify' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->ucrmApi = new UcrmApi($client, $config['apitoken'] ?? '');
|
$this->ucrmApi = new UcrmApi($client, $config['apitoken'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPaymentIntent(array $eventJson): void {
|
public function createPaymentIntent(array $eventJson): void
|
||||||
|
{
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$stripe = new StripeClient($config['tokenstripe']);
|
$stripe = new StripeClient($config['tokenstripe']);
|
||||||
|
|
||||||
$customer = $eventJson['data']['object']['customer'] ?? null;
|
$customer = $eventJson['data']['object']['customer'] ?? null;
|
||||||
$amount = $eventJson['data']['object']['net_amount'] ?? 0;
|
$amount = $eventJson['data']['object']['net_amount'] ?? 0;
|
||||||
|
|
||||||
@ -54,6 +56,39 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
$stripeCustomer = $stripe->customers->retrieve($customer);
|
$stripeCustomer = $stripe->customers->retrieve($customer);
|
||||||
$ucrmClientId = $stripeCustomer->metadata->ucrm_client_id ?? null;
|
$ucrmClientId = $stripeCustomer->metadata->ucrm_client_id ?? null;
|
||||||
|
|
||||||
|
// [NEW] Check for existing PENDING PaymentIntent to avoid duplicates
|
||||||
|
// Especially useful if webhook is retried or if one was already created.
|
||||||
|
$existingPIs = $stripe->paymentIntents->search([
|
||||||
|
'query' => "customer:'$customer' AND status:'requires_payment_method' AND amount>=" . ((int)$amount - 100) . " AND amount<=" . ((int)$amount + 100) . " AND metadata['tipoPago']:'Transferencia Bancaria'",
|
||||||
|
'limit' => 1
|
||||||
|
]);
|
||||||
|
// Note: Range check just in case, or exact check. Using exact check is safer if amount is precise.
|
||||||
|
// Let's use exact check for now, but sometimes small variations happen? No, Bank Transfer is exact.
|
||||||
|
// Actually, 'requires_payment_method' or 'requires_action' or 'processing'?
|
||||||
|
// If it's bank transfer funded, it might be in 'requires_confirmation' if not auto-confirmed.
|
||||||
|
// But if we are CREATING it, we want to know if we already created one that is waiting.
|
||||||
|
// If we created it with confirm=true, it should transition to succeeded immediately if funds are available (which they are, cause this event says so).
|
||||||
|
// However, IF the previous attempt failed or timed out but created the PI...
|
||||||
|
|
||||||
|
// BETTER: Check if we have processed this Event ID before?
|
||||||
|
// The method doesn't receive Event ID in the args easily (it's in $eventJson['id']).
|
||||||
|
// But we don't store Event IDs in DB.
|
||||||
|
|
||||||
|
// Let's stick to checking if there is a PI created recently (last 5 mins?) with same amount?
|
||||||
|
// Stripe Search API is powerful.
|
||||||
|
// query: "customer:'$customer' AND amount=$amount AND created>" . (time() - 300)
|
||||||
|
|
||||||
|
$fiveMinsAgo = time() - 300;
|
||||||
|
$existingPIs = $stripe->paymentIntents->search([
|
||||||
|
'query' => "customer:'$customer' AND amount=" . (int)$amount . " AND created>$fiveMinsAgo AND metadata['createdBy']:'UCRM'",
|
||||||
|
'limit' => 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (count($existingPIs->data) > 0) {
|
||||||
|
$this->logger->info("PaymentIntent duplicado evitado. Ya existe uno reciente tras el evento de fondos. ID: " . $existingPIs->data[0]->id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$pi = $stripe->paymentIntents->create([
|
$pi = $stripe->paymentIntents->create([
|
||||||
'amount' => (int)$amount,
|
'amount' => (int)$amount,
|
||||||
'currency' => 'mxn',
|
'currency' => 'mxn',
|
||||||
@ -73,18 +108,19 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
'signedInAdminId' => $config['idPaymentAdminCRM'],
|
'signedInAdminId' => $config['idPaymentAdminCRM'],
|
||||||
'tipoPago' => 'Transferencia Bancaria'
|
'tipoPago' => 'Transferencia Bancaria'
|
||||||
],
|
],
|
||||||
]);
|
], ['idempotency_key' => $eventJson['id'] ?? null]);
|
||||||
$this->logger->info("PaymentIntent creado: " . $pi->id);
|
$this->logger->info("PaymentIntent creado: " . $pi->id);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error("Error creando PaymentIntent: " . $e->getMessage());
|
$this->logger->error("Error creando PaymentIntent: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPaymentFromWebhook(array $eventJson): void {
|
public function registerPaymentFromWebhook(array $eventJson): void
|
||||||
|
{
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$stripe = new StripeClient($config['tokenstripe']);
|
$stripe = new StripeClient($config['tokenstripe']);
|
||||||
$data = $eventJson['data']['object'];
|
$data = $eventJson['data']['object'];
|
||||||
|
|
||||||
$piId = $data['applied_to_payment']['payment_intent'] ?? null;
|
$piId = $data['applied_to_payment']['payment_intent'] ?? null;
|
||||||
if (!$piId) return;
|
if (!$piId) return;
|
||||||
|
|
||||||
@ -110,18 +146,19 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerPaymentFromIntent(array $data): void {
|
public function registerPaymentFromIntent(array $data): void
|
||||||
|
{
|
||||||
$piId = $data['id'] ?? null;
|
$piId = $data['id'] ?? null;
|
||||||
if (!$piId) return;
|
if (!$piId) return;
|
||||||
|
|
||||||
if (($data['status'] ?? '') !== 'succeeded') {
|
if (($data['status'] ?? '') !== 'succeeded') {
|
||||||
$this->logger->warning("PaymentIntent $piId no está succeeded (status: " . ($data['status']??'unknown') . ")");
|
$this->logger->warning("PaymentIntent $piId no está succeeded (status: " . ($data['status'] ?? 'unknown') . ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buscar clientId en metadata (prioridad: clientId > ucrm_client_id)
|
// Buscar clientId en metadata (prioridad: clientId > ucrm_client_id)
|
||||||
$clientId = $data['metadata']['clientId'] ?? $data['metadata']['ucrm_client_id'] ?? null;
|
$clientId = $data['metadata']['clientId'] ?? $data['metadata']['ucrm_client_id'] ?? null;
|
||||||
|
|
||||||
// Fallback: Buscar por Stripe Customer ID si no hay metadata
|
// Fallback: Buscar por Stripe Customer ID si no hay metadata
|
||||||
if (!$clientId && !empty($data['customer'])) {
|
if (!$clientId && !empty($data['customer'])) {
|
||||||
$clientId = $this->findClientIdByStripeCustomer($data['customer']);
|
$clientId = $this->findClientIdByStripeCustomer($data['customer']);
|
||||||
@ -135,15 +172,40 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [NEW] Check for duplicate payment (same PI ID)
|
||||||
|
try {
|
||||||
|
$existingPayments = $this->ucrmApi->get('payments', [
|
||||||
|
'clientId' => $clientId,
|
||||||
|
'limit' => 20,
|
||||||
|
'order' => 'createdDate',
|
||||||
|
'direction' => 'DESC'
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($existingPayments as $p) {
|
||||||
|
// Check note for PI ID
|
||||||
|
if (isset($p['note']) && strpos($p['note'], $piId) !== false) {
|
||||||
|
$this->logger->info("Pago duplicado detectado para PI $piId (ID existente: {$p['id']}). Omitiendo creación.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Also check duplicate by transaction ID if applicable
|
||||||
|
if (isset($p['transactionId']) && $p['transactionId'] === $piId) {
|
||||||
|
$this->logger->info("Pago duplicado detectado (Transaction ID) para PI $piId (ID existente: {$p['id']}). Omitiendo creación.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->warning("Falló la verificación de duplicados para PI $piId: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Intentar detectar Payment Method name basado en tipo
|
// Intentar detectar Payment Method name basado en tipo
|
||||||
$type = $data['payment_method_types'][0] ?? 'card';
|
$type = $data['payment_method_types'][0] ?? 'card';
|
||||||
$methodSearchName = ($type === 'oxxo') ? 'OXXO' : 'Stripe';
|
$methodSearchName = ($type === 'oxxo') ? 'OXXO' : 'Stripe';
|
||||||
|
|
||||||
// Buscar ID de metodo en UCRM
|
// Buscar ID de metodo en UCRM
|
||||||
$methodId = $this->findPaymentMethodId($methodSearchName);
|
$methodId = $this->findPaymentMethodId($methodSearchName);
|
||||||
if (!$methodId && $methodSearchName === 'OXXO') {
|
if (!$methodId && $methodSearchName === 'OXXO') {
|
||||||
$methodId = $this->findPaymentMethodId('Stripe'); // Fallback
|
$methodId = $this->findPaymentMethodId('Stripe'); // Fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($methodId) {
|
if ($methodId) {
|
||||||
@ -156,7 +218,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
'note' => "Stripe ($methodSearchName) - PI: $piId",
|
'note' => "Stripe ($methodSearchName) - PI: $piId",
|
||||||
'createdDate' => date('c'),
|
'createdDate' => date('c'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->logger->info("Pago registrado en UCRM vía PI Succeeded: $piId, Cliente: $clientId, Monto: $checkAmount");
|
$this->logger->info("Pago registrado en UCRM vía PI Succeeded: $piId, Cliente: $clientId, Monto: $checkAmount");
|
||||||
} else {
|
} else {
|
||||||
$this->logger->warning("No se encontró método de pago para '$methodSearchName' en UCRM.");
|
$this->logger->warning("No se encontró método de pago para '$methodSearchName' en UCRM.");
|
||||||
@ -166,7 +228,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findClientIdByStripeCustomer(string $stripeCustomerId): ?int {
|
private function findClientIdByStripeCustomer(string $stripeCustomerId): ?int
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
// Nota: Esto puede ser lento si hay muchos clientes, pero es un fallback.
|
// Nota: Esto puede ser lento si hay muchos clientes, pero es un fallback.
|
||||||
// Idealmente usaríamos $this->ucrmApi->get('clients', ['customAttributeKey' => 'stripeCustomerId', ...]) si existiera ese filtro.
|
// Idealmente usaríamos $this->ucrmApi->get('clients', ['customAttributeKey' => 'stripeCustomerId', ...]) si existiera ese filtro.
|
||||||
@ -177,7 +240,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
// Dado que no queremos matar el server, limitaremos la búsqueda o asumiremos que el FIX de metadata es el principal.
|
// Dado que no queremos matar el server, limitaremos la búsqueda o asumiremos que el FIX de metadata es el principal.
|
||||||
// Sin embargo, para este caso específico, vamos a intentar buscar en los clientes recientes o usar una búsqueda básica.
|
// Sin embargo, para este caso específico, vamos a intentar buscar en los clientes recientes o usar una búsqueda básica.
|
||||||
// UCRM API v1.0 GET /clients soporta ?customAttributeId&customAttributeValue
|
// UCRM API v1.0 GET /clients soporta ?customAttributeId&customAttributeValue
|
||||||
|
|
||||||
$attrId = $this->resolveAttributeId('stripeCustomerId');
|
$attrId = $this->resolveAttributeId('stripeCustomerId');
|
||||||
if (!$attrId) return null;
|
if (!$attrId) return null;
|
||||||
|
|
||||||
@ -195,19 +258,22 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findPaymentMethodId(string $name): ?int {
|
private function findPaymentMethodId(string $name): ?int
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$methods = $this->ucrmApi->get('payment-methods');
|
$methods = $this->ucrmApi->get('payment-methods');
|
||||||
foreach ($methods as $m) {
|
foreach ($methods as $m) {
|
||||||
if (stripos($m['name'], $name) !== false) {
|
if (stripos($m['name'], $name) !== false) {
|
||||||
return $m['id'];
|
return $m['id'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(\Exception $e) {}
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createStripeClient(NotificationData $notificationData, string $tagName, bool $generateSpei = true): void {
|
public function createStripeClient(NotificationData $notificationData, string $tagName, bool $generateSpei = true): void
|
||||||
|
{
|
||||||
$clientId = $notificationData->clientId;
|
$clientId = $notificationData->clientId;
|
||||||
if (!$clientId) return;
|
if (!$clientId) return;
|
||||||
|
|
||||||
@ -216,7 +282,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$clientCRM = $this->ucrmApi->get("clients/$clientId", []);
|
$clientCRM = $this->ucrmApi->get("clients/$clientId", []);
|
||||||
|
|
||||||
// Si intenta crear CLABE pero NO tiene stripeCustomerId, cancelamos
|
// Si intenta crear CLABE pero NO tiene stripeCustomerId, cancelamos
|
||||||
if ($tagName === 'CREAR CLABE STRIPE') {
|
if ($tagName === 'CREAR CLABE STRIPE') {
|
||||||
$hasStripeId = false;
|
$hasStripeId = false;
|
||||||
@ -234,7 +300,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
|
|
||||||
$customer = $this->createCustomerStripe($stripe, $clientCRM, $generateSpei);
|
$customer = $this->createCustomerStripe($stripe, $clientCRM, $generateSpei);
|
||||||
|
|
||||||
if ($customer) {
|
if ($customer) {
|
||||||
$this->logger->info("Cliente Stripe procesado para ID: $clientId (Tag: $tagName, SPEI: " . ($generateSpei ? 'SI' : 'NO') . ")");
|
$this->logger->info("Cliente Stripe procesado para ID: $clientId (Tag: $tagName, SPEI: " . ($generateSpei ? 'SI' : 'NO') . ")");
|
||||||
}
|
}
|
||||||
@ -246,9 +312,10 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createCustomerStripe(StripeClient $stripe, array $clientCRM, bool $generateSpei): ?\Stripe\Customer {
|
protected function createCustomerStripe(StripeClient $stripe, array $clientCRM, bool $generateSpei): ?\Stripe\Customer
|
||||||
|
{
|
||||||
$clientId = $clientCRM['id'];
|
$clientId = $clientCRM['id'];
|
||||||
|
|
||||||
// Extraer email de contactos (prioridad) o username
|
// Extraer email de contactos (prioridad) o username
|
||||||
$email = $clientCRM['username'] ?? null;
|
$email = $clientCRM['username'] ?? null;
|
||||||
foreach ($clientCRM['contacts'] ?? [] as $contact) {
|
foreach ($clientCRM['contacts'] ?? [] as $contact) {
|
||||||
@ -266,7 +333,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
// Obtener IDs de atributos dinámicamente desde el sistema
|
// Obtener IDs de atributos dinámicamente desde el sistema
|
||||||
$cidAttrId = $this->resolveAttributeId('stripeCustomerId');
|
$cidAttrId = $this->resolveAttributeId('stripeCustomerId');
|
||||||
$clabeAttrId = $this->resolveAttributeId('clabeInterbancaria');
|
$clabeAttrId = $this->resolveAttributeId('clabeInterbancaria');
|
||||||
|
|
||||||
$this->logger->debug("IDs de atributos resueltos: Stripe=$cidAttrId, CLABE=$clabeAttrId");
|
$this->logger->debug("IDs de atributos resueltos: Stripe=$cidAttrId, CLABE=$clabeAttrId");
|
||||||
|
|
||||||
// Buscar cliente existente por metadata
|
// Buscar cliente existente por metadata
|
||||||
@ -288,7 +355,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
'name' => $name,
|
'name' => $name,
|
||||||
'metadata' => ['ucrm_client_id' => $clientId]
|
'metadata' => ['ucrm_client_id' => $clientId]
|
||||||
];
|
];
|
||||||
|
|
||||||
$customer = $stripe->customers->create($params);
|
$customer = $stripe->customers->create($params);
|
||||||
$this->logger->info("Nuevo Cliente Stripe creado para ID: $clientId. CID: {$customer->id}");
|
$this->logger->info("Nuevo Cliente Stripe creado para ID: $clientId. CID: {$customer->id}");
|
||||||
// Guardar CID en UCRM
|
// Guardar CID en UCRM
|
||||||
@ -309,7 +376,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
);
|
);
|
||||||
|
|
||||||
$clabe = $fundingInstructions['bank_transfer']['financial_addresses'][0]['spei']['clabe'] ?? null;
|
$clabe = $fundingInstructions['bank_transfer']['financial_addresses'][0]['spei']['clabe'] ?? null;
|
||||||
|
|
||||||
if ($clabe) {
|
if ($clabe) {
|
||||||
$this->logger->info("CLABE obtenida via Funding Instructions para cliente $clientId: $clabe");
|
$this->logger->info("CLABE obtenida via Funding Instructions para cliente $clientId: $clabe");
|
||||||
$this->patchClientCustomAttribute($clientId, (int)$clabeAttrId, $clabe);
|
$this->patchClientCustomAttribute($clientId, (int)$clabeAttrId, $clabe);
|
||||||
@ -324,10 +391,11 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return $customer;
|
return $customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getVaultCredentialsByClientId($clientId): string {
|
protected function getVaultCredentialsByClientId($clientId): string
|
||||||
|
{
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$ipServer = $config['ipserver'] ?? '';
|
$ipServer = $config['ipserver'] ?? '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// OPT: Lazy Check - Si ya tiene pass válido en CRM, no hace falta procesar nada
|
// OPT: Lazy Check - Si ya tiene pass válido en CRM, no hace falta procesar nada
|
||||||
$clientData = $this->ucrmApi->get("clients/$clientId");
|
$clientData = $this->ucrmApi->get("clients/$clientId");
|
||||||
@ -348,7 +416,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
|
|
||||||
// 1. Obtener los servicios del cliente
|
// 1. Obtener los servicios del cliente
|
||||||
$svcs = $this->ucrmApi->get('clients/services', ['clientId' => $clientId]);
|
$svcs = $this->ucrmApi->get('clients/services', ['clientId' => $clientId]);
|
||||||
|
|
||||||
if (empty($svcs)) {
|
if (empty($svcs)) {
|
||||||
$msg = '⚠️ Cliente sin servicios/antenas';
|
$msg = '⚠️ Cliente sin servicios/antenas';
|
||||||
$this->syncPasswordWithCrm((int)$clientId, $msg);
|
$this->syncPasswordWithCrm((int)$clientId, $msg);
|
||||||
@ -415,7 +483,9 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
$passVault = $vault['credentials'][0]['password'];
|
$passVault = $vault['credentials'][0]['password'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) { continue; }
|
} catch (\Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($passVault) {
|
if ($passVault) {
|
||||||
@ -429,7 +499,9 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
'json' => [['username' => 'ubnt', 'password' => $newPass, 'readOnly' => true]]
|
'json' => [['username' => 'ubnt', 'password' => $newPass, 'readOnly' => true]]
|
||||||
]);
|
]);
|
||||||
$passwordValue = $newPass;
|
$passwordValue = $newPass;
|
||||||
} catch (\Exception $e) { $passwordValue = $newPass; }
|
} catch (\Exception $e) {
|
||||||
|
$passwordValue = $newPass;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$passwordValue = "⚠️ Sin antena";
|
$passwordValue = "⚠️ Sin antena";
|
||||||
}
|
}
|
||||||
@ -443,28 +515,28 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
|
|
||||||
$finalValue = implode(' ', $allServicePasswords);
|
$finalValue = implode(' ', $allServicePasswords);
|
||||||
|
|
||||||
// Evitar sincronización redundante si el valor es idéntico al actual (anti-bucle)
|
// Evitar sincronización redundante si el valor es idéntico al actual (anti-bucle)
|
||||||
if ($finalValue === $passCRM) {
|
if ($finalValue === $passCRM) {
|
||||||
return $finalValue;
|
return $finalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->syncPasswordWithCrm((int)$clientId, $finalValue);
|
$this->syncPasswordWithCrm((int)$clientId, $finalValue);
|
||||||
return $finalValue;
|
return $finalValue;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error("Excepción en getVaultCredentialsByClientId (Cliente: $clientId): " . $e->getMessage());
|
$this->logger->error("Excepción en getVaultCredentialsByClientId (Cliente: $clientId): " . $e->getMessage());
|
||||||
return 'Error: ' . $e->getMessage();
|
return 'Error: ' . $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function syncPasswordWithCrm(int $clientId, string $passVault): void {
|
private function syncPasswordWithCrm(int $clientId, string $passVault): void
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$clientData = $this->ucrmApi->get("clients/$clientId");
|
$clientData = $this->ucrmApi->get("clients/$clientId");
|
||||||
|
|
||||||
$passCRM = '';
|
$passCRM = '';
|
||||||
$attributeId = 17; // ID real para 'passwordAntenaCliente'
|
$attributeId = 17; // ID real para 'passwordAntenaCliente'
|
||||||
|
|
||||||
if (isset($clientData['attributes'])) {
|
if (isset($clientData['attributes'])) {
|
||||||
foreach ($clientData['attributes'] as $attr) {
|
foreach ($clientData['attributes'] as $attr) {
|
||||||
if ($attr['key'] === 'passwordAntenaCliente') {
|
if ($attr['key'] === 'passwordAntenaCliente') {
|
||||||
@ -484,7 +556,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateStrongPassword(int $length = 16): string {
|
protected function generateStrongPassword(int $length = 16): string
|
||||||
|
{
|
||||||
$lower = 'abcdefghijkmnopqrstuvwxyz'; // Eliminamos 'l'
|
$lower = 'abcdefghijkmnopqrstuvwxyz'; // Eliminamos 'l'
|
||||||
$upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Eliminamos 'I', 'O'
|
$upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Eliminamos 'I', 'O'
|
||||||
$digits = '23456789'; // Eliminamos '1', '0'
|
$digits = '23456789'; // Eliminamos '1', '0'
|
||||||
@ -492,7 +565,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
|
|
||||||
$all = $lower . $upper . $digits . $symbols;
|
$all = $lower . $upper . $digits . $symbols;
|
||||||
$pwChars = [];
|
$pwChars = [];
|
||||||
|
|
||||||
// Asegurar que tenga al menos uno de cada tipo si es posible
|
// Asegurar que tenga al menos uno de cada tipo si es posible
|
||||||
$pwChars[] = $lower[random_int(0, strlen($lower) - 1)];
|
$pwChars[] = $lower[random_int(0, strlen($lower) - 1)];
|
||||||
$pwChars[] = $upper[random_int(0, strlen($upper) - 1)];
|
$pwChars[] = $upper[random_int(0, strlen($upper) - 1)];
|
||||||
@ -515,7 +588,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return implode('', $pwChars);
|
return implode('', $pwChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function patchClientCustomAttribute(int $clientId, int $attributeId, string $value): bool {
|
protected function patchClientCustomAttribute(int $clientId, int $attributeId, string $value): bool
|
||||||
|
{
|
||||||
if ($attributeId <= 0) {
|
if ($attributeId <= 0) {
|
||||||
$this->logger->error("Intento de patchAttribute con ID inválido ($attributeId) para cliente $clientId");
|
$this->logger->error("Intento de patchAttribute con ID inválido ($attributeId) para cliente $clientId");
|
||||||
return false;
|
return false;
|
||||||
@ -537,7 +611,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveAttributeId(string $key): int {
|
private function resolveAttributeId(string $key): int
|
||||||
|
{
|
||||||
if ($this->systemAttributesCache === null) {
|
if ($this->systemAttributesCache === null) {
|
||||||
try {
|
try {
|
||||||
$this->systemAttributesCache = $this->ucrmApi->get('custom-attributes', ['attributeType' => 'client']);
|
$this->systemAttributesCache = $this->ucrmApi->get('custom-attributes', ['attributeType' => 'client']);
|
||||||
@ -556,13 +631,15 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function comparePasswords(?string $crm, ?string $vault): string {
|
protected function comparePasswords(?string $crm, ?string $vault): string
|
||||||
|
{
|
||||||
if ($crm && strpos($crm, 'Error') !== 0) return $crm;
|
if ($crm && strpos($crm, 'Error') !== 0) return $crm;
|
||||||
if ($vault && strpos($vault, 'Error') !== 0) return $vault;
|
if ($vault && strpos($vault, 'Error') !== 0) return $vault;
|
||||||
return '⚠️ Probar pass conocida.';
|
return '⚠️ Probar pass conocida.';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncStripeCustomerData(int $clientId, string $name, ?string $email): void {
|
public function syncStripeCustomerData(int $clientId, string $name, ?string $email): void
|
||||||
|
{
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$stripe = new StripeClient($config['tokenstripe']);
|
$stripe = new StripeClient($config['tokenstripe']);
|
||||||
try {
|
try {
|
||||||
@ -583,7 +660,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function removeTagFromClient(int $clientId, string $tagName): void {
|
protected function removeTagFromClient(int $clientId, string $tagName): void
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$client = $this->ucrmApi->get("clients/$clientId");
|
$client = $this->ucrmApi->get("clients/$clientId");
|
||||||
$targetTagId = null;
|
$targetTagId = null;
|
||||||
@ -593,7 +671,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($targetTagId) {
|
if ($targetTagId) {
|
||||||
$this->ucrmApi->patch("clients/$clientId/remove-tag/$targetTagId", []);
|
$this->ucrmApi->patch("clients/$clientId/remove-tag/$targetTagId", []);
|
||||||
$this->logger->info("Etiqueta '$tagName' (ID: $targetTagId) removida del cliente $clientId via endpoint especializado.");
|
$this->logger->info("Etiqueta '$tagName' (ID: $targetTagId) removida del cliente $clientId via endpoint especializado.");
|
||||||
@ -605,7 +683,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validarNumeroTelefono($n): string {
|
protected function validarNumeroTelefono($n): string
|
||||||
|
{
|
||||||
if (!$n) return '';
|
if (!$n) return '';
|
||||||
$n = preg_replace('/\D/', '', (string)$n);
|
$n = preg_replace('/\D/', '', (string)$n);
|
||||||
return (strlen($n) === 10) ? '52' . $n : $n;
|
return (strlen($n) === 10) ? '52' . $n : $n;
|
||||||
@ -615,7 +694,7 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
{
|
{
|
||||||
// 1. Get Payment ID
|
// 1. Get Payment ID
|
||||||
$paymentId = is_object($notificationObject) ? ($notificationObject->entityId ?? null) : ($notificationObject['entityId'] ?? null);
|
$paymentId = is_object($notificationObject) ? ($notificationObject->entityId ?? null) : ($notificationObject['entityId'] ?? null);
|
||||||
|
|
||||||
if (!$paymentId) {
|
if (!$paymentId) {
|
||||||
$this->logger->warning("ensureStripePaymentAttribute: No entityId found in notification.");
|
$this->logger->warning("ensureStripePaymentAttribute: No entityId found in notification.");
|
||||||
return;
|
return;
|
||||||
@ -649,29 +728,74 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
// 3. Update User ID if missing (Direct DB Patch via Microservice)
|
// 3. Update User ID if missing (Direct DB Patch via Microservice)
|
||||||
// UCRM API doesn't support PATCH userId, so we use microservice
|
// UCRM API doesn't support PATCH userId, so we use microservice
|
||||||
if ($stripeUserId) {
|
if ($stripeUserId) {
|
||||||
try {
|
try {
|
||||||
$payment = $this->ucrmApi->get('payments/' . $paymentId);
|
$payment = $this->ucrmApi->get('payments/' . $paymentId);
|
||||||
if (empty($payment['userId'])) {
|
if (empty($payment['userId'])) {
|
||||||
$this->logger->info("Payment $paymentId has no User ID. Assigning Stripe User ID: $stripeUserId");
|
$this->logger->info("Payment $paymentId has no User ID. Assigning Stripe User ID: $stripeUserId");
|
||||||
$httpClient->patch("$microserviceBaseUrl/payments/$paymentId/user", [
|
$httpClient->patch("$microserviceBaseUrl/payments/$paymentId/user", [
|
||||||
'json' => ['userId' => $stripeUserId],
|
'json' => ['userId' => $stripeUserId],
|
||||||
'timeout' => 5
|
'timeout' => 5
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->logger->debug("Payment $paymentId already has User ID: " . $payment['userId']);
|
$this->logger->debug("Payment $paymentId already has User ID: " . $payment['userId']);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->logger->error("Failed to patch User ID via microservice: " . $e->getMessage());
|
$this->logger->error("Failed to patch User ID via microservice: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// [NEW] 3.5. Patch Payment Method ID via Microservice
|
||||||
|
$targetMethodId = null;
|
||||||
|
|
||||||
|
if ($metadataTipoPago === 'OXXO') {
|
||||||
|
$targetMethodId = 'b01c0b35-b42c-48d9-9ad9-ea6591adfbbb'; // OXXO Pay
|
||||||
|
} elseif ($metadataTipoPago === 'Transferencia Bancaria') {
|
||||||
|
$targetMethodId = '4145b5f5-3bbc-45e3-8fc5-9cda970c62fb'; // Transferencia Bancaria
|
||||||
|
} else {
|
||||||
|
// [NEW] Default to "Credit/Debit Card" if no specific metadata type found
|
||||||
|
$targetMethodId = '93814765-66a1-4c7d-a777-05c18fd6aab3'; // Tarjeta de crédito/débito
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($targetMethodId) {
|
||||||
|
// [NEW] Update Notification Object in Memory so the calling code knows the change
|
||||||
|
if (is_object($notificationObject) && isset($notificationObject->paymentData)) {
|
||||||
|
// Fix for "Indirect modification of overloaded property" error
|
||||||
|
// We must read the array, modify it, and write it back.
|
||||||
|
$pData = $notificationObject->paymentData;
|
||||||
|
if (is_array($pData)) {
|
||||||
|
$pData['methodId'] = $targetMethodId;
|
||||||
|
$notificationObject->paymentData = $pData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check current methodId (reuse 'payment' if available, otherwise fetch)
|
||||||
|
// Note: We fetched 'payment' in Step 3 ONLY if stripeUserId was valid.
|
||||||
|
// Safe to fetch again or reuse specific check.
|
||||||
|
$paymentCheck = $this->ucrmApi->get('payments/' . $paymentId);
|
||||||
|
|
||||||
|
if ($paymentCheck['methodId'] !== $targetMethodId) {
|
||||||
|
$this->logger->info("Payment $paymentId has wrong Method ID ({$paymentCheck['methodId']}). Patching to $targetMethodId via Microservice.");
|
||||||
|
|
||||||
|
$httpClient->patch("$microserviceBaseUrl/payments/$paymentId/method", [
|
||||||
|
'json' => ['methodId' => $targetMethodId],
|
||||||
|
'timeout' => 5
|
||||||
|
]);
|
||||||
|
$this->logger->info("Payment Method ID patched successfully.");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error("Failed to patch Payment Method ID via microservice: " . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Determine Target Attribute Value
|
// 4. Determine Target Attribute Value
|
||||||
// Truth Source Priority: 1. Metadata (DB), 2. Existing Attribute, 3. Method Name (Guess)
|
// Truth Source Priority: 1. Metadata (DB), 2. Existing Attribute, 3. Method Name (Guess)
|
||||||
|
|
||||||
// A. Check Existing Attribute (Don't overwrite valid values unless Metadata says otherwise?)
|
// A. Check Existing Attribute (Don't overwrite valid values unless Metadata says otherwise?)
|
||||||
// Actually, Metadata keys are stronger than manual edits if the flow is automatic.
|
// Actually, Metadata keys are stronger than manual edits if the flow is automatic.
|
||||||
// But let's respect existing valid attributes if metadata is missing.
|
// But let's respect existing valid attributes if metadata is missing.
|
||||||
|
|
||||||
$payment = $this->ucrmApi->get('payments/' . $paymentId); // Re-fetch in case changed? Or Use previous result.
|
$payment = $this->ucrmApi->get('payments/' . $paymentId); // Re-fetch in case changed? Or Use previous result.
|
||||||
$currentValue = null;
|
$currentValue = null;
|
||||||
$hasAttribute = false;
|
$hasAttribute = false;
|
||||||
@ -694,23 +818,23 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to Method Name Guessing if Metadata missing
|
// Fallback to Method Name Guessing if Metadata missing
|
||||||
if ($hasAttribute && in_array($currentValue, ['OXXO Pay', 'Transferencia Bancaria', 'Tarjeta de Crédito'])) {
|
if ($hasAttribute && in_array($currentValue, ['OXXO Pay', 'Transferencia Bancaria', 'Tarjeta de Crédito'])) {
|
||||||
$this->logger->debug("Payment $paymentId ya tiene atributo '$currentValue' y no hay metadata. Respetando.");
|
$this->logger->debug("Payment $paymentId ya tiene atributo '$currentValue' y no hay metadata. Respetando.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$methodId = $payment['methodId'];
|
$methodId = $payment['methodId'];
|
||||||
$method = $this->ucrmApi->get('payment-methods/' . $methodId);
|
$method = $this->ucrmApi->get('payment-methods/' . $methodId);
|
||||||
$methodName = $method['name'] ?? '';
|
$methodName = $method['name'] ?? '';
|
||||||
|
|
||||||
if (stripos($methodName, 'OXXO') !== false) {
|
if (stripos($methodName, 'OXXO') !== false) {
|
||||||
$targetValue = 'OXXO Pay';
|
$targetValue = 'OXXO Pay';
|
||||||
} elseif (stripos($methodName, 'Transferencia') !== false) {
|
} elseif (stripos($methodName, 'Transferencia') !== false) {
|
||||||
$targetValue = 'Transferencia Bancaria';
|
$targetValue = 'Transferencia Bancaria';
|
||||||
} elseif (stripos($methodName, 'Tarjeta') !== false && stripos($methodName, 'Stripe') !== false) {
|
} elseif (stripos($methodName, 'Tarjeta') !== false && stripos($methodName, 'Stripe') !== false) {
|
||||||
$targetValue = 'Tarjeta de Crédito';
|
$targetValue = 'Tarjeta de Crédito';
|
||||||
}
|
}
|
||||||
$this->logger->debug("Fallback Method Guessing '$methodName' -> '$targetValue'");
|
$this->logger->debug("Fallback Method Guessing '$methodName' -> '$targetValue'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Apply Update
|
// 5. Apply Update
|
||||||
@ -731,9 +855,8 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->logger->debug("No se pudo determinar el tipoPagoStripe.");
|
$this->logger->debug("No se pudo determinar el tipoPagoStripe.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->logger->error("Error in ensureStripePaymentAttribute: " . $e->getMessage());
|
$this->logger->error("Error in ensureStripePaymentAttribute: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace SmsNotifier\Facade;
|
namespace SmsNotifier\Facade;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
@ -64,9 +65,7 @@ class ClientCallBellAPI
|
|||||||
$this->CallBellAPIToken = $CallBellAPIToken;
|
$this->CallBellAPIToken = $CallBellAPIToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateContact($client_uuid)
|
public function updateContact($client_uuid) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function printPrueba($clientWhatsAppNumber, $notificationData)
|
public function printPrueba($clientWhatsAppNumber, $notificationData)
|
||||||
{
|
{
|
||||||
@ -156,7 +155,7 @@ class ClientCallBellAPI
|
|||||||
|
|
||||||
if ($reprogramming && !$changeInstaller) {
|
if ($reprogramming && !$changeInstaller) {
|
||||||
//{{1}}, se ha reprogramado su visita técnica con el folio {{2}}
|
//{{1}}, se ha reprogramado su visita técnica con el folio {{2}}
|
||||||
|
|
||||||
$campo3 = '🗓️➡️ ' . sprintf('%s', $jobNotificationData['date']);
|
$campo3 = '🗓️➡️ ' . sprintf('%s', $jobNotificationData['date']);
|
||||||
$campo1_combinado = "Estimado cliente $campo1 se ha reprogramado su visita técnica con folio $campo2";
|
$campo1_combinado = "Estimado cliente $campo1 se ha reprogramado su visita técnica con folio $campo2";
|
||||||
|
|
||||||
@ -245,7 +244,7 @@ class ClientCallBellAPI
|
|||||||
'Content-Type: application/json',
|
'Content-Type: application/json',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
if (!$reprogramming && $changeInstaller) {
|
if (!$reprogramming && $changeInstaller) {
|
||||||
$campo1 = $jobInstallerNotificationData['installerName'];
|
$campo1 = $jobInstallerNotificationData['installerName'];
|
||||||
$campo2 = $jobInstallerNotificationData['subjectOfChange'];
|
$campo2 = $jobInstallerNotificationData['subjectOfChange'];
|
||||||
@ -273,7 +272,7 @@ class ClientCallBellAPI
|
|||||||
$campo1_combinado = "$installerName se reprogramó una tarea con el folio $jobId para el cliente $clientFullName, la nueva fecha será el $date";
|
$campo1_combinado = "$installerName se reprogramó una tarea con el folio $jobId para el cliente $clientFullName, la nueva fecha será el $date";
|
||||||
$campo2 = $jobInstallerNotificationData['clientWhatsApp'];
|
$campo2 = $jobInstallerNotificationData['clientWhatsApp'];
|
||||||
$campo3 = $jobInstallerNotificationData['gmapsLocation'];
|
$campo3 = $jobInstallerNotificationData['gmapsLocation'];
|
||||||
$campo4 = $jobInstallerNotificationData['passwordAntenaCliente'];
|
$campo4 = $jobInstallerNotificationData['passwordAntenaCliente'];
|
||||||
|
|
||||||
//Enviar notificación de reprogramación
|
//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);
|
$log->appendLog("Enviando notificación de reprogramación al instalador, valor de reprogramming $reprogramming y valor de changeInstaller $changeInstaller " . PHP_EOL);
|
||||||
@ -312,7 +311,7 @@ class ClientCallBellAPI
|
|||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Validar la respuesta de Callbell
|
// Validar la respuesta de Callbell
|
||||||
$jsonResponse = json_decode($response, true);
|
$jsonResponse = json_decode($response, true);
|
||||||
@ -357,17 +356,43 @@ class ClientCallBellAPI
|
|||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$gClient = new Client(['base_uri' => "https://{$this->IPServer}/crm/api/v1.0/", 'verify' => false]);
|
$gClient = new Client(['base_uri' => "https://{$this->IPServer}/crm/api/v1.0/", 'verify' => false]);
|
||||||
$this->ucrmApi = new UcrmApi($gClient, $this->UCRMAPIToken ?? '');
|
$this->ucrmApi = new UcrmApi($gClient, $this->UCRMAPIToken ?? '');
|
||||||
$payments = $this->ucrmApi->get(
|
$payment_id = $notificationData->paymentData['id'];
|
||||||
'payments/',
|
$payment_amount = '$' . $notificationData->paymentData['amount'];
|
||||||
[
|
|
||||||
'clientId' => $notificationData->clientData['id'],
|
|
||||||
'limit' => 1,
|
|
||||||
'direction' => 'DESC',
|
|
||||||
|
|
||||||
]
|
// We already have the payment data in $notificationData, no need to fetch 'payments/' again to get the ID.
|
||||||
);
|
// However, if we need the 'note' field for the overlay which might not be in notificationData (depending on richness),
|
||||||
|
// we should try to use what we have or fetch SPECIFICALLY this payment.
|
||||||
|
|
||||||
|
// Let's verify if 'note' is in paymentData. Usually UCRM webhook payload has it.
|
||||||
|
// But to be safe and consistent with previous logic, if we need 'note', we can fetch THIS payment.
|
||||||
|
|
||||||
|
// $payments = $this->ucrmApi->get('payments/'.$payment_id); // This would be better if we need details.
|
||||||
|
|
||||||
|
// The previous code did:
|
||||||
|
// $payments = $this->ucrmApi->get('payments/', ['clientId' => ..., 'limit' => 1 ...]);
|
||||||
|
// $payment_id = $payments[0]['id']; <-- THIS WAS THE BUG. Always getting latest.
|
||||||
|
|
||||||
|
// Fix: Use the ID passed in notificationData.
|
||||||
|
|
||||||
|
// If we need the NOTE for the Overlay (OXXO/Transfer check later in code), we should ensure we have it.
|
||||||
|
// $notificationData->paymentData usually contains 'note'.
|
||||||
|
$note = $notificationData->paymentData['note'] ?? '';
|
||||||
|
|
||||||
|
// Let's keep $payments array structure if downstream code expects it, OR refactor downstream.
|
||||||
|
// Downstream uses $payments[0]['note'].
|
||||||
|
|
||||||
|
// Let's just mock $payments[0] with our data OR fetch the correct single payment.
|
||||||
|
// Fetching single payment is safer to ensure we have the Note.
|
||||||
|
try {
|
||||||
|
$fetchedPayment = $this->ucrmApi->get('payments/' . $payment_id);
|
||||||
|
$payments = [$fetchedPayment];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Fallback if fetch fails (unlikely if ID is valid)
|
||||||
|
$payments = [$notificationData->paymentData];
|
||||||
|
}
|
||||||
|
|
||||||
|
// $payment_id is already set above.
|
||||||
|
|
||||||
$payment_id = $payments[0]['id'];
|
|
||||||
$payment_amount = '$' . $payments[0]['amount'];
|
$payment_amount = '$' . $payments[0]['amount'];
|
||||||
//$saldo = '$' . $notificationData->clientData['accountBalance'];
|
//$saldo = '$' . $notificationData->clientData['accountBalance'];
|
||||||
$nombre_cliente = sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']);
|
$nombre_cliente = sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']);
|
||||||
@ -440,23 +465,23 @@ class ClientCallBellAPI
|
|||||||
// 1. Instanciar Logger y MinioService
|
// 1. Instanciar Logger y MinioService
|
||||||
$loggerService = new Logger(); // Servicio de Logging propio del namespace
|
$loggerService = new Logger(); // Servicio de Logging propio del namespace
|
||||||
$minioService = new MinioStorageService($loggerService);
|
$minioService = new MinioStorageService($loggerService);
|
||||||
|
|
||||||
// 2. Configurar Microservicio
|
// 2. Configurar Microservicio
|
||||||
$ipMicroservice = $config['ipPuppeteer'] ?? 'localhost'; // Reutilizamos IP de Puppeteer
|
$ipMicroservice = $config['ipPuppeteer'] ?? 'localhost'; // Reutilizamos IP de Puppeteer
|
||||||
$portMicroservice = '8050'; // Puerto definido en docker-compose
|
$portMicroservice = '8050'; // Puerto definido en docker-compose
|
||||||
$microserviceUrl = "http://{$ipMicroservice}:{$portMicroservice}/process";
|
$microserviceUrl = "http://{$ipMicroservice}:{$portMicroservice}/process";
|
||||||
|
|
||||||
$log->appendLog("Procesando PDF con microservicio: $microserviceUrl" . PHP_EOL);
|
$log->appendLog("Procesando PDF con microservicio: $microserviceUrl" . PHP_EOL);
|
||||||
|
|
||||||
// 2. DETECTAR METODO DE PAGO (Overlay)
|
// 2. DETECTAR METODO DE PAGO (Overlay)
|
||||||
$overlayText = '';
|
$overlayText = '';
|
||||||
$note = $payments[0]['note'] ?? '';
|
$note = $payments[0]['note'] ?? '';
|
||||||
|
|
||||||
// Si la nota dice OXXO o Transferencia, preparamos el reemplazo
|
// Si la nota dice OXXO o Transferencia, preparamos el reemplazo
|
||||||
if (stripos($note, 'OXXO') !== false) {
|
if (stripos($note, 'OXXO') !== false) {
|
||||||
$overlayText = 'OXXO Pay';
|
$overlayText = 'OXXO Pay';
|
||||||
} elseif (stripos($note, 'Transferencia') !== false) {
|
} elseif (stripos($note, 'Transferencia') !== false) {
|
||||||
$overlayText = 'Transferencia Bancaria';
|
$overlayText = 'Transferencia Bancaria';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coordenadas APROXIMADAS para el template default de UCRM (Ajustar si es necesario)
|
// Coordenadas APROXIMADAS para el template default de UCRM (Ajustar si es necesario)
|
||||||
@ -477,7 +502,7 @@ class ClientCallBellAPI
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'mode',
|
'name' => 'mode',
|
||||||
'contents' => 'full'
|
'contents' => 'full'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -497,19 +522,19 @@ class ClientCallBellAPI
|
|||||||
|
|
||||||
$httpClient = new Client();
|
$httpClient = new Client();
|
||||||
$responseMs = $httpClient->post($microserviceUrl, $guzzleParams);
|
$responseMs = $httpClient->post($microserviceUrl, $guzzleParams);
|
||||||
|
|
||||||
if ($responseMs->getStatusCode() === 200) {
|
if ($responseMs->getStatusCode() === 200) {
|
||||||
$jpgContent = $responseMs->getBody()->getContents();
|
$jpgContent = $responseMs->getBody()->getContents();
|
||||||
if (file_put_contents($rutaImagen, $jpgContent) !== false) {
|
if (file_put_contents($rutaImagen, $jpgContent) !== false) {
|
||||||
$log->appendLog("Imagen generada por microservicio guardada en: $rutaImagen" . PHP_EOL);
|
$log->appendLog("Imagen generada por microservicio guardada en: $rutaImagen" . PHP_EOL);
|
||||||
|
|
||||||
// 4. Subir a MinIO
|
// 4. Subir a MinIO
|
||||||
$fileNameComprobanteImage = str_replace('.jpg', '_' . time() . '.jpg', basename($rutaImagen));
|
$fileNameComprobanteImage = str_replace('.jpg', '_' . time() . '.jpg', basename($rutaImagen));
|
||||||
$url_file = $minioService->uploadFile($rutaImagen, $fileNameComprobanteImage);
|
$url_file = $minioService->uploadFile($rutaImagen, $fileNameComprobanteImage);
|
||||||
|
|
||||||
if ($url_file) {
|
if ($url_file) {
|
||||||
$log->appendLog("Imagen subida a MinIO: $url_file" . PHP_EOL);
|
$log->appendLog("Imagen subida a MinIO: $url_file" . PHP_EOL);
|
||||||
|
|
||||||
// 5. LIMPIEZA AUTOMÁTICA (Consistencia con req. de usuario)
|
// 5. LIMPIEZA AUTOMÁTICA (Consistencia con req. de usuario)
|
||||||
if (file_exists($rutaArchivo)) {
|
if (file_exists($rutaArchivo)) {
|
||||||
unlink($rutaArchivo);
|
unlink($rutaArchivo);
|
||||||
@ -520,7 +545,6 @@ class ClientCallBellAPI
|
|||||||
//$log->appendLog("Archivo JPG temporal eliminado." . PHP_EOL);
|
//$log->appendLog("Archivo JPG temporal eliminado." . PHP_EOL);
|
||||||
}
|
}
|
||||||
$log->appendLog("Archivos temporales (PDF/JPG) eliminados tras subida exitosa." . PHP_EOL);
|
$log->appendLog("Archivos temporales (PDF/JPG) eliminados tras subida exitosa." . PHP_EOL);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$log->appendLog("Error: Falló la subida a MinIO." . PHP_EOL);
|
$log->appendLog("Error: Falló la subida a MinIO." . PHP_EOL);
|
||||||
return false;
|
return false;
|
||||||
@ -533,7 +557,6 @@ class ClientCallBellAPI
|
|||||||
$log->appendLog("Error microservicio PDF: HTTP " . $responseMs->getStatusCode() . PHP_EOL);
|
$log->appendLog("Error microservicio PDF: HTTP " . $responseMs->getStatusCode() . PHP_EOL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$log->appendLog("Excepción en flujo Microservicio/MinIO: " . $e->getMessage() . PHP_EOL);
|
$log->appendLog("Excepción en flujo Microservicio/MinIO: " . $e->getMessage() . PHP_EOL);
|
||||||
return false;
|
return false;
|
||||||
@ -1173,7 +1196,6 @@ class ClientCallBellAPI
|
|||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
|
$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$log->appendLog("NO SE EJECUTA PATCH - No hay cambios que actualizar" . PHP_EOL);
|
$log->appendLog("NO SE EJECUTA PATCH - No hay cambios que actualizar" . PHP_EOL);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare (strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace SmsNotifier;
|
namespace SmsNotifier;
|
||||||
|
|
||||||
@ -131,19 +131,17 @@ class Plugin
|
|||||||
if ($jsonData['data']['object']['type'] === 'funded') {
|
if ($jsonData['data']['object']['type'] === 'funded') {
|
||||||
$this->logger->info('Evento de transferencia de un cliente recibido: ' . json_encode($jsonData) . PHP_EOL);
|
$this->logger->info('Evento de transferencia de un cliente recibido: ' . json_encode($jsonData) . PHP_EOL);
|
||||||
$this->pluginNotifierFacade->createPaymentIntent($jsonData);
|
$this->pluginNotifierFacade->createPaymentIntent($jsonData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($jsonData['data']['object']['type'] === 'applied_to_payment') {
|
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->logger->info('Se aplicó el saldo en Stripe de un pago: ' . json_encode($jsonData) . PHP_EOL);
|
||||||
$this->pluginNotifierFacade->registerPaymentFromWebhook($jsonData);
|
$this->pluginNotifierFacade->registerPaymentFromWebhook($jsonData);
|
||||||
}elseif ($jsonData['data']['object']['type'] === 'unapplied_from_payment'){
|
} 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"}
|
//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'];
|
$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
|
//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('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);
|
$this->logger->warning('Monto neto de la transferencia cancelada: ' . $jsonData['data']['object']['net_amount'] . PHP_EOL);
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'payout.failed':
|
case 'payout.failed':
|
||||||
@ -165,7 +163,7 @@ class Plugin
|
|||||||
case 'payment_intent.succeeded':
|
case 'payment_intent.succeeded':
|
||||||
$this->logger->info('Evento de pago exitoso (Stripe PI) recibido.');
|
$this->logger->info('Evento de pago exitoso (Stripe PI) recibido.');
|
||||||
if (isset($jsonData['data']['object'])) {
|
if (isset($jsonData['data']['object'])) {
|
||||||
$this->pluginNotifierFacade->registerPaymentFromIntent($jsonData['data']['object']);
|
$this->pluginNotifierFacade->registerPaymentFromIntent($jsonData['data']['object']);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'oxxo.retrieve':
|
case 'oxxo.retrieve':
|
||||||
@ -175,7 +173,7 @@ class Plugin
|
|||||||
if ($orderId) {
|
if ($orderId) {
|
||||||
$status = $this->pluginOxxoNotifierFacade->getOxxoOrderStatus($orderId);
|
$status = $this->pluginOxxoNotifierFacade->getOxxoOrderStatus($orderId);
|
||||||
$this->logger->debug("Estado recuperado para Orden #$orderId: " . json_encode($status));
|
$this->logger->debug("Estado recuperado para Orden #$orderId: " . json_encode($status));
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode($status);
|
echo json_encode($status);
|
||||||
} else {
|
} else {
|
||||||
@ -195,7 +193,7 @@ class Plugin
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'oxxo_reference' => '',
|
'oxxo_reference' => '',
|
||||||
'url' => '',
|
'url' => '',
|
||||||
'error' => $stripeResult['data']['error'] ?? 'stripe_error',
|
'error' => $stripeResult['data']['error'] ?? 'stripe_error',
|
||||||
'status' => 'failed'
|
'status' => 'failed'
|
||||||
]);
|
]);
|
||||||
@ -203,13 +201,13 @@ class Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
$oxxoData = $stripeResult['data'];
|
$oxxoData = $stripeResult['data'];
|
||||||
|
|
||||||
// 2. Crear Orden (Sync) para obtener ID
|
// 2. Crear Orden (Sync) para obtener ID
|
||||||
// Ahora pasamos $oxxoData que SI tiene clientID, clientFullName, etc.
|
// Ahora pasamos $oxxoData que SI tiene clientID, clientFullName, etc.
|
||||||
$responseOxxo = $this->pluginOxxoNotifierFacade->createOxxoOrder($oxxoData);
|
$responseOxxo = $this->pluginOxxoNotifierFacade->createOxxoOrder($oxxoData);
|
||||||
|
|
||||||
$orderId = $responseOxxo['order_id'] ?? null;
|
$orderId = $responseOxxo['order_id'] ?? null;
|
||||||
|
|
||||||
// 3. Preparar respuesta inmediata (Pending)
|
// 3. Preparar respuesta inmediata (Pending)
|
||||||
$responseArray = [
|
$responseArray = [
|
||||||
'oxxo_reference' => $responseOxxo['oxxo_reference'] ?? '',
|
'oxxo_reference' => $responseOxxo['oxxo_reference'] ?? '',
|
||||||
@ -229,13 +227,13 @@ class Plugin
|
|||||||
while (ob_get_level() > 0) {
|
while (ob_get_level() > 0) {
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header('Connection: close');
|
header('Connection: close');
|
||||||
ignore_user_abort(true); // Permitir que el script siga corriendo
|
ignore_user_abort(true); // Permitir que el script siga corriendo
|
||||||
|
|
||||||
echo json_encode($responseArray);
|
echo json_encode($responseArray);
|
||||||
|
|
||||||
// Forzar envío al cliente
|
// Forzar envío al cliente
|
||||||
if (function_exists('fastcgi_finish_request')) {
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
fastcgi_finish_request();
|
fastcgi_finish_request();
|
||||||
@ -250,18 +248,17 @@ class Plugin
|
|||||||
$this->logger->info("Iniciando generación background para orden #$orderId");
|
$this->logger->info("Iniciando generación background para orden #$orderId");
|
||||||
set_time_limit(180); // 3 minutos para Puppeteer
|
set_time_limit(180); // 3 minutos para Puppeteer
|
||||||
sleep(1); // Pequeña pausa para asegurar liberación del socket
|
sleep(1); // Pequeña pausa para asegurar liberación del socket
|
||||||
|
|
||||||
$this->pluginOxxoNotifierFacade->generateOxxoVoucher($responseOxxo, true);
|
$this->pluginOxxoNotifierFacade->generateOxxoVoucher($responseOxxo, true);
|
||||||
|
|
||||||
$this->logger->info("Generación background finalizada para orden #$orderId");
|
$this->logger->info("Generación background finalizada para orden #$orderId");
|
||||||
} else {
|
} else {
|
||||||
$this->logger->error("No se pudo iniciar background job: Falta order_id");
|
$this->logger->error("No se pudo iniciar background job: Falta order_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminar proceso hijo
|
|
||||||
exit;
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
// Terminar proceso hijo
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -299,8 +296,8 @@ class Plugin
|
|||||||
$result = json_encode($notification);
|
$result = json_encode($notification);
|
||||||
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
|
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
|
||||||
|
|
||||||
// [NEW] Attempt to patch the payment with correct Stripe attribute if applicable
|
// [MOVED] Attempt to patch method ID only if it comes as "Stripe Credit Card" (catch-all)
|
||||||
$this->pluginNotifierFacade->ensureStripePaymentAttribute($notification);
|
//$this->pluginNotifierFacade->ensureStripePaymentAttribute($notification); (Removed from top)
|
||||||
|
|
||||||
$payment_method_id = $notification->paymentData['methodId'];
|
$payment_method_id = $notification->paymentData['methodId'];
|
||||||
$payment_method = '';
|
$payment_method = '';
|
||||||
@ -337,9 +334,30 @@ class Plugin
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '1dd098fa-5d63-4c8d-88b7-3c27ffbbb6ae':
|
case '1dd098fa-5d63-4c8d-88b7-3c27ffbbb6ae':
|
||||||
$payment_method = 'Tarjeta de crédito Stripe';
|
// [NEW] Logic to patch method ID based on metadata
|
||||||
if ($config['creditCardStripePaymentMethodId'] ?? false) {
|
$this->pluginNotifierFacade->ensureStripePaymentAttribute($notification);
|
||||||
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
|
||||||
|
// Check if Method ID was updated in memory to OXXO or Transfer
|
||||||
|
$patchedMethodId = $notification->paymentData['methodId'];
|
||||||
|
|
||||||
|
if ($patchedMethodId === 'b01c0b35-b42c-48d9-9ad9-ea6591adfbbb') {
|
||||||
|
// It is OXXO Pay
|
||||||
|
$payment_method = 'OXXO Pay';
|
||||||
|
if ($config['oxxoPayPaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
|
} elseif ($patchedMethodId === '4145b5f5-3bbc-45e3-8fc5-9cda970c62fb') {
|
||||||
|
// It is Bank Transfer
|
||||||
|
$payment_method = 'Transferencia bancaria';
|
||||||
|
if ($config['bankTransferPaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default: Credit Card Stripe
|
||||||
|
$payment_method = 'Tarjeta de crédito Stripe';
|
||||||
|
if ($config['creditCardStripePaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'b9e1e9d1-5c7b-41d2-b6b2-3e568d700290':
|
case 'b9e1e9d1-5c7b-41d2-b6b2-3e568d700290':
|
||||||
@ -372,11 +390,28 @@ class Plugin
|
|||||||
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'b01c0b35-b42c-48d9-9ad9-ea6591adfbbb':
|
||||||
|
$payment_method = 'OXXO Pay';
|
||||||
|
if ($config['oxxoPayPaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '4145b5f5-3bbc-45e3-8fc5-9cda970c62fb':
|
||||||
|
$payment_method = 'Transferencia bancaria';
|
||||||
|
if ($config['bankTransferPaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '93814765-66a1-4c7d-a777-05c18fd6aab3':
|
||||||
|
$payment_method = 'Tarjeta de crédito/débito';
|
||||||
|
if ($config['creditDebitCardPaymentMethodId'] ?? false) {
|
||||||
|
$this->notifierFacade->verifyPaymentActionToDo($notification);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$payment_method = 'Desconocido';
|
$payment_method = 'Desconocido';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ($notification->eventName === 'client.edit') {
|
} else if ($notification->eventName === 'client.edit') {
|
||||||
$this->logger->info('Procesando evento client.edit para entityId: ' . ($jsonData['entityId'] ?? 'unknown'));
|
$this->logger->info('Procesando evento client.edit para entityId: ' . ($jsonData['entityId'] ?? 'unknown'));
|
||||||
$this->logger->debug('Payload completo client.edit: ' . json_encode($jsonData));
|
$this->logger->debug('Payload completo client.edit: ' . json_encode($jsonData));
|
||||||
@ -388,7 +423,7 @@ class Plugin
|
|||||||
} else {
|
} else {
|
||||||
$this->logger->info('Llamando a updatePasswordAntenaIfNeeded para cliente: ' . $clientID);
|
$this->logger->info('Llamando a updatePasswordAntenaIfNeeded para cliente: ' . $clientID);
|
||||||
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded((int)$clientID, $jsonData);
|
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded((int)$clientID, $jsonData);
|
||||||
$this->logger->info('Llamada finalizada exitosamente.');
|
// $this->lcdf4d7cf10e2ogger->info('Llamada finalizada exitosamente.');
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->logger->error('ERROR FATAL procesando client.edit: ' . $e->getMessage());
|
$this->logger->error('ERROR FATAL procesando client.edit: ' . $e->getMessage());
|
||||||
@ -444,10 +479,10 @@ class Plugin
|
|||||||
// Automatización: Sincronizar cambios de Nombre o Email con Stripe
|
// Automatización: Sincronizar cambios de Nombre o Email con Stripe
|
||||||
$nameBefore = trim(($entityBeforeEdit['firstName'] ?? '') . ' ' . ($entityBeforeEdit['lastName'] ?? ''));
|
$nameBefore = trim(($entityBeforeEdit['firstName'] ?? '') . ' ' . ($entityBeforeEdit['lastName'] ?? ''));
|
||||||
if (empty($nameBefore)) $nameBefore = $entityBeforeEdit['companyName'] ?? '';
|
if (empty($nameBefore)) $nameBefore = $entityBeforeEdit['companyName'] ?? '';
|
||||||
|
|
||||||
$nameAfter = trim(($entity['firstName'] ?? '') . ' ' . ($entity['lastName'] ?? ''));
|
$nameAfter = trim(($entity['firstName'] ?? '') . ' ' . ($entity['lastName'] ?? ''));
|
||||||
if (empty($nameAfter)) $nameAfter = $entity['companyName'] ?? '';
|
if (empty($nameAfter)) $nameAfter = $entity['companyName'] ?? '';
|
||||||
|
|
||||||
$emailBefore = null;
|
$emailBefore = null;
|
||||||
foreach ($entityBeforeEdit['contacts'] ?? [] as $contact) {
|
foreach ($entityBeforeEdit['contacts'] ?? [] as $contact) {
|
||||||
if ($contact['isBilling'] || $contact['isContact']) {
|
if ($contact['isBilling'] || $contact['isContact']) {
|
||||||
@ -473,18 +508,15 @@ class Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->notifierFacade->verifyClientActionToDo($notification);
|
$this->notifierFacade->verifyClientActionToDo($notification);
|
||||||
|
|
||||||
} else if ($notification->eventName === 'client.add') {
|
} else if ($notification->eventName === 'client.add') {
|
||||||
$this->logger->debug('Se agregó un nuevo cliente');
|
$this->logger->debug('Se agregó un nuevo cliente');
|
||||||
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
|
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
|
||||||
|
|
||||||
} else if ($notification->eventName === 'service.edit') {
|
} else if ($notification->eventName === 'service.edit') {
|
||||||
$this->logger->debug('Se editó el servicio a un cliente' . PHP_EOL);
|
$this->logger->debug('Se editó el servicio a un cliente' . PHP_EOL);
|
||||||
$this->notifierFacade->verifyServiceActionToDo($notification);
|
$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":[]}}}
|
//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":[]}}}
|
||||||
$clientID = $jsonData['extraData']['entity']['clientId'];
|
$clientID = $jsonData['extraData']['entity']['clientId'];
|
||||||
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded($clientID, $jsonData);
|
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded($clientID, $jsonData);
|
||||||
|
|
||||||
} else if ($notification->eventName === 'service.suspend') {
|
} else if ($notification->eventName === 'service.suspend') {
|
||||||
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
|
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
|
||||||
$this->notifierFacade->verifyServiceActionToDo($notification);
|
$this->notifierFacade->verifyServiceActionToDo($notification);
|
||||||
@ -532,7 +564,6 @@ class Plugin
|
|||||||
'title' => $title,
|
'title' => $title,
|
||||||
]);
|
]);
|
||||||
$this->logger->debug('Respuesta de la API al agregar el trabajo: ' . json_encode($responsePatch));
|
$this->logger->debug('Respuesta de la API al agregar el trabajo: ' . json_encode($responsePatch));
|
||||||
|
|
||||||
} else if ($notification->eventName === 'job.edit') {
|
} else if ($notification->eventName === 'job.edit') {
|
||||||
$this->logger->debug('Se actualiza un trabajo' . PHP_EOL);
|
$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));
|
||||||
@ -589,14 +620,12 @@ class Plugin
|
|||||||
$this->logger->debug('Edición de trabajo "En curso" sin cambios de fecha o técnico relevantes para notificación');
|
$this->logger->debug('Edición de trabajo "En curso" sin cambios de fecha o técnico relevantes para notificación');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$this->logger->warning('El campo assignedUserId no existe en entityBeforeEdit o entity');
|
$this->logger->warning('El campo assignedUserId no existe en entityBeforeEdit o entity');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->logger->warning('Los datos entityBeforeEdit o entity no están presentes en extraData');
|
$this->logger->warning('Los datos entityBeforeEdit o entity no están presentes en extraData');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//$this->notifierFacade->update($notification);
|
//$this->notifierFacade->update($notification);
|
||||||
|
|||||||
32
test_microservice_patch.php
Normal file
32
test_microservice_patch.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
|
||||||
|
$ip = $config['ipPuppeteer'] ?? '127.0.0.1'; // Fallback
|
||||||
|
$port = $config['portPuppeteer'] ?? '4100'; // Fallback, docker-compose says 4100 host -> 4000 container
|
||||||
|
$paymentId = 907;
|
||||||
|
$oxxoMethodId = 'b01c0b35-b42c-48d9-9ad9-ea6591adfbbb';
|
||||||
|
|
||||||
|
echo "Testing Microservice Patch on Payment $paymentId to OXXO Pay ($oxxoMethodId)...\n";
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
try {
|
||||||
|
$url = "http://$ip:$port/payments/$paymentId/method";
|
||||||
|
echo "URL: $url\n";
|
||||||
|
|
||||||
|
$response = $client->patch($url, [
|
||||||
|
'json' => ['methodId' => $oxxoMethodId]
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Response Code: " . $response->getStatusCode() . "\n";
|
||||||
|
echo "Body: " . $response->getBody()->getContents() . "\n";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
|
if (method_exists($e, 'getResponse') && $e->getResponse()) {
|
||||||
|
echo "Response Error: " . $e->getResponse()->getBody()->getContents() . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user