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:
DANYDHSV 2026-05-11 15:32:20 -06:00
parent 45a0d84caa
commit c8aec7caad
12 changed files with 8016 additions and 250 deletions

View File

@ -1,5 +1,11 @@
# 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
### 🚀 Nuevas Características (Features)

View File

@ -1,6 +1,6 @@
# SIIP - WhatsApp Notifications & Integrated Payment Portal
![Version](https://img.shields.io/badge/version-4.6.0-blue.svg?style=for-the-badge)
![Version](https://img.shields.io/badge/version-4.6.1-blue.svg?style=for-the-badge)
![UCRM Compatibility](https://img.shields.io/badge/UCRM-v2.1.0%2B-green.svg?style=for-the-badge)
![Status](https://img.shields.io/badge/status-PRODUCTION-success.svg?style=for-the-badge)
![Author](https://img.shields.io/badge/author-SIIP_INTERNET-orange.svg?style=for-the-badge)

File diff suppressed because one or more lines are too long

View File

@ -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",
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
"url": "https://siip.mx/",
"version": "4.6.0",
"version": "4.6.1",
"unmsVersionCompliancy": {
"min": "2.1.0",
"max": null
},
"author": "SIIP INTERNET",
"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",
"date": "2026-04-11",

View File

@ -339,9 +339,12 @@ if (isset($_GET['action'])) {
if ($_GET['action'] === 'get_stripe_history') {
$stripeCustomerId = $_GET['stripeCustomerId'] ?? $_GET['customerId'] ?? null;
if ($stripeCustomerId) {
// getCustomerCashBalance returns MXN (already /100), multiply by 100 so JS can do /100
$cashBalanceMxn = $paymentIntentService->getCustomerCashBalance($stripeCustomerId);
echo json_encode([
'success' => true,
'payments' => $paymentIntentService->getLastPayments($stripeCustomerId, 10)
'payments' => $paymentIntentService->getLastPayments($stripeCustomerId, 10),
'cashBalance' => $cashBalanceMxn * 100 // centavos, JS divides by 100
]);
} else {
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-item" onclick="showModule('installers')">
<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>
</div>
<div class="menu-item" onclick="showModule('notifications')">
<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>
</div>
<div class="menu-item" onclick="showModule('stripe')">
<img src="?action=image&file=stripe-logo.png">
<h3>Pagos SPEI</h3>
<p>Genera referencias de Transferencia Bancaria personalizadas.</p>
<h3>Generador de Intenciones de Pago Stripe</h3>
<p>Genera intenciones de pago vía Stripe personalizadas.</p>
</div>
<div class="menu-item" onclick="showModule('oxxo')">
<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>
</div>
</div>
@ -1817,16 +1820,16 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<!-- Tabs Navigation -->
<nav class="tabs">
<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 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 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 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>
</nav>
@ -1994,11 +1997,21 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
background: var(--bg-card);
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);">
Plugin Desarrollado por <strong>SIIP INTERNET</strong> - Todos los derechos reservados.
</p>
<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>
</footer>
@ -2045,8 +2058,6 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
let SYSTEM_USER_ID = <?php echo $currentUser ? $currentUser->userId : 'null'; ?>;
const store = {
installers: <?php echo json_encode($installersData['instaladores']); ?>,
theme: localStorage.getItem('theme') || 'light',
installers: <?php echo json_encode($installersData['instaladores']); ?>,
theme: localStorage.getItem('theme') || 'light',
crmUrl: '<?php echo $ucrmApiUrl; ?>',

View File

@ -713,10 +713,87 @@ abstract class AbstractStripeOperationsFacade
$metadataTipoPago = $data['metadata']['tipoPago'];
$this->logger->info("Microservice found metadata: tipoPago = '$metadataTipoPago'");
}
// Capture Stripe ID for prefix-based heuristic detection below
$metadataStripeId = $data['stripeId'] ?? null;
} catch (\Throwable $e) {
$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)
// UCRM API doesn't support PATCH userId, so we use microservice
if ($stripeUserId) {
@ -821,9 +898,19 @@ abstract class AbstractStripeOperationsFacade
if (stripos($methodName, 'OXXO') !== false) {
$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';
} 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';
}
$this->logger->debug("Fallback Method Guessing '$methodName' -> '$targetValue'");

View File

@ -74,7 +74,10 @@ class PaymentIntentService
'stripeCustomerId' => $stripeCustomerId,
'clabeInterbancaria' => $clabeInterbancaria,
'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) {
return ['error' => $e->getMessage()];
@ -172,13 +175,14 @@ class PaymentIntentService
}
}
$dt = (new \DateTime('@' . $payment->created))->setTimezone(new \DateTimeZone('America/Mexico_City'));
$result[] = [
'id' => $payment->id,
'amount' => $payment->amount / 100,
'currency' => strtoupper($payment->currency),
'status' => $payment->status,
'created' => $payment->created,
'date' => date('d/m/Y H:i', $payment->created),
'date' => $dt->format('d/m/Y H:i'),
'description' => $description,
'reference' => $reference
];

21
test_patch.php Normal file
View 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
View 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
View 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
View 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";
}
}

View File

@ -38,12 +38,18 @@
<h3>
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="stripeClientName">Nombre del Cliente</span>
</h3>
<p style="color: var(--text-muted); margin: 0;">
<span id="stripeClientIdDisplay" style="margin-right: 15px;">ID: #0</span>
<p style="color: var(--text-muted); margin: 0 0 3px 0;">
<span id="stripeClientIdDisplay">ID: #0</span>
</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 class="client-balance">
<span class="label">Saldo Actual</span>
<span class="label">Saldo en CRM</span>
<span class="badge" id="stripeBalanceBadge">$0.00</span>
</div>
</div>
@ -77,23 +83,20 @@
<label>Asignar a Administrador</label>
<select id="stripeAdminSelect" class="form-control">
<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; ?>
</select>
</div>
</div>
<div class="actions-row">
<a id="btnVerEnCrm" href="#" target="_blank" class="btn btn-uniform btn-lg">
<img src="?action=image&file=crm.webp" class="icon-crm">
Ver en CRM
</a>
<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>
<!-- Botón CTA centrado y destacado -->
<div style="text-align:center; margin-top:1.8rem;">
<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)'">
<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 Referencia SPEI
Generar Intención de Pago
</button>
</div>
</div>
@ -134,13 +137,38 @@
document.getElementById('stripeClientName').textContent = data.fullName;
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('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}`;
// 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('stripePlaceholder').style.display = 'none'; // Hide placeholder
document.getElementById('stripePlaceholder').style.display = 'none';
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
else document.getElementById('stripeHistoryContainer').style.display = 'none';
});
@ -173,7 +201,8 @@
showToast('Error de conexión', true);
}
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) {
@ -184,18 +213,25 @@
try {
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 d = await res.json();
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';
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) {
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;
}
@ -205,9 +241,12 @@
if (p.status === 'succeeded') { statusColor = 'var(--success)'; statusLabel = 'PAGADO'; }
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 `
<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-size:0.9rem;">${p.description}</td>
<td style="padding:12px; font-size:0.85rem; color:var(--text-muted);">${p.date}</td>
@ -217,7 +256,9 @@
} catch (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;">
Realiza tu transferencia vía SPEI con estos datos. El pago se registrará automáticamente al recibirse.
</p>`;
}
} 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;">
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>`;
}