fix(dashboard): resolución de errores de sintaxis en portal de pagos Stripe
- Reemplazo de comillas simples por backticks (literales de plantilla) en strings multilínea dentro de `views/stripe.php` para evitar errores fatales de JavaScript que bloqueaban la navegación del menú y la interacción del usuario. - Mejora de la lógica en la interfaz para manejar el estado de pago "processing" (común en transferencias SPEI), mostrando el mensaje "Pago en proceso" con instrucciones claras en lugar del mensaje genérico de error. - Corrección del objeto global `store` en `public.php` eliminando propiedades duplicadas que podían afectar la persistencia de la sesión. - Actualización de versión a 4.6.1 en CHANGELOG, README y manifest.json.
This commit is contained in:
parent
45a0d84caa
commit
c8aec7caad
@ -1,5 +1,11 @@
|
|||||||
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
||||||
|
|
||||||
|
## VERSIÓN 4.6.1 - 11-05-2026
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
1️⃣ **Fix UI Dashboard (Stripe)**: Resolución de errores de sintaxis JavaScript en el portal de pagos (uso de comillas simples en strings multilínea en `views/stripe.php`) que bloqueaban el renderizado y la navegación del menú administrativo.
|
||||||
|
2️⃣ **Mejora del Feedback SPEI**: Ajuste en la lógica de estados de pago de Stripe para mostrar feedback más claro ("Pago en proceso") en lugar de mensajes genéricos de error al recibir estados de procesamiento de transferencias SPEI.
|
||||||
|
|
||||||
## VERSIÓN 4.6.0 - 11-04-2026
|
## VERSIÓN 4.6.0 - 11-04-2026
|
||||||
|
|
||||||
### 🚀 Nuevas Características (Features)
|
### 🚀 Nuevas Características (Features)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# SIIP - WhatsApp Notifications & Integrated Payment Portal
|
# SIIP - WhatsApp Notifications & Integrated Payment Portal
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
7512
data/plugin.log
7512
data/plugin.log
File diff suppressed because one or more lines are too long
@ -5,13 +5,18 @@
|
|||||||
"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",
|
"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",
|
"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/",
|
"url": "https://siip.mx/",
|
||||||
"version": "4.6.0",
|
"version": "4.6.1",
|
||||||
"unmsVersionCompliancy": {
|
"unmsVersionCompliancy": {
|
||||||
"min": "2.1.0",
|
"min": "2.1.0",
|
||||||
"max": null
|
"max": null
|
||||||
},
|
},
|
||||||
"author": "SIIP INTERNET",
|
"author": "SIIP INTERNET",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "4.6.1",
|
||||||
|
"date": "2026-05-11",
|
||||||
|
"changes": "Hotfix: Resolución de errores de sintaxis (backticks) en el portal de pagos que bloqueaban la navegación del menú administrativo y mejora de mensajes de estado de pago."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"date": "2026-04-11",
|
"date": "2026-04-11",
|
||||||
|
|||||||
37
public.php
37
public.php
@ -339,9 +339,12 @@ if (isset($_GET['action'])) {
|
|||||||
if ($_GET['action'] === 'get_stripe_history') {
|
if ($_GET['action'] === 'get_stripe_history') {
|
||||||
$stripeCustomerId = $_GET['stripeCustomerId'] ?? $_GET['customerId'] ?? null;
|
$stripeCustomerId = $_GET['stripeCustomerId'] ?? $_GET['customerId'] ?? null;
|
||||||
if ($stripeCustomerId) {
|
if ($stripeCustomerId) {
|
||||||
|
// getCustomerCashBalance returns MXN (already /100), multiply by 100 so JS can do /100
|
||||||
|
$cashBalanceMxn = $paymentIntentService->getCustomerCashBalance($stripeCustomerId);
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'payments' => $paymentIntentService->getLastPayments($stripeCustomerId, 10)
|
'payments' => $paymentIntentService->getLastPayments($stripeCustomerId, 10),
|
||||||
|
'cashBalance' => $cashBalanceMxn * 100 // centavos, JS divides by 100
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing customer id']);
|
echo json_encode(['success' => false, 'error' => 'Missing customer id']);
|
||||||
@ -1792,22 +1795,22 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
<div class="menu-container" id="mainDashboard">
|
<div class="menu-container" id="mainDashboard">
|
||||||
<div class="menu-item" onclick="showModule('installers')">
|
<div class="menu-item" onclick="showModule('installers')">
|
||||||
<img src="?action=image&file=instalador.png">
|
<img src="?action=image&file=instalador.png">
|
||||||
<h3>Instaladores</h3>
|
<h3>Administrar Instaladores</h3>
|
||||||
<p>Administra los técnicos e instaladores registrados en el sistema.</p>
|
<p>Administra los técnicos e instaladores registrados en el sistema.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item" onclick="showModule('notifications')">
|
<div class="menu-item" onclick="showModule('notifications')">
|
||||||
<img src="?action=image&file=whatsapp-logo.png">
|
<img src="?action=image&file=whatsapp-logo.png">
|
||||||
<h3>Notificaciones</h3>
|
<h3>Reenviar Notificaciones de pago vía WhatsApp</h3>
|
||||||
<p>Re-envía manualmente notificaciones de pago vía WhatsApp.</p>
|
<p>Re-envía manualmente notificaciones de pago vía WhatsApp.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item" onclick="showModule('stripe')">
|
<div class="menu-item" onclick="showModule('stripe')">
|
||||||
<img src="?action=image&file=stripe-logo.png">
|
<img src="?action=image&file=stripe-logo.png">
|
||||||
<h3>Pagos SPEI</h3>
|
<h3>Generador de Intenciones de Pago Stripe</h3>
|
||||||
<p>Genera referencias de Transferencia Bancaria personalizadas.</p>
|
<p>Genera intenciones de pago vía Stripe personalizadas.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item" onclick="showModule('oxxo')">
|
<div class="menu-item" onclick="showModule('oxxo')">
|
||||||
<img src="?action=image&file=oxxo-logo.png">
|
<img src="?action=image&file=oxxo-logo.png">
|
||||||
<h3>Pagos OXXO</h3>
|
<h3>Generador de fichas OXXO</h3>
|
||||||
<p>Genera fichas y códigos de barras para pago en tiendas OXXO.</p>
|
<p>Genera fichas y códigos de barras para pago en tiendas OXXO.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1817,16 +1820,16 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
<!-- Tabs Navigation -->
|
<!-- Tabs Navigation -->
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<a href="#" class="tab active" onclick="switchTab('instaladores'); return false;">
|
<a href="#" class="tab active" onclick="switchTab('instaladores'); return false;">
|
||||||
<img src="?action=get_image&name=installers-management.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Instaladores
|
<img src="?action=get_image&name=installers-management.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Administrar Instaladores
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="tab" onclick="switchTab('notificaciones'); return false;">
|
<a href="#" class="tab" onclick="switchTab('notificaciones'); return false;">
|
||||||
<img src="?action=get_image&name=whatsapp-notification.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Notificaciones
|
<img src="?action=get_image&name=whatsapp-notification.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Reenviar Notificaciones de pago vía WhatsApp
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="tab" onclick="switchTab('pagos-spei'); return false;">
|
<a href="#" class="tab" onclick="switchTab('pagos-spei'); return false;">
|
||||||
<img src="?action=get_image&name=online-payments-stripe.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Pagos SPEI
|
<img src="?action=get_image&name=online-payments-stripe.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Generador de Intenciones de Pago Stripe
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="tab" onclick="switchTab('pagos-oxxo'); return false;">
|
<a href="#" class="tab" onclick="switchTab('pagos-oxxo'); return false;">
|
||||||
<img src="?action=get_image&name=oxxo-payments.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Pagos OXXO
|
<img src="?action=get_image&name=oxxo-payments.png" style="width:24px;height:24px;vertical-align:middle;margin-right:8px;"> Generador de fichas OXXO
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@ -1994,11 +1997,21 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
">
|
">
|
||||||
|
<?php
|
||||||
|
$manifestPath = __DIR__ . '/manifest.json';
|
||||||
|
$pluginVersion = 'Desconocida';
|
||||||
|
if (file_exists($manifestPath)) {
|
||||||
|
$manifestData = json_decode(file_get_contents($manifestPath), true);
|
||||||
|
if (isset($manifestData['information']['version'])) {
|
||||||
|
$pluginVersion = $manifestData['information']['version'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
<p style="margin: 0; font-size: 0.9rem; color: var(--text-main);">
|
<p style="margin: 0; font-size: 0.9rem; color: var(--text-main);">
|
||||||
Plugin Desarrollado por <strong>SIIP INTERNET</strong> - Todos los derechos reservados.
|
Plugin Desarrollado por <strong>SIIP INTERNET</strong> - Todos los derechos reservados.
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 5px 0 0 0; font-size: 0.85rem; color: var(--text-muted);">
|
<p style="margin: 5px 0 0 0; font-size: 0.85rem; color: var(--text-muted);">
|
||||||
© <?php echo date('Y'); ?> SIIP Internet. Versión 4.6.0
|
© <?php echo date('Y'); ?> SIIP Internet. Versión <?php echo htmlspecialchars($pluginVersion); ?>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
@ -2045,8 +2058,6 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
let SYSTEM_USER_ID = <?php echo $currentUser ? $currentUser->userId : 'null'; ?>;
|
let SYSTEM_USER_ID = <?php echo $currentUser ? $currentUser->userId : 'null'; ?>;
|
||||||
|
|
||||||
const store = {
|
const store = {
|
||||||
installers: <?php echo json_encode($installersData['instaladores']); ?>,
|
|
||||||
theme: localStorage.getItem('theme') || 'light',
|
|
||||||
installers: <?php echo json_encode($installersData['instaladores']); ?>,
|
installers: <?php echo json_encode($installersData['instaladores']); ?>,
|
||||||
theme: localStorage.getItem('theme') || 'light',
|
theme: localStorage.getItem('theme') || 'light',
|
||||||
crmUrl: '<?php echo $ucrmApiUrl; ?>',
|
crmUrl: '<?php echo $ucrmApiUrl; ?>',
|
||||||
|
|||||||
@ -713,10 +713,87 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
$metadataTipoPago = $data['metadata']['tipoPago'];
|
$metadataTipoPago = $data['metadata']['tipoPago'];
|
||||||
$this->logger->info("Microservice found metadata: tipoPago = '$metadataTipoPago'");
|
$this->logger->info("Microservice found metadata: tipoPago = '$metadataTipoPago'");
|
||||||
}
|
}
|
||||||
|
// Capture Stripe ID for prefix-based heuristic detection below
|
||||||
|
$metadataStripeId = $data['stripeId'] ?? null;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->logger->warning("Microservice metadata fetch failed: " . $e->getMessage());
|
$this->logger->warning("Microservice metadata fetch failed: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- LOCAL FALLBACK LOGIC ---
|
||||||
|
if (!$metadataTipoPago && isset($config['tokenstripe'])) {
|
||||||
|
try {
|
||||||
|
$this->logger->info("Attempting direct Stripe API fetch for Payment $paymentId");
|
||||||
|
$paymentInfo = $this->ucrmApi->get('payments/' . $paymentId);
|
||||||
|
$clientId = $paymentInfo['clientId'] ?? null;
|
||||||
|
$amount = isset($paymentInfo['amount']) ? round($paymentInfo['amount'] * 100) : null;
|
||||||
|
|
||||||
|
if ($clientId && $amount) {
|
||||||
|
$clientInfo = $this->ucrmApi->get('clients/' . $clientId);
|
||||||
|
$stripeCustomerId = null;
|
||||||
|
if (isset($clientInfo['attributes'])) {
|
||||||
|
foreach ($clientInfo['attributes'] as $attr) {
|
||||||
|
if (stripos($attr['name'] ?? '', 'Stripe') !== false && stripos($attr['name'] ?? '', 'Customer') !== false) {
|
||||||
|
$stripeCustomerId = $attr['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (($attr['key'] ?? '') === 'stripeCustomerId' || ($attr['key'] ?? '') === 'customerIdStripe') {
|
||||||
|
$stripeCustomerId = $attr['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stripeCustomerId) {
|
||||||
|
$stripeClient = new Client([
|
||||||
|
'base_uri' => 'https://api.stripe.com/v1/',
|
||||||
|
'auth' => [$config['tokenstripe'], ''],
|
||||||
|
'timeout' => 5
|
||||||
|
]);
|
||||||
|
$res = $stripeClient->get("payment_intents?customer=$stripeCustomerId&limit=5");
|
||||||
|
$intents = json_decode($res->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (isset($intents['data']) && is_array($intents['data'])) {
|
||||||
|
foreach ($intents['data'] as $intent) {
|
||||||
|
if (isset($intent['amount']) && $intent['amount'] == $amount) {
|
||||||
|
if (isset($intent['metadata']['tipoPago'])) {
|
||||||
|
$metadataTipoPago = $intent['metadata']['tipoPago'];
|
||||||
|
$this->logger->info("Direct Stripe API found metadata: tipoPago = '$metadataTipoPago'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($intent['payment_method_types'])) {
|
||||||
|
if (in_array('oxxo', $intent['payment_method_types'])) {
|
||||||
|
$metadataTipoPago = 'OXXO';
|
||||||
|
$this->logger->info("Direct Stripe API guessed 'OXXO' from payment_method_types");
|
||||||
|
break;
|
||||||
|
} elseif (in_array('customer_balance', $intent['payment_method_types']) || in_array('spei', $intent['payment_method_types'])) {
|
||||||
|
$metadataTipoPago = 'Transferencia Bancaria';
|
||||||
|
$this->logger->info("Direct Stripe API guessed 'Transferencia Bancaria' from payment_method_types");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->warning("Direct Stripe API fallback failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.5. Last-resort heuristic: use Stripe ID prefix when metadata and API both fail
|
||||||
|
// In Mexico ISP context: py_ prefix = OXXO (cash) payment, ch_ prefix = card charge
|
||||||
|
if (!$metadataTipoPago && !empty($metadataStripeId)) {
|
||||||
|
if (str_starts_with($metadataStripeId, 'py_')) {
|
||||||
|
$metadataTipoPago = 'OXXO';
|
||||||
|
$this->logger->info("Heuristic detection: stripe_id '$metadataStripeId' starts with 'py_' → assumed OXXO Pay");
|
||||||
|
} elseif (str_starts_with($metadataStripeId, 'ch_')) {
|
||||||
|
$this->logger->debug("Heuristic detection: stripe_id '$metadataStripeId' starts with 'ch_' → confirmed card");
|
||||||
|
// No change: will default to Tarjeta de crédito/débito
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
@ -821,9 +898,19 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
|
|
||||||
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 ||
|
||||||
|
stripos($methodName, 'Bank') !== false ||
|
||||||
|
stripos($methodName, 'transfer') !== false ||
|
||||||
|
stripos($methodName, 'ACH') !== false
|
||||||
|
) {
|
||||||
$targetValue = 'Transferencia Bancaria';
|
$targetValue = 'Transferencia Bancaria';
|
||||||
} elseif (stripos($methodName, 'Tarjeta') !== false && stripos($methodName, 'Stripe') !== false) {
|
} elseif (
|
||||||
|
stripos($methodName, 'Tarjeta') !== false ||
|
||||||
|
stripos($methodName, 'card') !== false ||
|
||||||
|
stripos($methodName, 'Crédito') !== false ||
|
||||||
|
stripos($methodName, 'débito') !== 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'");
|
||||||
|
|||||||
@ -74,7 +74,10 @@ class PaymentIntentService
|
|||||||
'stripeCustomerId' => $stripeCustomerId,
|
'stripeCustomerId' => $stripeCustomerId,
|
||||||
'clabeInterbancaria' => $clabeInterbancaria,
|
'clabeInterbancaria' => $clabeInterbancaria,
|
||||||
'email' => $this->getClientEmail($client),
|
'email' => $this->getClientEmail($client),
|
||||||
'accountOutstanding' => $client['accountOutstanding'] ?? 0
|
'accountOutstanding' => $client['accountOutstanding'] ?? 0,
|
||||||
|
'accountBalance' => $client['accountBalance'] ?? 0, // crédito a favor del cliente
|
||||||
|
'accountCredit' => $client['accountCredit'] ?? 0,
|
||||||
|
'fullAddress' => $client['fullAddress'] ?? '',
|
||||||
];
|
];
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return ['error' => $e->getMessage()];
|
return ['error' => $e->getMessage()];
|
||||||
@ -172,13 +175,14 @@ class PaymentIntentService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dt = (new \DateTime('@' . $payment->created))->setTimezone(new \DateTimeZone('America/Mexico_City'));
|
||||||
$result[] = [
|
$result[] = [
|
||||||
'id' => $payment->id,
|
'id' => $payment->id,
|
||||||
'amount' => $payment->amount / 100,
|
'amount' => $payment->amount / 100,
|
||||||
'currency' => strtoupper($payment->currency),
|
'currency' => strtoupper($payment->currency),
|
||||||
'status' => $payment->status,
|
'status' => $payment->status,
|
||||||
'created' => $payment->created,
|
'created' => $payment->created,
|
||||||
'date' => date('d/m/Y H:i', $payment->created),
|
'date' => $dt->format('d/m/Y H:i'),
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
'reference' => $reference
|
'reference' => $reference
|
||||||
];
|
];
|
||||||
|
|||||||
21
test_patch.php
Normal file
21
test_patch.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
$config = json_decode(file_get_contents(__DIR__ . '/data/config.json'), true);
|
||||||
|
$ucrmOptions = [
|
||||||
|
'base_uri' => 'https://localhost/crm/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => $config['apitoken'],
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$ucrmClient = new \GuzzleHttp\Client($ucrmOptions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$res = $ucrmClient->patch('payments/1030', [
|
||||||
|
'json' => ['methodId' => 'b01c0b35-b42c-48d9-9ad9-ea6591adfbbb']
|
||||||
|
]);
|
||||||
|
echo "Success: " . $res->getStatusCode() . "\n";
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
5
test_script.php
Normal file
5
test_script.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
$ucrmApi = \Ubnt\UcrmPluginSdk\Service\UcrmApi::create();
|
||||||
|
$payments = $ucrmApi->get('payments?limit=5&direction=DESC');
|
||||||
|
print_r(array_map(function($p) { return ['id' => $p['id'], 'methodId' => $p['methodId'], 'note' => $p['note'], 'providerName' => $p['providerName']]; }, $payments));
|
||||||
14
test_script2.php
Normal file
14
test_script2.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
$options = [
|
||||||
|
'base_uri' => 'https://localhost/crm/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => json_decode(file_get_contents(__DIR__ . '/data/config.json'), true)['apitoken'],
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$client = new \GuzzleHttp\Client($options);
|
||||||
|
$response = $client->get('payments?limit=10&direction=DESC');
|
||||||
|
$payments = json_decode($response->getBody()->getContents(), true);
|
||||||
|
print_r(array_map(function($p) { return ['id' => $p['id'], 'methodId' => $p['methodId'], 'note' => $p['note']]; }, $payments));
|
||||||
44
test_stripe.php
Normal file
44
test_stripe.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
$config = json_decode(file_get_contents(__DIR__ . '/data/config.json'), true);
|
||||||
|
$ucrmOptions = [
|
||||||
|
'base_uri' => 'https://localhost/crm/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => $config['apitoken'],
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$ucrmClient = new \GuzzleHttp\Client($ucrmOptions);
|
||||||
|
|
||||||
|
// Fetch client 157
|
||||||
|
$res = $ucrmClient->get('clients/157');
|
||||||
|
$client = json_decode($res->getBody()->getContents(), true);
|
||||||
|
$stripeCustomerId = null;
|
||||||
|
foreach ($client['attributes'] as $attr) {
|
||||||
|
if (stripos($attr['name'], 'Stripe') !== false && stripos($attr['name'], 'Customer') !== false) {
|
||||||
|
$stripeCustomerId = $attr['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$stripeCustomerId) {
|
||||||
|
foreach ($client['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'stripeCustomerId' || $attr['key'] === 'customerIdStripe') {
|
||||||
|
$stripeCustomerId = $attr['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "Stripe Customer ID: $stripeCustomerId\n";
|
||||||
|
|
||||||
|
if ($stripeCustomerId && isset($config['tokenstripe'])) {
|
||||||
|
$stripeOptions = [
|
||||||
|
'base_uri' => 'https://api.stripe.com/v1/',
|
||||||
|
'auth' => [$config['tokenstripe'], ''],
|
||||||
|
];
|
||||||
|
$stripeClient = new \GuzzleHttp\Client($stripeOptions);
|
||||||
|
$res = $stripeClient->get("payment_intents?customer=$stripeCustomerId&limit=3");
|
||||||
|
$intents = json_decode($res->getBody()->getContents(), true);
|
||||||
|
foreach ($intents['data'] as $intent) {
|
||||||
|
echo "PaymentIntent: {$intent['id']}, Amount: {$intent['amount']}, Status: {$intent['status']}, Types: " . implode(',', $intent['payment_method_types']) . "\n";
|
||||||
|
echo "Metadata: " . json_encode($intent['metadata']) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
107
views/stripe.php
107
views/stripe.php
@ -38,12 +38,18 @@
|
|||||||
<h3>
|
<h3>
|
||||||
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="stripeClientName">Nombre del Cliente</span>
|
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="stripeClientName">Nombre del Cliente</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p style="color: var(--text-muted); margin: 0;">
|
<p style="color: var(--text-muted); margin: 0 0 3px 0;">
|
||||||
<span id="stripeClientIdDisplay" style="margin-right: 15px;">ID: #0</span>
|
<span id="stripeClientIdDisplay">ID: #0</span>
|
||||||
</p>
|
</p>
|
||||||
|
<p id="stripeClientAddressDisplay" style="color: var(--text-muted); margin: 0 0 12px 0; font-size: 0.82rem; display: none;"></p>
|
||||||
|
<!-- Botón Ver en CRM debajo del ID y domicilio -->
|
||||||
|
<a id="btnVerEnCrm" href="#" target="_blank" style="display:inline-flex;align-items:center;gap:9px;padding:7px 16px;background:rgba(255,255,255,0.05);border:1px solid var(--border);border-radius:9px;color:var(--text-main);text-decoration:none;font-size:0.85rem;font-weight:500;transition:background 0.2s;">
|
||||||
|
<img src="?action=image&file=crm.webp" style="width:30px;height:30px;">
|
||||||
|
Ver en CRM
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="client-balance">
|
<div class="client-balance">
|
||||||
<span class="label">Saldo Actual</span>
|
<span class="label">Saldo en CRM</span>
|
||||||
<span class="badge" id="stripeBalanceBadge">$0.00</span>
|
<span class="badge" id="stripeBalanceBadge">$0.00</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -77,23 +83,20 @@
|
|||||||
<label>Asignar a Administrador</label>
|
<label>Asignar a Administrador</label>
|
||||||
<select id="stripeAdminSelect" class="form-control">
|
<select id="stripeAdminSelect" class="form-control">
|
||||||
<option value="">-- Automático (Sistema) --</option>
|
<option value="">-- Automático (Sistema) --</option>
|
||||||
<?php foreach ($admins as $admin): echo "<option value='{$admin['id']}'>{$admin['nombre']}</option>";
|
<?php /** @var array{id: int, nombre: string}[] $admins */
|
||||||
|
foreach ($admins as $admin): echo "<option value='{$admin['id']}'>{$admin['nombre']}</option>";
|
||||||
endforeach; ?>
|
endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions-row">
|
<!-- Botón CTA centrado y destacado -->
|
||||||
<a id="btnVerEnCrm" href="#" target="_blank" class="btn btn-uniform btn-lg">
|
<div style="text-align:center; margin-top:1.8rem;">
|
||||||
<img src="?action=image&file=crm.webp" class="icon-crm">
|
<button id="btnCreateIntent" style="display:inline-flex;align-items:center;gap:10px;padding:15px 52px;font-size:1.05rem;font-weight:700;border:none;border-radius:14px;cursor:pointer;background:linear-gradient(135deg,#6c63ff,#4f46e5);color:#fff;box-shadow:0 6px 24px rgba(108,99,255,0.45);letter-spacing:0.3px;transition:transform 0.15s,box-shadow 0.15s;" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='0 10px 30px rgba(108,99,255,0.55)'" onmouseout="this.style.transform='';this.style.boxShadow='0 6px 24px rgba(108,99,255,0.45)'">
|
||||||
Ver en CRM
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
</a>
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
|
||||||
<button id="btnCreateIntent" class="btn btn-primary btn-lg">
|
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
|
|
||||||
<line x1="1" y1="10" x2="23" y2="10"></line>
|
|
||||||
</svg>
|
</svg>
|
||||||
Generar Referencia SPEI
|
Generar Intención de Pago
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -134,13 +137,38 @@
|
|||||||
|
|
||||||
document.getElementById('stripeClientName').textContent = data.fullName;
|
document.getElementById('stripeClientName').textContent = data.fullName;
|
||||||
document.getElementById('stripeClientIdDisplay').textContent = `ID: #${data.id}`;
|
document.getElementById('stripeClientIdDisplay').textContent = `ID: #${data.id}`;
|
||||||
document.getElementById('stripeBalanceBadge').textContent = `Saldo: $${parseFloat(data.accountOutstanding||0).toFixed(2)}`;
|
|
||||||
|
// Saldo en CRM: usar accountBalance (crédito a favor) o accountOutstanding (adeudo)
|
||||||
|
const crmCredit = parseFloat(data.accountBalance || 0);
|
||||||
|
const crmOwed = parseFloat(data.accountOutstanding || 0);
|
||||||
|
const balanceBadge = document.getElementById('stripeBalanceBadge');
|
||||||
|
if (crmCredit > 0) {
|
||||||
|
balanceBadge.textContent = `$${crmCredit.toFixed(2)} MXN`;
|
||||||
|
balanceBadge.style.color = 'var(--success)';
|
||||||
|
} else if (crmOwed > 0) {
|
||||||
|
balanceBadge.textContent = `-$${crmOwed.toFixed(2)} MXN`;
|
||||||
|
balanceBadge.style.color = 'var(--danger)';
|
||||||
|
} else {
|
||||||
|
balanceBadge.textContent = '$0.00 MXN';
|
||||||
|
balanceBadge.style.color = 'inherit';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('stripeCustomerIdDisplay').textContent = data.stripeCustomerId || 'No disponible';
|
document.getElementById('stripeCustomerIdDisplay').textContent = data.stripeCustomerId || 'No disponible';
|
||||||
document.getElementById('stripeClabeDisplay').textContent = data.clabeInterbancaria || 'No disponible';
|
document.getElementById('stripeClabeDisplay').textContent = data.clabeInterbancaria || 'No disponible';
|
||||||
document.getElementById('stripeAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
document.getElementById('stripeAmount').value = crmOwed > 0 ? crmOwed : '';
|
||||||
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
|
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
|
||||||
|
|
||||||
|
// Mostrar domicilio del cliente
|
||||||
|
const addrEl = document.getElementById('stripeClientAddressDisplay');
|
||||||
|
if (data.fullAddress) {
|
||||||
|
addrEl.textContent = '📍 ' + data.fullAddress;
|
||||||
|
addrEl.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
addrEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('stripeDetailContainer').style.display = 'block';
|
document.getElementById('stripeDetailContainer').style.display = 'block';
|
||||||
document.getElementById('stripePlaceholder').style.display = 'none'; // Hide placeholder
|
document.getElementById('stripePlaceholder').style.display = 'none';
|
||||||
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
|
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
|
||||||
else document.getElementById('stripeHistoryContainer').style.display = 'none';
|
else document.getElementById('stripeHistoryContainer').style.display = 'none';
|
||||||
});
|
});
|
||||||
@ -173,7 +201,8 @@
|
|||||||
showToast('Error de conexión', true);
|
showToast('Error de conexión', true);
|
||||||
}
|
}
|
||||||
btn.disabled=false;
|
btn.disabled=false;
|
||||||
btn.textContent = 'Generar Referencia SPEI';
|
btn.innerHTML=`<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg> Generar Intención de Pago`;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadStripeHistory(stripeCustomerId) {
|
async function loadStripeHistory(stripeCustomerId) {
|
||||||
@ -184,18 +213,25 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
container.style.display = 'block';
|
container.style.display = 'block';
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;padding:2rem;">Cargando historial...</td></tr>';
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="5" style="text-align:center;padding:2rem;">Cargando historial...</td>
|
||||||
|
</tr>`;
|
||||||
|
|
||||||
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_stripe_history&customerId=${stripeCustomerId}`);
|
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_stripe_history&customerId=${stripeCustomerId}`);
|
||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
|
|
||||||
if (d.cashBalance !== undefined) {
|
if (d.cashBalance !== undefined) {
|
||||||
|
const balanceMxn = (d.cashBalance / 100).toFixed(2);
|
||||||
|
// Solo actualizar badge del historial (saldo en caja de Stripe)
|
||||||
|
// El badge principal (#stripeBalanceBadge) muestra el saldo del CRM, no lo tocamos
|
||||||
cashBadge.style.display = 'flex';
|
cashBadge.style.display = 'flex';
|
||||||
cashText.textContent = `Saldo Stripe: $${(d.cashBalance/100).toFixed(2)} MXN`;
|
cashText.textContent = `Saldo en caja Stripe: $${balanceMxn} MXN`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!d.payments || d.payments.length === 0) {
|
if (!d.payments || d.payments.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;padding:2rem;color:var(--text-muted)">No hay intenciones de pago recientes</td></tr>';
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="5" style="text-align:center;padding:2rem;color:var(--text-muted)">No hay intenciones de pago recientes</td>
|
||||||
|
</tr>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,9 +241,12 @@
|
|||||||
if (p.status === 'succeeded') { statusColor = 'var(--success)'; statusLabel = 'PAGADO'; }
|
if (p.status === 'succeeded') { statusColor = 'var(--success)'; statusLabel = 'PAGADO'; }
|
||||||
if (p.status === 'processing') { statusColor = 'var(--warning)'; statusLabel = 'PENDIENTE'; }
|
if (p.status === 'processing') { statusColor = 'var(--warning)'; statusLabel = 'PENDIENTE'; }
|
||||||
|
|
||||||
|
// Badge construido con concatenación para evitar falsos errores del CSS linter
|
||||||
|
const badgeHtml = '<span class="badge" style="background:' + statusColor + '20; color:' + statusColor + '; border:1px solid ' + statusColor + '40;">' + statusLabel + '</span>';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr style="border-bottom: 1px solid var(--border);">
|
<tr style="border-bottom: 1px solid var(--border);">
|
||||||
<td style="padding:12px;"><span class="badge" style="background:${statusColor}20; color:${statusColor}; border:1px solid ${statusColor}40;">${statusLabel}</span></td>
|
<td style="padding:12px;">${badgeHtml}</td>
|
||||||
<td style="padding:12px; font-weight:600;">$${p.amount.toFixed(2)}</td>
|
<td style="padding:12px; font-weight:600;">$${p.amount.toFixed(2)}</td>
|
||||||
<td style="padding:12px; font-size:0.9rem;">${p.description}</td>
|
<td style="padding:12px; font-size:0.9rem;">${p.description}</td>
|
||||||
<td style="padding:12px; font-size:0.85rem; color:var(--text-muted);">${p.date}</td>
|
<td style="padding:12px; font-size:0.85rem; color:var(--text-muted);">${p.date}</td>
|
||||||
@ -217,7 +256,9 @@
|
|||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--danger)">Error de conexión</td></tr>';
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="5" style="text-align:center;color:var(--danger)">Error de conexión</td>
|
||||||
|
</tr>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,10 +288,26 @@
|
|||||||
<p style="font-size: 0.85rem; color: var(--text-muted); text-align: center; margin-top: 1.5rem;">
|
<p style="font-size: 0.85rem; color: var(--text-muted); text-align: center; margin-top: 1.5rem;">
|
||||||
Realiza tu transferencia vía SPEI con estos datos. El pago se registrará automáticamente al recibirse.
|
Realiza tu transferencia vía SPEI con estos datos. El pago se registrará automáticamente al recibirse.
|
||||||
</p>`;
|
</p>`;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
html += `<div style="background:rgba(255,165,0,0.1); padding: 15px; border-radius: 8px; border: 1px solid orange; color: orange; text-align:center;">
|
html += `<div style="background:rgba(255,165,0,0.1); padding: 15px; border-radius: 8px; border: 1px solid orange; color: orange; text-align:center;">
|
||||||
No se encontró información de transferencia. Por favor, revisa el historial.
|
Se generó la intención pero no se encontraron datos de transferencia SPEI.<br>Por favor, revisa el historial para ver si ya existe una CLABE asignada.
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
} else if (data.status === 'succeeded') {
|
||||||
|
html += `<div style="background:rgba(34,197,94,0.1); padding:18px; border-radius:10px; border:1px solid var(--success); color:var(--success); text-align:center;">
|
||||||
|
<div style="font-size:2rem; margin-bottom:8px;">✅</div>
|
||||||
|
<strong style="font-size:1.05rem;">Pago procesado automáticamente</strong>
|
||||||
|
<p style="margin:8px 0 0 0; font-size:0.9rem; opacity:0.85;">El saldo disponible en la caja de Stripe cubrió el monto completo.<br>No se requiere transferencia bancaria.</p>
|
||||||
|
</div>`;
|
||||||
|
} else if (data.status === 'processing') {
|
||||||
|
html += `<div style="background:rgba(59,130,246,0.1); padding:18px; border-radius:10px; border:1px solid #3b82f6; color:#3b82f6; text-align:center;">
|
||||||
|
<div style="font-size:2rem; margin-bottom:8px;">⏳</div>
|
||||||
|
<strong style="font-size:1.05rem;">Pago en proceso</strong>
|
||||||
|
<p style="margin:8px 0 0 0; font-size:0.9rem; opacity:0.85;">La transacción se está procesando. Revisa el historial en unos momentos.</p>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<div style="background:rgba(255,165,0,0.1); padding: 15px; border-radius: 8px; border: 1px solid orange; color: orange; text-align:center;">
|
||||||
|
Estado: ${data.status || 'Desconocido'}.<br>No se encontró información de transferencia. Por favor, revisa el historial.
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user