Se ajustó el campo de RESUMEN que se sincroniza con callbell par redicir los caracteres a menos de 500 porque se estaban rechazando por la API de callbell
This commit is contained in:
parent
5c2ab3de74
commit
01f42e07d7
7
.gitignore
vendored
7
.gitignore
vendored
@ -4,5 +4,12 @@
|
||||
*.jpeg
|
||||
.vscode/
|
||||
*.zip
|
||||
*.apib
|
||||
*.jrxml
|
||||
*.jasper
|
||||
vouchers_oxxo/
|
||||
comprobantes/
|
||||
pack-plugin-versioned.php
|
||||
*.txt
|
||||
Callbell Public API.postman_collection.json
|
||||
unms-swagger.json
|
||||
|
||||
0
Datos de la versión para el equipo - WhatsApp.txt
Normal file → Executable file
0
Datos de la versión para el equipo - WhatsApp.txt
Normal file → Executable file
@ -1,35 +1 @@
|
||||
{
|
||||
"ipserver": "venus.siip.mx",
|
||||
"apitoken": "gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9",
|
||||
"unmsApiToken": "4f5219de-cc5b-413d-b2fb-5133d02f3b26",
|
||||
"tokencallbell": "g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79",
|
||||
"tokenstripe": "sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M",
|
||||
"hostServerFTP": "siip.mx",
|
||||
"usernameServerFTP": "siip0001",
|
||||
"passServerFTP": "$spGiT,[wa)n",
|
||||
"ipPuppeteer": "172.16.5.134",
|
||||
"portPuppeteer": "4100",
|
||||
"idPaymentAdminCRM": "1180",
|
||||
"cashPaymentMethodId": true,
|
||||
"courtesyPaymentMethodId": true,
|
||||
"bankTransferPaymentMethodId": true,
|
||||
"paypalPaymentMethodId": true,
|
||||
"creditCardPaypalPaymentMethodId": true,
|
||||
"creditCardStripePaymentMethodId": true,
|
||||
"stripeSubscriptionCreditCardPaymentMethodId": true,
|
||||
"paypalSubscriptionPaymentMethodId": true,
|
||||
"mercadopagoPaymentMethodId": true,
|
||||
"checkPaymentMethodId": true,
|
||||
"customPaymentMethodId": true,
|
||||
"oxxoPayPaymentMethodId": true,
|
||||
"creditDebitCardPaymentMethodId": true,
|
||||
"notificationTypeText": false,
|
||||
"installersDataWhatsApp": "{\"instaladores\":[{\"id\":1019,\"nombre\":\"Mucio Robledo\",\"whatsapp\":\"4181878106\"},{\"id\":1173,\"nombre\":\"Ángel Arvizu\",\"whatsapp\":\"4181878106\"},{\"id\":1172,\"nombre\":\"Juan Rostro\",\"whatsapp\":\"4181878106\"},{\"id\":1015,\"nombre\":\"Daniel Humberto\",\"whatsapp\":\"4181878106\"},{\"id\":1131,\"nombre\":\"Gricelda Avalos\",\"whatsapp\":\"4181817609\"},{\"id\":\"1184\",\"nombre\":\"José Luis Enrique Sánchez\",\"whatsapp\":\"4181878106\"}]}",
|
||||
"debugMode": true,
|
||||
"minioEndpoint": "http:\/\/172.16.5.134:9002",
|
||||
"minioPublicUrl": "https:\/\/aws-venus.siip.mx",
|
||||
"minioAccessKey": "minioadmin",
|
||||
"minioSecretKey": "minioadmin",
|
||||
"minioBucket": "vouchers-oxxo",
|
||||
"logging_level": true
|
||||
}
|
||||
{"ipserver":"venus.siip.mx","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"4f5219de-cc5b-413d-b2fb-5133d02f3b26","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"4100","idPaymentAdminCRM":"1180","cashPaymentMethodId":true,"courtesyPaymentMethodId":true,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"oxxoPayPaymentMethodId":true,"creditDebitCardPaymentMethodId":true,"notificationTypeText":false,"installersDataWhatsApp":"{\"instaladores\":[{\"id\":1019,\"nombre\":\"Mucio Robledo\",\"whatsapp\":\"4181878106\"},{\"id\":1173,\"nombre\":\"Ángel Arvizu\",\"whatsapp\":\"4181878106\"},{\"id\":1172,\"nombre\":\"Juan Rostro\",\"whatsapp\":\"4181878106\"},{\"id\":1015,\"nombre\":\"Daniel Humberto\",\"whatsapp\":\"4181878106\"},{\"id\":1131,\"nombre\":\"Gricelda Avalos\",\"whatsapp\":\"4181817609\"},{\"id\":\"1184\",\"nombre\":\"José Luis Enrique Sánchez\",\"whatsapp\":\"4181878106\"}]}","debugMode":true,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo","logging_level":true}
|
||||
290
data/plugin.log
290
data/plugin.log
File diff suppressed because one or more lines are too long
445
public.php
Normal file → Executable file
445
public.php
Normal file → Executable file
@ -1954,244 +1954,8 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODULE 3: STRIPE -->
|
||||
<section id="section-pagos-spei" class="section-view">
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<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
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG CONTAINER -->
|
||||
<div class="config-container">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label>Buscar Cliente para Stripe</label>
|
||||
<div class="search-wrapper">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<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="stripeDetailContainer" class="card" style="display: none;">
|
||||
<!-- HEADER: CLIENT INFO & BALANCE -->
|
||||
<div class="client-header-grid">
|
||||
<div class="client-info">
|
||||
<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>
|
||||
</div>
|
||||
<div class="client-balance">
|
||||
<span class="label">Saldo Actual</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 class="form-group">
|
||||
<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>";
|
||||
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>
|
||||
</svg>
|
||||
Generar Referencia SPEI
|
||||
</button>
|
||||
</div>
|
||||
</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 Pagos (Últimos 10)</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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODULE 4: OXXO -->
|
||||
<section id="section-pagos-oxxo" class="section-view">
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<h2 style="margin: 0; display: flex; align-items: center; gap: 10px;">
|
||||
<img src="?action=get_image&name=oxxo-payments.png" style="width:64px;height:64px;"> Pagos OXXO
|
||||
</h2>
|
||||
<p style="color: var(--text-muted); margin: 5px 0 0 0;">🏪 Genera fichas de pago OXXO para que tus clientes paguen en tiendas de conveniencia</p>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG CONTAINER -->
|
||||
<div class="config-container">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label>Buscar Cliente para OXXO</label>
|
||||
<div class="search-wrapper">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<input type="text" id="oxxoSearch" class="form-control search-input-padded" placeholder="Buscar cliente..." autocomplete="off">
|
||||
</div>
|
||||
<div id="oxxoResults" class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PLACEHOLDER STATE (Initially Visible) -->
|
||||
<div id="oxxoModulePlaceholder" class="placeholder-state">
|
||||
<span class="placeholder-icon">🏪</span>
|
||||
<p>Selecciona un cliente para generar una ficha OXXO Pay</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="oxxoDetailContainer" style="display: none; margin-top: 2rem;">
|
||||
<div class="stripe-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start;">
|
||||
<!-- Left Column: Form & Client Info -->
|
||||
<div class="card" style="height: 100%; display: flex; flex-direction: column;">
|
||||
<!-- Client Header -->
|
||||
<div style="border-bottom: 1px solid var(--border); padding-bottom: 1rem; margin-bottom: 1.5rem;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 0.5rem;">
|
||||
<img src="?action=image&file=client.webp" class="client-header-icon" style="width: 32px; height: 32px; border-radius: 50%;">
|
||||
<h3 style="margin: 0; font-size: 1.1rem; color: var(--text-main);"><span id="oxxoClientName">Nombre del Cliente</span></h3>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<p id="oxxoClientIdDisplay" style="color: var(--text-muted); margin: 0; font-size: 0.9rem;">ID: #0</p>
|
||||
<span class="badge" id="oxxoBalanceBadge" style="background: #fee2e2; color: #ef4444; font-size: 0.8rem; padding: 2px 8px; border-radius: 4px;">Saldo: $0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OXXO Form -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
||||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||||
<img src="?action=image&file=oxxo-logo.png" style="max-width: 100px; height: auto;">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label style="font-weight: 500; margin-bottom: 0.5rem; display: block; color: var(--text-main);">Monto a Cobrar</label>
|
||||
<div class="input-group-unified">
|
||||
<span class="input-prefix">$</span>
|
||||
<input type="number" id="oxxoAmount" class="form-control" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-row" style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<button id="btnCreateOxxoIntent" class="btn btn-primary" style="width: 100%; justify-content: center; background-color: #E20613; border-color: #E20613; color: white; display: flex; align-items: center; gap: 10px; padding: 12px; border-radius: 8px; font-weight: 600; font-size: 1rem; transition: all 0.2s;">
|
||||
Generar Ficha OXXO Pay
|
||||
</button>
|
||||
<a id="btnOxxoCrm" href="#" target="_blank" class="btn btn-uniform" style="text-align: center; display: flex; justify-content: center; width: 100%; padding: 12px; border-radius: 8px; font-size: 1rem;">
|
||||
<img src="?action=image&file=crm.webp" class="icon-crm" style="vertical-align: middle; margin-right: 5px; width: 16px;"> Ver en CRM
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Ficha Preview -->
|
||||
<div class="card" id="oxxoFichaContainer" style="height: 100%; min-height: 450px; display: flex; align-items: center; justify-content: center; background: var(--bg-body); border: 1px dashed var(--border); position: relative;">
|
||||
<div id="oxxoPlaceholder" style="text-align: center; color: var(--text-muted);">
|
||||
<img src="?action=image&file=oxxo-payments.png" style="max-width: 80px; opacity: 0.5; margin-bottom: 1rem; filter: grayscale(1);">
|
||||
<h4 style="margin: 0; font-weight: 500;">Vista Previa</h4>
|
||||
<p style="margin: 5px 0 0 0; font-size: 0.9rem;">La ficha generada aparecerá aquí</p>
|
||||
</div>
|
||||
<div id="oxxoResult" style="display:none; width: 100%; height: 100%;">
|
||||
<!-- Generated Ficha will be injected here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- OXXO HISTORY CONTAINER -->
|
||||
<div id="oxxoHistoryContainer" style="display: none; margin-top: 2rem;">
|
||||
<div class="card">
|
||||
<h3 class="section-title" style="margin-bottom: 1rem;">Historial de Fichas OXXO (Últimas 5)</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="oxxoHistoryTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID Pago</th>
|
||||
<th>Fecha</th>
|
||||
<th>Monto</th>
|
||||
<th>Ref. OXXO</th>
|
||||
<th>Estatus</th>
|
||||
<th>Ficha</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="6" style="text-align:center">Seleccione un cliente...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php include __DIR__ . '/views/stripe.php'; ?>
|
||||
<?php include __DIR__ . '/views/oxxo.php'; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -2537,210 +2301,9 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
||||
showToast(d.message, !d.success);
|
||||
}
|
||||
|
||||
// 2. STRIPE SEARCH
|
||||
let selectedStripeClient = null;
|
||||
setupSearch('stripeSearch', 'stripeResults', 'search_stripe', async (partialClient) => {
|
||||
const res = await fetch(`?action=get_stripe_details&id=${partialClient.id}`);
|
||||
const data = await res.json();
|
||||
selectedStripeClient = data;
|
||||
<?php $isModuleJs = true; include __DIR__ . '/views/stripe.php'; ?>
|
||||
|
||||
document.getElementById('stripeClientName').textContent = data.fullName;
|
||||
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';
|
||||
document.getElementById('stripeClabeDisplay').textContent = data.clabeInterbancaria || 'No disponible';
|
||||
document.getElementById('stripeAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
||||
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
|
||||
document.getElementById('stripeDetailContainer').style.display = 'block';
|
||||
document.getElementById('stripePlaceholder').style.display = 'none'; // Hide placeholder
|
||||
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('?', {
|
||||
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';
|
||||
};
|
||||
|
||||
function showStripeResult(data) {
|
||||
const c = document.getElementById('stripeResultContent');
|
||||
c.innerHTML = `<h3 style="color:var(--success);text-align:center">¡Referencia Creada!</h3><p style="text-align:center">Monto: $${data.amount}</p>`;
|
||||
if (data.next_action?.display_bank_transfer_instructions) {
|
||||
const i = data.next_action.display_bank_transfer_instructions.financial_addresses[0].spei;
|
||||
c.innerHTML += `<div style="background:#f1f5f9;padding:15px;border-radius:8px;margin-top:10px"><p><strong>Banco:</strong> ${i.bank_name}</p><p><strong>CLABE:</strong> ${i.clabe}</p></div>`;
|
||||
}
|
||||
document.getElementById('stripeResultModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
async function loadOxxoHistory(stripeCustomerId) {
|
||||
const tbody = document.querySelector('#oxxoHistoryTable tbody');
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center">Cargando historial...</td></tr>';
|
||||
try {
|
||||
const res = await fetch(`?action=get_oxxo_history&stripeCustomerId=${stripeCustomerId}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--danger)">Error: ${data.error}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.history || data.history.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center">No hay fichas recientes</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.history.map(p => {
|
||||
let statusBadge = '';
|
||||
switch (p.status) {
|
||||
case 'succeeded':
|
||||
statusBadge = '<span class="badge badge-success">Pagado</span>';
|
||||
break;
|
||||
case 'requires_action':
|
||||
statusBadge = '<span class="badge badge-warning">Pendiente</span>';
|
||||
break;
|
||||
case 'canceled':
|
||||
statusBadge = '<span class="badge badge-danger">Cancelado</span>';
|
||||
break;
|
||||
default:
|
||||
statusBadge = `<span class="badge badge-secondary">${p.status}</span>`;
|
||||
}
|
||||
|
||||
const date = new Date(p.created * 1000).toLocaleString();
|
||||
const voucherBtn = p.voucherUrl ?
|
||||
`<a href="${p.voucherUrl}" target="_blank" class="btn btn-uniform" style="padding: 4px 12px; font-size: 0.8rem;">Ver Ficha</a>` :
|
||||
'-';
|
||||
|
||||
return `<tr>
|
||||
<td><small style="font-family:monospace">${p.id.slice(-8)}</small></td>
|
||||
<td>${date}</td>
|
||||
<td>$${p.amount.toFixed(2)} ${p.currency}</td>
|
||||
<td style="font-family:monospace">${p.oxxoNumber}</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>${voucherBtn}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--danger)">Error de conexión</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// 3. OXXO SEARCH
|
||||
let selectedOxxoClient = null;
|
||||
setupSearch('oxxoSearch', 'oxxoResults', 'search_stripe', async (partialClient) => {
|
||||
const res = await fetch(`?action=get_stripe_details&id=${partialClient.id}`);
|
||||
const data = await res.json();
|
||||
selectedOxxoClient = data;
|
||||
|
||||
document.getElementById('oxxoClientName').textContent = data.fullName;
|
||||
document.getElementById('oxxoClientIdDisplay').textContent = `ID: #${data.id}`;
|
||||
document.getElementById('oxxoBalanceBadge').textContent = `Saldo: $${parseFloat(data.accountOutstanding||0).toFixed(2)}`;
|
||||
document.getElementById('oxxoAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
||||
document.getElementById('btnOxxoCrm').href = `${store.publicUrl}/client/${data.id}`;
|
||||
|
||||
// Reset View
|
||||
document.getElementById('oxxoPlaceholder').style.display = 'block'; // This is the PREVIEW placeholder (keep as is)
|
||||
document.getElementById('oxxoModulePlaceholder').style.display = 'none'; // Hide MODULE placeholder
|
||||
document.getElementById('oxxoResult').style.display = 'none';
|
||||
document.getElementById('oxxoResult').innerHTML = '';
|
||||
|
||||
document.getElementById('oxxoDetailContainer').style.display = 'block';
|
||||
|
||||
// Load History
|
||||
if (data.stripeCustomerId) {
|
||||
document.getElementById('oxxoHistoryContainer').style.display = 'block';
|
||||
loadOxxoHistory(data.stripeCustomerId);
|
||||
} else {
|
||||
document.getElementById('oxxoHistoryContainer').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnCreateOxxoIntent').onclick = async () => {
|
||||
if (!selectedOxxoClient) return;
|
||||
const amt = parseFloat(document.getElementById('oxxoAmount').value);
|
||||
if (!amt || amt < 10) return showToast('Mínimo 10 MXN', true);
|
||||
|
||||
const btn = document.getElementById('btnCreateOxxoIntent');
|
||||
const original = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner" style="border: 2px solid #fff; border-top: 2px solid transparent; width: 16px; height: 16px; border-radius: 50%; display: inline-block; animation: spin 1s linear infinite; margin-right: 8px;"></span> Procesando...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'create_oxxo_intent');
|
||||
fd.append('clientId', selectedOxxoClient.id);
|
||||
fd.append('amount', amt);
|
||||
|
||||
try {
|
||||
const res = await fetch('?', {
|
||||
method: 'POST',
|
||||
body: fd
|
||||
});
|
||||
const d = await res.json();
|
||||
if (d.success) {
|
||||
const url = d.data.voucher_image_url || `?action=image&file=${d.data.voucher_filename}`;
|
||||
|
||||
// Hide Placeholder
|
||||
document.getElementById('oxxoPlaceholder').style.display = 'none';
|
||||
|
||||
// Show Result
|
||||
const resDiv = document.getElementById('oxxoResult');
|
||||
resDiv.style.display = 'flex';
|
||||
resDiv.innerHTML = `
|
||||
<div style="text-align:center; width: 100%; animation: fadeIn 0.5s ease-out;">
|
||||
<div style="background: #dcfce7; color: #15803d; width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<h3 style="color:var(--text-main); margin-bottom: 0.5rem;">¡Ficha Generada!</h3>
|
||||
<p style="color:var(--text-muted); margin-bottom: 1.5rem;">Referencia OXXO Pay</p>
|
||||
|
||||
<div style="background: white; padding: 10px; border-radius: 8px; border: 1px solid var(--border); display: inline-block; margin-bottom: 1.5rem;">
|
||||
<p style="font-size:1.4rem; font-weight:bold; letter-spacing: 2px; margin: 0; color: #000;">${d.data.oxxo_reference}</p>
|
||||
</div>
|
||||
|
||||
<img src="${url}" style="max-width:100%; height:auto; display: block; margin: 0 auto 1.5rem; border:1px solid var(--border); border-radius:8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
||||
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<a href="${url}" target="_blank" class="btn btn-primary" style="display: inline-flex; align-items: center; gap: 8px;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
Descargar Ficha
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
} else showToast(d.error, true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Error de conexión', true);
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = original;
|
||||
};
|
||||
<?php $isModuleJs = true; include __DIR__ . '/views/oxxo.php'; ?>
|
||||
|
||||
// --- INPUT VALIDATION & UX ---
|
||||
function setupNumericInput(inputId) {
|
||||
|
||||
0
scripts-uisp/audit_client_passwords.php
Normal file → Executable file
0
scripts-uisp/audit_client_passwords.php
Normal file → Executable file
0
scripts-uisp/debug_clients.php
Normal file → Executable file
0
scripts-uisp/debug_clients.php
Normal file → Executable file
0
scripts-uisp/ejemplo_script_actualizador.php
Normal file → Executable file
0
scripts-uisp/ejemplo_script_actualizador.php
Normal file → Executable file
0
scripts-uisp/generate_invalid_password_list.php
Normal file → Executable file
0
scripts-uisp/generate_invalid_password_list.php
Normal file → Executable file
0
src/.DS_Store
vendored
Normal file → Executable file
0
src/.DS_Store
vendored
Normal file → Executable file
0
src/Data/NotificationData.php
Normal file → Executable file
0
src/Data/NotificationData.php
Normal file → Executable file
0
src/Data/PluginData.php
Normal file → Executable file
0
src/Data/PluginData.php
Normal file → Executable file
0
src/Data/UcrmData.php
Normal file → Executable file
0
src/Data/UcrmData.php
Normal file → Executable file
0
src/Exception/CurlException.php
Normal file → Executable file
0
src/Exception/CurlException.php
Normal file → Executable file
0
src/Facade/AbstractMessageNotifierFacade.php
Normal file → Executable file
0
src/Facade/AbstractMessageNotifierFacade.php
Normal file → Executable file
0
src/Facade/AbstractOxxoOperationsFacade.php
Normal file → Executable file
0
src/Facade/AbstractOxxoOperationsFacade.php
Normal file → Executable file
0
src/Facade/AbstractStripeOperationsFacade.php
Normal file → Executable file
0
src/Facade/AbstractStripeOperationsFacade.php
Normal file → Executable file
0
src/Facade/AbstractUpdateClientFacade.php
Normal file → Executable file
0
src/Facade/AbstractUpdateClientFacade.php
Normal file → Executable file
3
src/Facade/ClientCallBellAPI.php
Normal file → Executable file
3
src/Facade/ClientCallBellAPI.php
Normal file → Executable file
@ -1148,7 +1148,6 @@ class ClientCallBellAPI
|
||||
$resumenClienteJSON = '{' .
|
||||
'"Cliente": "' . $notificationData->clientData['id'] . '",' .
|
||||
'"Nombre": "' . sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']) . '",' .
|
||||
'"URL": "https://sistema.siip.mx/crm/client/' . $notificationData->clientId . '",' .
|
||||
'"Saldo Actual": "' . $saldoTexto . '",' .
|
||||
'"Monto Ultimo Pago": "$ ' . $payments[0]['amount'] . '",' .
|
||||
'"Estado": "' .
|
||||
@ -1156,8 +1155,6 @@ class ClientCallBellAPI
|
||||
'"Fecha Ultimo Pago": " ' . $fecha_ultimoPago_ajustada . ' con ' . $payment_method . '",' .
|
||||
'"Fecha Ultima Actualizacion": "' . $fecha_actual_ajustada . '",' .
|
||||
'"Clabe Interbancaria": "' . $clabeInterbancaria . '",' .
|
||||
'"Site": "' . $site . '",' .
|
||||
'"Antena/Sectorial": "' . $antenaSectorial . '",' .
|
||||
'"Password Antena": ' . (empty($passAntenaFinal) ? '""' : $passAntenaFinal) .
|
||||
'}';
|
||||
|
||||
|
||||
0
src/Facade/PluginNotifierFacade.php
Normal file → Executable file
0
src/Facade/PluginNotifierFacade.php
Normal file → Executable file
0
src/Facade/PluginOxxoNotifierFacade.php
Normal file → Executable file
0
src/Facade/PluginOxxoNotifierFacade.php
Normal file → Executable file
0
src/Facade/TwilioNotifierFacade.php
Normal file → Executable file
0
src/Facade/TwilioNotifierFacade.php
Normal file → Executable file
0
src/Facade/pruebas_ucrm_api.php
Normal file → Executable file
0
src/Facade/pruebas_ucrm_api.php
Normal file → Executable file
0
src/Factory/MessageTextFactory.php
Normal file → Executable file
0
src/Factory/MessageTextFactory.php
Normal file → Executable file
0
src/Factory/NotificationDataFactory.php
Normal file → Executable file
0
src/Factory/NotificationDataFactory.php
Normal file → Executable file
0
src/Plugin.php
Normal file → Executable file
0
src/Plugin.php
Normal file → Executable file
0
src/Service/CurlExecutor.php
Normal file → Executable file
0
src/Service/CurlExecutor.php
Normal file → Executable file
0
src/Service/LogCleaner.php
Normal file → Executable file
0
src/Service/LogCleaner.php
Normal file → Executable file
0
src/Service/Logger.php
Normal file → Executable file
0
src/Service/Logger.php
Normal file → Executable file
0
src/Service/MinioStorageService.php
Normal file → Executable file
0
src/Service/MinioStorageService.php
Normal file → Executable file
0
src/Service/OptionsManager.php
Normal file → Executable file
0
src/Service/OptionsManager.php
Normal file → Executable file
0
src/Service/PaymentIntentService.php
Normal file → Executable file
0
src/Service/PaymentIntentService.php
Normal file → Executable file
0
src/Service/PluginDataValidator.php
Normal file → Executable file
0
src/Service/PluginDataValidator.php
Normal file → Executable file
0
src/Service/SmsNumberProvider.php
Normal file → Executable file
0
src/Service/SmsNumberProvider.php
Normal file → Executable file
0
src/Service/UcrmApi.php
Normal file → Executable file
0
src/Service/UcrmApi.php
Normal file → Executable file
0
test_facade_crash.php
Normal file → Executable file
0
test_facade_crash.php
Normal file → Executable file
208
views/oxxo.php
Executable file
208
views/oxxo.php
Executable file
@ -0,0 +1,208 @@
|
||||
<?php if (!isset($isModuleJs)): ?>
|
||||
|
||||
<!-- MODULE 4: OXXO -->
|
||||
<section id="section-pagos-oxxo" class="section-view">
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<h2 style="margin: 0; display: flex; align-items: center; gap: 10px;">
|
||||
<img src="?action=get_image&name=oxxo-payments.png" style="width:64px;height:64px;"> Pagos OXXO
|
||||
</h2>
|
||||
<p style="color: var(--text-muted); margin: 5px 0 0 0;">🏪 Genera fichas de pago OXXO para que tus clientes paguen en tiendas de conveniencia</p>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG CONTAINER -->
|
||||
<div class="config-container">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label>Buscar Cliente para OXXO</label>
|
||||
<div class="search-wrapper">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<input type="text" id="oxxoSearch" class="form-control search-input-padded" placeholder="Buscar cliente..." autocomplete="off">
|
||||
</div>
|
||||
<div id="oxxoResults" class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PLACEHOLDER STATE (Initially Visible) -->
|
||||
<div id="oxxoModulePlaceholder" class="placeholder-state">
|
||||
<span class="placeholder-icon">🏪</span>
|
||||
<p>Selecciona un cliente para generar una ficha OXXO Pay</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="oxxoDetailContainer" style="display: none; margin-top: 2rem;">
|
||||
<div class="stripe-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start;">
|
||||
<!-- Left Column: Form & Client Info -->
|
||||
<div class="card" style="height: 100%; display: flex; flex-direction: column;">
|
||||
<!-- Client Header -->
|
||||
<div style="border-bottom: 1px solid var(--border); padding-bottom: 1rem; margin-bottom: 1.5rem;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 0.5rem;">
|
||||
<img src="?action=image&file=client.webp" class="client-header-icon" style="width: 32px; height: 32px; border-radius: 50%;">
|
||||
<h3 style="margin: 0; font-size: 1.1rem; color: var(--text-main);"><span id="oxxoClientName">Nombre del Cliente</span></h3>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<p id="oxxoClientIdDisplay" style="color: var(--text-muted); margin: 0; font-size: 0.9rem;">ID: #0</p>
|
||||
<span class="badge" id="oxxoBalanceBadge" style="background: #fee2e2; color: #ef4444; font-size: 0.8rem; padding: 2px 8px; border-radius: 4px;">Saldo: $0.00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OXXO Form -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
||||
<div style="text-align: center; margin-bottom: 1.5rem;">
|
||||
<img src="?action=image&file=oxxo-logo.png" style="max-width: 100px; height: auto;">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label style="font-weight: 500; margin-bottom: 0.5rem; display: block; color: var(--text-main);">Monto a Cobrar</label>
|
||||
<div class="input-group-unified">
|
||||
<span class="input-prefix">$</span>
|
||||
<input type="number" id="oxxoAmount" class="form-control" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-row" style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<button id="btnCreateOxxoIntent" class="btn btn-primary" style="width: 100%; justify-content: center; background-color: #E20613; border-color: #E20613; color: white; display: flex; align-items: center; gap: 10px; padding: 12px; border-radius: 8px; font-weight: 600; font-size: 1rem; transition: all 0.2s;">
|
||||
Generar Ficha OXXO Pay
|
||||
</button>
|
||||
<a id="btnOxxoCrm" href="#" target="_blank" class="btn btn-uniform" style="text-align: center; display: flex; justify-content: center; width: 100%; padding: 12px; border-radius: 8px; font-size: 1rem;">
|
||||
<img src="?action=image&file=crm.webp" class="icon-crm" style="vertical-align: middle; margin-right: 5px; width: 16px;"> Ver en CRM
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Ficha Preview -->
|
||||
<div class="card" id="oxxoFichaContainer" style="height: 100%; min-height: 450px; display: flex; align-items: center; justify-content: center; background: var(--bg-body); border: 1px dashed var(--border); position: relative;">
|
||||
<div id="oxxoPlaceholder" style="text-align: center; color: var(--text-muted);">
|
||||
<img src="?action=image&file=oxxo-payments.png" style="max-width: 80px; opacity: 0.5; margin-bottom: 1rem; filter: grayscale(1);">
|
||||
<h4 style="margin: 0; font-weight: 500;">Vista Previa</h4>
|
||||
<p style="margin: 5px 0 0 0; font-size: 0.9rem;">La ficha generada aparecerá aquí</p>
|
||||
</div>
|
||||
<div id="oxxoResult" style="display:none; width: 100%; height: 100%;">
|
||||
<!-- Generated Ficha will be injected here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- OXXO HISTORY CONTAINER -->
|
||||
<div id="oxxoHistoryContainer" style="display: none; margin-top: 2rem;">
|
||||
<div class="card">
|
||||
<h3 class="section-title" style="margin-bottom: 1rem;">Historial de Fichas OXXO (Últimas 5)</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="oxxoHistoryTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID Pago</th>
|
||||
<th>Fecha</th>
|
||||
<th>Monto</th>
|
||||
<th>Ref. OXXO</th>
|
||||
<th>Estatus</th>
|
||||
<th>Ficha</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="6" style="text-align:center">Seleccione un cliente...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 3. OXXO SEARCH
|
||||
let selectedOxxoClient = null;
|
||||
setupSearch('oxxoSearch', 'oxxoResults', 'search_stripe', async (partialClient) => {
|
||||
const res = await fetch(`${window.SIIP_OXXO_PATH || ''}?action=get_stripe_details&id=${partialClient.id}`);
|
||||
const data = await res.json();
|
||||
selectedOxxoClient = data;
|
||||
|
||||
document.getElementById('oxxoClientName').textContent = data.fullName;
|
||||
document.getElementById('oxxoClientIdDisplay').textContent = `ID: #${data.id}`;
|
||||
document.getElementById('oxxoBalanceBadge').textContent = `Saldo: $${parseFloat(data.accountOutstanding||0).toFixed(2)}`;
|
||||
document.getElementById('oxxoAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
||||
document.getElementById('btnOxxoCrm').href = `${store.publicUrl}/client/${data.id}`;
|
||||
|
||||
// Reset View
|
||||
document.getElementById('oxxoPlaceholder').style.display = 'block'; // This is the PREVIEW placeholder (keep as is)
|
||||
document.getElementById('oxxoModulePlaceholder').style.display = 'none'; // Hide MODULE placeholder
|
||||
document.getElementById('oxxoResult').style.display = 'none';
|
||||
document.getElementById('oxxoResult').innerHTML = '';
|
||||
|
||||
document.getElementById('oxxoDetailContainer').style.display = 'block';
|
||||
|
||||
// Load History
|
||||
if (data.stripeCustomerId) {
|
||||
document.getElementById('oxxoHistoryContainer').style.display = 'block';
|
||||
loadOxxoHistory(data.stripeCustomerId);
|
||||
} else {
|
||||
document.getElementById('oxxoHistoryContainer').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnCreateOxxoIntent').onclick = async () => {
|
||||
if (!selectedOxxoClient) return;
|
||||
const amt = parseFloat(document.getElementById('oxxoAmount').value);
|
||||
if (!amt || amt < 10) return showToast('Mínimo 10 MXN', true);
|
||||
|
||||
const btn = document.getElementById('btnCreateOxxoIntent');
|
||||
const original = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner" style="border: 2px solid #fff; border-top: 2px solid transparent; width: 16px; height: 16px; border-radius: 50%; display: inline-block; animation: spin 1s linear infinite; margin-right: 8px;"></span> Procesando...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'create_oxxo_intent');
|
||||
fd.append('clientId', selectedOxxoClient.id);
|
||||
fd.append('amount', amt);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${window.SIIP_OXXO_PATH || ''}?`, {
|
||||
method: 'POST',
|
||||
body: fd
|
||||
});
|
||||
const d = await res.json();
|
||||
if (d.success) {
|
||||
const url = d.data.voucher_image_url || `?action=image&file=${d.data.voucher_filename}`;
|
||||
|
||||
// Hide Placeholder
|
||||
document.getElementById('oxxoPlaceholder').style.display = 'none';
|
||||
|
||||
// Show Result
|
||||
const resDiv = document.getElementById('oxxoResult');
|
||||
resDiv.style.display = 'flex';
|
||||
resDiv.innerHTML = `
|
||||
<div style="text-align:center; width: 100%; animation: fadeIn 0.5s ease-out;">
|
||||
<div style="background: #dcfce7; color: #15803d; width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</div>
|
||||
<h3 style="color:var(--text-main); margin-bottom: 0.5rem;">¡Ficha Generada!</h3>
|
||||
<p style="color:var(--text-muted); margin-bottom: 1.5rem;">Referencia OXXO Pay</p>
|
||||
|
||||
<div style="background: white; padding: 10px; border-radius: 8px; border: 1px solid var(--border); display: inline-block; margin-bottom: 1.5rem;">
|
||||
<p style="font-size:1.4rem; font-weight:bold; letter-spacing: 2px; margin: 0; color: #000;">${d.data.oxxo_reference}</p>
|
||||
</div>
|
||||
|
||||
<img src="${url}" style="max-width:100%; height:auto; display: block; margin: 0 auto 1.5rem; border:1px solid var(--border); border-radius:8px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
||||
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<a href="${url}" target="_blank" class="btn btn-primary" style="display: inline-flex; align-items: center; gap: 8px;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
Descargar Ficha
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
} else showToast(d.error, true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Error de conexión', true);
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = original;
|
||||
};
|
||||
});
|
||||
<?php endif; ?>
|
||||
243
views/stripe.php
Executable file
243
views/stripe.php
Executable file
@ -0,0 +1,243 @@
|
||||
<?php if (!isset($isModuleJs)): ?>
|
||||
<!-- MODULE 3: STRIPE -->
|
||||
<section id="section-pagos-spei" class="section-view">
|
||||
<div class="card">
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<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
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG CONTAINER -->
|
||||
<div class="config-container">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label>Buscar Cliente para Stripe</label>
|
||||
<div class="search-wrapper">
|
||||
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
<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="stripeDetailContainer" class="card" style="display: none;">
|
||||
<!-- HEADER: CLIENT INFO & BALANCE -->
|
||||
<div class="client-header-grid">
|
||||
<div class="client-info">
|
||||
<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>
|
||||
</div>
|
||||
<div class="client-balance">
|
||||
<span class="label">Saldo Actual</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 class="form-group">
|
||||
<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>";
|
||||
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>
|
||||
</svg>
|
||||
Generar Referencia SPEI
|
||||
</button>
|
||||
</div>
|
||||
</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 Pagos (Últimos 10)</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: ?>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 2. STRIPE SEARCH
|
||||
let selectedStripeClient = null;
|
||||
setupSearch('stripeSearch', 'stripeResults', 'search_stripe', async (partialClient) => {
|
||||
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_stripe_details&id=${partialClient.id}`);
|
||||
const data = await res.json();
|
||||
selectedStripeClient = data;
|
||||
|
||||
document.getElementById('stripeClientName').textContent = data.fullName;
|
||||
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';
|
||||
document.getElementById('stripeClabeDisplay').textContent = data.clabeInterbancaria || 'No disponible';
|
||||
document.getElementById('stripeAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
|
||||
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
|
||||
document.getElementById('stripeDetailContainer').style.display = 'block';
|
||||
document.getElementById('stripePlaceholder').style.display = 'none'; // Hide placeholder
|
||||
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();
|
||||
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';
|
||||
};
|
||||
|
||||
function showStripeResult(data) {
|
||||
const c = document.getElementById('stripeResultContent');
|
||||
c.innerHTML = `<h3 style="color:var(--success);text-align:center">¡Referencia Creada!</h3><p style="text-align:center">Monto: $${data.amount}</p>`;
|
||||
if (data.next_action?.display_bank_transfer_instructions) {
|
||||
const i = data.next_action.display_bank_transfer_instructions.financial_addresses[0].spei;
|
||||
c.innerHTML += `<div style="background:#f1f5f9;padding:15px;border-radius:8px;margin-top:10px"><p><strong>Banco:</strong> ${i.bank_name}</p><p><strong>CLABE:</strong> ${i.clabe}</p></div>`;
|
||||
}
|
||||
document.getElementById('stripeResultModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
async function loadOxxoHistory(stripeCustomerId) {
|
||||
const tbody = document.querySelector('#oxxoHistoryTable tbody');
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center">Cargando historial...</td></tr>';
|
||||
try {
|
||||
const res = await fetch(`${window.SIIP_STRIPE_PATH || ''}?action=get_oxxo_history&stripeCustomerId=${stripeCustomerId}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--danger)">Error: ${data.error}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.history || data.history.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center">No hay fichas recientes</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.history.map(p => {
|
||||
let statusBadge = '';
|
||||
switch (p.status) {
|
||||
case 'succeeded':
|
||||
statusBadge = '<span class="badge badge-success">Pagado</span>';
|
||||
break;
|
||||
case 'requires_action':
|
||||
statusBadge = '<span class="badge badge-warning">Pendiente</span>';
|
||||
break;
|
||||
case 'canceled':
|
||||
statusBadge = '<span class="badge badge-danger">Cancelado</span>';
|
||||
break;
|
||||
default:
|
||||
statusBadge = `<span class="badge badge-secondary">${p.status}</span>`;
|
||||
}
|
||||
|
||||
const date = new Date(p.created * 1000).toLocaleString();
|
||||
const voucherBtn = p.voucherUrl ?
|
||||
`<a href="${p.voucherUrl}" target="_blank" class="btn btn-uniform" style="padding: 4px 12px; font-size: 0.8rem;">Ver Ficha</a>` :
|
||||
'-';
|
||||
|
||||
return `<tr>
|
||||
<td><small style="font-family:monospace">${p.id.slice(-8)}</small></td>
|
||||
<td>${date}</td>
|
||||
<td>$${p.amount.toFixed(2)} ${p.currency}</td>
|
||||
<td style="font-family:monospace">${p.oxxoNumber}</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>${voucherBtn}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:var(--danger)">Error de conexión</td></tr>';
|
||||
}
|
||||
}
|
||||
});
|
||||
<?php endif; ?>
|
||||
Loading…
Reference in New Issue
Block a user