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",
|
||||||
|
|||||||
39
public.php
39
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'");
|
||||||
|
|||||||
@ -67,14 +67,17 @@ class PaymentIntentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $client['id'],
|
'id' => $client['id'],
|
||||||
'fullName' => ($client['clientType'] == 1)
|
'fullName' => ($client['clientType'] == 1)
|
||||||
? $client['firstName'] . ' ' . $client['lastName']
|
? $client['firstName'] . ' ' . $client['lastName']
|
||||||
: $client['companyName'],
|
: $client['companyName'],
|
||||||
'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,15 +175,16 @@ 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
|
||||||
];
|
];
|
||||||
|
|
||||||
if (count($result) >= $limit) break;
|
if (count($result) >= $limit) break;
|
||||||
|
|||||||
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
497
views/stripe.php
497
views/stripe.php
@ -1,261 +1,318 @@
|
|||||||
<?php if (!isset($isModuleJs)): ?>
|
<?php if (!isset($isModuleJs)): ?>
|
||||||
<!-- MODULE 3: STRIPE -->
|
<!-- MODULE 3: STRIPE -->
|
||||||
<section id="section-pagos-spei" class="section-view">
|
<section id="section-pagos-spei" class="section-view">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div style="margin-bottom: 2rem;">
|
<div style="margin-bottom: 2rem;">
|
||||||
<h2 style="margin: 0; display: flex; align-items: center; gap: 10px;">
|
<h2 style="margin: 0; display: flex; align-items: center; gap: 10px;">
|
||||||
<img src="?action=get_image&name=online-payments-stripe.png" style="width:64px;height:64px;"> Pagos SPEI
|
<img src="?action=get_image&name=online-payments-stripe.png" style="width:64px;height:64px;"> Pagos SPEI
|
||||||
</h2>
|
</h2>
|
||||||
<p style="color: var(--text-muted); margin: 5px 0 0 0;">🏦 Genera referencias de transferencia bancaria para que tus clientes paguen vía SPEI</p>
|
<p style="color: var(--text-muted); margin: 5px 0 0 0;">🏦 Genera referencias de transferencia bancaria para que tus clientes paguen vía SPEI</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CONFIG CONTAINER -->
|
<!-- CONFIG CONTAINER -->
|
||||||
<div class="config-container">
|
<div class="config-container">
|
||||||
<div class="form-group" style="position: relative;">
|
<div class="form-group" style="position: relative;">
|
||||||
<label>Buscar Cliente para Stripe</label>
|
<label>Buscar Cliente para Stripe</label>
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="11" cy="11" r="8" />
|
<circle cx="11" cy="11" r="8" />
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" id="stripeSearch" class="form-control search-input-padded" placeholder="Buscar cliente..." autocomplete="off">
|
<input type="text" id="stripeSearch" class="form-control search-input-padded" placeholder="Buscar cliente..." autocomplete="off">
|
||||||
</div>
|
|
||||||
<div id="stripeResults" class="search-results"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PLACEHOLDER STATE (Initially Visible) -->
|
|
||||||
<div id="stripePlaceholder" class="placeholder-state">
|
|
||||||
<span class="placeholder-icon">🏦</span>
|
|
||||||
<p>Selecciona un cliente para generar una referencia de pago SPEI</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="stripeResults" class="search-results"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="stripeDetailContainer" class="card" style="display: none;">
|
<!-- PLACEHOLDER STATE (Initially Visible) -->
|
||||||
<!-- HEADER: CLIENT INFO & BALANCE -->
|
<div id="stripePlaceholder" class="placeholder-state">
|
||||||
<div class="client-header-grid">
|
<span class="placeholder-icon">🏦</span>
|
||||||
<div class="client-info">
|
<p>Selecciona un cliente para generar una referencia de pago SPEI</p>
|
||||||
<h3>
|
</div>
|
||||||
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="stripeClientName">Nombre del Cliente</span>
|
</div>
|
||||||
</h3>
|
|
||||||
<p style="color: var(--text-muted); margin: 0;">
|
<div id="stripeDetailContainer" class="card" style="display: none;">
|
||||||
<span id="stripeClientIdDisplay" style="margin-right: 15px;">ID: #0</span>
|
<!-- HEADER: CLIENT INFO & BALANCE -->
|
||||||
</p>
|
<div class="client-header-grid">
|
||||||
</div>
|
<div class="client-info">
|
||||||
<div class="client-balance">
|
<h3>
|
||||||
<span class="label">Saldo Actual</span>
|
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="stripeClientName">Nombre del Cliente</span>
|
||||||
<span class="badge" id="stripeBalanceBadge">$0.00</span>
|
</h3>
|
||||||
|
<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 en CRM</span>
|
||||||
|
<span class="badge" id="stripeBalanceBadge">$0.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TECHNICAL DETAILS GRID -->
|
||||||
|
<div class="details-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Stripe Customer ID</label>
|
||||||
|
<div class="value-box" id="stripeCustomerIdDisplay">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Clabe Interbancaria</label>
|
||||||
|
<div class="value-box" id="stripeClabeDisplay">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PAYMENT INTENT FORM -->
|
||||||
|
<div>
|
||||||
|
<h4 class="section-title">Generar Intención de Pago</h4>
|
||||||
|
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Monto a Cobrar</label>
|
||||||
|
<div class="input-group-unified">
|
||||||
|
<span class="input-prefix">$</span>
|
||||||
|
<input type="number" id="stripeAmount" class="form-control" placeholder="0.00">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TECHNICAL DETAILS GRID -->
|
<div class="form-group">
|
||||||
<div class="details-grid">
|
<label>Asignar a Administrador</label>
|
||||||
<div class="detail-item">
|
|
||||||
<label>Stripe Customer ID</label>
|
|
||||||
<div class="value-box" id="stripeCustomerIdDisplay">-</div>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<label>Clabe Interbancaria</label>
|
|
||||||
<div class="value-box" id="stripeClabeDisplay">-</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PAYMENT INTENT FORM -->
|
|
||||||
<div>
|
|
||||||
<h4 class="section-title">Generar Intención de Pago</h4>
|
|
||||||
|
|
||||||
<div class="form-grid">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Monto a Cobrar</label>
|
|
||||||
<div class="input-group-unified">
|
|
||||||
<span class="input-prefix">$</span>
|
|
||||||
<input type="number" id="stripeAmount" class="form-control" placeholder="0.00">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<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 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>
|
|
||||||
</svg>
|
|
||||||
Generar Referencia SPEI
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="stripeHistoryContainer" style="display:none; margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1.5rem;">
|
<!-- Botón CTA centrado y destacado -->
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
<div style="text-align:center; margin-top:1.8rem;">
|
||||||
<h4 class="section-title" style="margin-bottom: 0;">Historial de Intenciones de Pago por transferencia</h4>
|
<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)'">
|
||||||
<div id="stripeCashBalanceBadge" class="balance-badge" style="display:none;">
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
<img src="?action=image&file=account-balance.webp" style="width: 32px; height: 32px; margin-right: 8px;">
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
|
||||||
<span id="stripeCashBalanceText">Saldo Stripe: $0.00 MXN</span>
|
</svg>
|
||||||
</div>
|
Generar Intención de Pago
|
||||||
</div>
|
</button>
|
||||||
<div style="overflow-x: auto;">
|
|
||||||
<table class="table" id="stripeHistoryTable" style="width: 100%; border-collapse: collapse;">
|
|
||||||
<thead>
|
|
||||||
<tr style="text-align: left; background: var(--bg-body);">
|
|
||||||
<th style="padding: 10px;">Estado</th>
|
|
||||||
<th style="padding: 10px;">Importe</th>
|
|
||||||
<th style="padding: 10px;">Descripción</th>
|
|
||||||
<th style="padding: 10px;">Fecha</th>
|
|
||||||
<th style="padding: 10px;">ID</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="stripeHistoryContainer" style="display:none; margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1.5rem;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||||
|
<h4 class="section-title" style="margin-bottom: 0;">Historial de Intenciones de Pago por transferencia</h4>
|
||||||
|
<div id="stripeCashBalanceBadge" class="balance-badge" style="display:none;">
|
||||||
|
<img src="?action=image&file=account-balance.webp" style="width: 32px; height: 32px; margin-right: 8px;">
|
||||||
|
<span id="stripeCashBalanceText">Saldo Stripe: $0.00 MXN</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="overflow-x: auto;">
|
||||||
|
<table class="table" id="stripeHistoryTable" style="width: 100%; border-collapse: collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align: left; background: var(--bg-body);">
|
||||||
|
<th style="padding: 10px;">Estado</th>
|
||||||
|
<th style="padding: 10px;">Importe</th>
|
||||||
|
<th style="padding: 10px;">Descripción</th>
|
||||||
|
<th style="padding: 10px;">Fecha</th>
|
||||||
|
<th style="padding: 10px;">ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// 2. STRIPE SEARCH
|
// 2. STRIPE SEARCH
|
||||||
let selectedStripeClient = null;
|
let selectedStripeClient = null;
|
||||||
setupSearch('stripeSearch', 'stripeResults', 'search_stripe', async (partialClient) => {
|
setupSearch('stripeSearch', 'stripeResults', 'search_stripe', async (partialClient) => {
|
||||||
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_stripe_details&id=${partialClient.id}`);
|
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_stripe_details&id=${partialClient.id}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
selectedStripeClient = data;
|
selectedStripeClient = data;
|
||||||
|
|
||||||
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)}`;
|
|
||||||
document.getElementById('stripeCustomerIdDisplay').textContent = data.stripeCustomerId || 'No disponible';
|
// Saldo en CRM: usar accountBalance (crédito a favor) o accountOutstanding (adeudo)
|
||||||
document.getElementById('stripeClabeDisplay').textContent = data.clabeInterbancaria || 'No disponible';
|
const crmCredit = parseFloat(data.accountBalance || 0);
|
||||||
document.getElementById('stripeAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
const crmOwed = parseFloat(data.accountOutstanding || 0);
|
||||||
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
|
const balanceBadge = document.getElementById('stripeBalanceBadge');
|
||||||
document.getElementById('stripeDetailContainer').style.display = 'block';
|
if (crmCredit > 0) {
|
||||||
document.getElementById('stripePlaceholder').style.display = 'none'; // Hide placeholder
|
balanceBadge.textContent = `$${crmCredit.toFixed(2)} MXN`;
|
||||||
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
|
balanceBadge.style.color = 'var(--success)';
|
||||||
else document.getElementById('stripeHistoryContainer').style.display = 'none';
|
} 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 = 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';
|
||||||
|
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
|
||||||
|
else document.getElementById('stripeHistoryContainer').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnCreateIntent').onclick = async () => {
|
||||||
|
if (!selectedStripeClient?.stripeCustomerId) return showToast('Error: Cliente sin Stripe ID', true);
|
||||||
|
const amt = parseFloat(document.getElementById('stripeAmount').value);
|
||||||
|
if (!amt || amt < 10) return showToast('Mínimo 10 MXN', true);
|
||||||
|
|
||||||
|
const btn=document.getElementById('btnCreateIntent');
|
||||||
|
btn.disabled=true;
|
||||||
|
btn.textContent='Procesando...' ;
|
||||||
|
|
||||||
|
const fd=new FormData();
|
||||||
|
fd.append('action', 'create_intent' );
|
||||||
|
fd.append('clientId', selectedStripeClient.id);
|
||||||
|
fd.append('amount', amt);
|
||||||
|
fd.append('stripeCustomerId', selectedStripeClient.stripeCustomerId);
|
||||||
|
fd.append('adminId', document.getElementById('stripeAdminSelect').value || store.defaultStripeAdminId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res=await fetch(`${window.SIIP_STRIPE_PATH || '' }?`, {
|
||||||
|
method: 'POST' ,
|
||||||
|
body: fd
|
||||||
});
|
});
|
||||||
|
const d=await res.json();
|
||||||
document.getElementById('btnCreateIntent').onclick = async () => {
|
if (d.success) showStripeResult(d);
|
||||||
if (!selectedStripeClient?.stripeCustomerId) return showToast('Error: Cliente sin Stripe ID', true);
|
else showToast(d.error, true);
|
||||||
const amt = parseFloat(document.getElementById('stripeAmount').value);
|
} catch (e) {
|
||||||
if (!amt || amt < 10) return showToast('Mínimo 10 MXN', true);
|
showToast('Error de conexión', true);
|
||||||
|
}
|
||||||
const btn = document.getElementById('btnCreateIntent');
|
btn.disabled=false;
|
||||||
btn.disabled = true;
|
btn.innerHTML=`<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
btn.textContent = 'Procesando...';
|
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg> Generar Intención de Pago`;
|
||||||
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('action', 'create_intent');
|
|
||||||
fd.append('clientId', selectedStripeClient.id);
|
|
||||||
fd.append('amount', amt);
|
|
||||||
fd.append('stripeCustomerId', selectedStripeClient.stripeCustomerId);
|
|
||||||
fd.append('adminId', document.getElementById('stripeAdminSelect').value || store.defaultStripeAdminId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: fd
|
|
||||||
});
|
|
||||||
const d = await res.json();
|
|
||||||
if (d.success) showStripeResult(d);
|
|
||||||
else showToast(d.error, true);
|
|
||||||
} catch (e) {
|
|
||||||
showToast('Error de conexión', true);
|
|
||||||
}
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = 'Generar Referencia SPEI';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadStripeHistory(stripeCustomerId) {
|
async function loadStripeHistory(stripeCustomerId) {
|
||||||
const container = document.getElementById('stripeHistoryContainer');
|
const container = document.getElementById('stripeHistoryContainer');
|
||||||
const tbody = document.querySelector('#stripeHistoryTable tbody');
|
const tbody = document.querySelector('#stripeHistoryTable tbody');
|
||||||
const cashBadge = document.getElementById('stripeCashBalanceBadge');
|
const cashBadge = document.getElementById('stripeCashBalanceBadge');
|
||||||
const cashText = document.getElementById('stripeCashBalanceText');
|
const cashText = document.getElementById('stripeCashBalanceText');
|
||||||
|
|
||||||
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) {
|
||||||
cashBadge.style.display = 'flex';
|
const balanceMxn = (d.cashBalance / 100).toFixed(2);
|
||||||
cashText.textContent = `Saldo Stripe: $${(d.cashBalance/100).toFixed(2)} MXN`;
|
// 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 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>
|
||||||
return;
|
<td colspan="5" style="text-align:center;padding:2rem;color:var(--text-muted)">No hay intenciones de pago recientes</td>
|
||||||
}
|
</tr>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tbody.innerHTML = d.payments.map(p => {
|
tbody.innerHTML = d.payments.map(p => {
|
||||||
let statusColor = 'var(--text-muted)';
|
let statusColor = 'var(--text-muted)';
|
||||||
let statusLabel = p.status.toUpperCase();
|
let statusLabel = p.status.toUpperCase();
|
||||||
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'; }
|
||||||
|
|
||||||
return `
|
// Badge construido con concatenación para evitar falsos errores del CSS linter
|
||||||
<tr style="border-bottom: 1px solid var(--border);">
|
const badgeHtml = '<span class="badge" style="background:' + statusColor + '20; color:' + statusColor + '; border:1px solid ' + statusColor + '40;">' + statusLabel + '</span>';
|
||||||
<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; 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>
|
|
||||||
<td style="padding:12px; font-family:monospace; font-size:0.8rem; color:var(--text-muted);">${p.id}</td>
|
|
||||||
</tr>`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
} catch (e) {
|
return `
|
||||||
console.error(e);
|
<tr style="border-bottom: 1px solid var(--border);">
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--danger)">Error de conexión</td></tr>';
|
<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>
|
||||||
|
<td style="padding:12px; font-family:monospace; font-size:0.8rem; color:var(--text-muted);">${p.id}</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="5" style="text-align:center;color:var(--danger)">Error de conexión</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStripeResult(data) {
|
function showStripeResult(data) {
|
||||||
const c = document.getElementById('stripeResultContent');
|
const c = document.getElementById('stripeResultContent');
|
||||||
let html = `<div style="text-align:center; margin-bottom: 20px;">
|
let html = `<div style="text-align:center; margin-bottom: 20px;">
|
||||||
<h3 style="color:var(--success); margin-bottom: 5px;">¡Referencia Creada!</h3>
|
<h3 style="color:var(--success); margin-bottom: 5px;">¡Referencia Creada!</h3>
|
||||||
<p style="font-size: 1.1rem; color: var(--text-main);">Monto: <strong>$${data.amount} MXN</strong></p>
|
<p style="font-size: 1.1rem; color: var(--text-main);">Monto: <strong>$${data.amount} MXN</strong></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
if (data.next_action?.display_bank_transfer_instructions) {
|
if (data.next_action?.display_bank_transfer_instructions) {
|
||||||
const instr = data.next_action.display_bank_transfer_instructions;
|
const instr = data.next_action.display_bank_transfer_instructions;
|
||||||
const spei = instr.financial_addresses[0]?.spei;
|
const spei = instr.financial_addresses[0]?.spei;
|
||||||
|
|
||||||
if (spei) {
|
if (spei) {
|
||||||
html += `
|
html += `
|
||||||
<div style="background:#f8fafc; border: 1px solid #e2e8f0; padding:1.5rem; border-radius:12px; color: #1e293b; text-align: left;">
|
<div style="background:#f8fafc; border: 1px solid #e2e8f0; padding:1.5rem; border-radius:12px; color: #1e293b; text-align: left;">
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem;">
|
||||||
<span style="display:block; font-size: 0.8rem; color: #64748b; text-transform: uppercase; font-weight: 600;">Institución Bancaria</span>
|
<span style="display:block; font-size: 0.8rem; color: #64748b; text-transform: uppercase; font-weight: 600;">Institución Bancaria</span>
|
||||||
<strong style="font-size: 1.1rem;">${spei.bank_name}</strong>
|
<strong style="font-size: 1.1rem;">${spei.bank_name}</strong>
|
||||||
</div>
|
|
||||||
<div style="margin-bottom: 0;">
|
|
||||||
<span style="display:block; font-size: 0.8rem; color: #64748b; text-transform: uppercase; font-weight: 600;">CLABE Interbancaria</span>
|
|
||||||
<strong style="font-size: 1.3rem; letter-spacing: 1px; font-family: monospace; display: block; margin-top: 5px;">${spei.clabe}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p style="font-size: 0.85rem; color: var(--text-muted); text-align: center; margin-top: 1.5rem;">
|
<div style="margin-bottom: 0;">
|
||||||
Realiza tu transferencia vía SPEI con estos datos. El pago se registrará automáticamente al recibirse.
|
<span style="display:block; font-size: 0.8rem; color: #64748b; text-transform: uppercase; font-weight: 600;">CLABE Interbancaria</span>
|
||||||
</p>`;
|
<strong style="font-size: 1.3rem; letter-spacing: 1px; font-family: monospace; display: block; margin-top: 5px;">${spei.clabe}</strong>
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 {
|
} 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>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
} else if (data.status === 'succeeded') {
|
||||||
c.innerHTML = html;
|
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;">
|
||||||
document.getElementById('stripeResultModal').style.display = 'flex';
|
<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>`;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
<?php endif; ?>
|
c.innerHTML = html;
|
||||||
|
document.getElementById('stripeResultModal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
Loading…
Reference in New Issue
Block a user