saldo stripe conseguido

This commit is contained in:
DANYDHSV 2026-02-12 08:13:26 -06:00
parent 28b4324406
commit aa42284cc5
7 changed files with 3541 additions and 36 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
img/webp/delete.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
img/webp/edit.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -157,6 +157,30 @@ if (isset($_GET['action'])) {
exit;
}
if ($_GET['action'] === 'get_stripe_history') {
if (ob_get_level()) ob_end_clean();
header('Content-Type: application/json');
$stripeCustomerId = $_GET['stripeCustomerId'] ?? '';
if (!$stripeCustomerId) {
echo json_encode(['error' => 'Missing stripeCustomerId']);
exit;
}
try {
$history = $stripeService->getLastPayments($stripeCustomerId);
$balance = $stripeService->getCustomerCashBalance($stripeCustomerId);
echo json_encode([
'history' => $history,
'cashBalance' => $balance
]);
} catch (Exception $e) {
echo json_encode(['error' => $e->getMessage()]);
}
exit;
}
if ($_GET['action'] === 'search_stripe') {
$q = $_GET['q'] ?? '';
echo json_encode($stripeService->searchClients($q));
@ -172,7 +196,7 @@ if (isset($_GET['action'])) {
// Image Handler
if (ob_get_level()) ob_end_clean();
$filename = basename($_GET['file'] ?? $_GET['name'] ?? '');
$paths = [__DIR__ . '/img/' . $filename, __DIR__ . '/vouchers_oxxo/' . $filename];
$paths = [__DIR__ . '/img/' . $filename, __DIR__ . '/img/webp/' . $filename, __DIR__ . '/vouchers_oxxo/' . $filename];
$finalPath = null;
foreach ($paths as $p) {
if (file_exists($p)) {
@ -622,6 +646,58 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
z-index: 4000;
}
/* UNIFORM BUTTONS */
.btn-uniform {
background-color: var(--primary) !important;
color: white !important;
border: none;
border-radius: 8px;
padding: 8px 16px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
text-decoration: none;
}
.btn-uniform:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* ICONS */
.icon-btn {
width: 20px;
height: 20px;
object-fit: contain;
}
.icon-crm {
width: 18px;
height: 18px;
object-fit: contain;
}
.icon-action {
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s;
}
.icon-action:hover {
transform: scale(1.2);
}
.client-header-icon {
width: 32px;
height: 32px;
object-fit: contain;
margin-right: 10px;
vertical-align: middle;
}
#toast.show {
transform: translate(-50%, 0);
}
@ -894,9 +970,13 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<div id="paymentsContainer" class="card" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 10px;">
<h3 id="selectedClientName" style="margin: 0;">Pagos del Cliente</h3>
<h3 style="margin: 0; display: flex; align-items: center;">
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="selectedClientName">Pagos del Cliente</span>
</h3>
<div style="display: flex; gap: 8px;">
<a id="btnNotifCrm" href="#" target="_blank" class="btn btn-secondary" style="height: 38px;">Ver en CRM</a>
<a id="btnNotifCrm" href="#" target="_blank" class="btn btn-uniform" style="height: 38px;">
<img src="?action=image&file=crm.webp" class="icon-crm"> Ver en CRM
</a>
<button class="btn btn-secondary" onclick="refreshClientPayments()" style="height: 38px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M23 4v6h-6" />
@ -948,8 +1028,12 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<!-- HEADER: CLIENT INFO & BALANCE -->
<div class="client-header-grid">
<div class="client-info">
<h3 id="stripeClientName">Nombre del Cliente</h3>
<p id="stripeClientIdDisplay" style="color: var(--text-muted);">ID: #0</p>
<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>
@ -993,12 +1077,8 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
</div>
<div class="actions-row">
<a id="btnVerEnCrm" href="#" target="_blank" class="btn btn-secondary btn-lg">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
<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">
@ -1011,6 +1091,31 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
</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" style="display:none; align-items: center; background-color: #f1f5f9; color: var(--text-main); padding: 5px 12px; border-radius: 8px; font-size: 0.9em; font-weight: 600; border: 1px solid transparent;">
<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 -->
@ -1036,12 +1141,17 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<div id="oxxoDetailContainer" class="card" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem;">
<div>
<h3 id="oxxoClientName">Nombre</h3>
<div>
<h3>
<img src="?action=image&file=client.webp" class="client-header-icon"> <span id="oxxoClientName">Nombre</span>
</h3>
<p id="oxxoClientIdDisplay" style="color: var(--text-muted);">ID: #0</p>
</div>
<div style="text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 8px;">
<span class="badge" id="oxxoBalanceBadge" style="background: #fee2e2; color: #ef4444;">Saldo: $0.00</span>
<a id="btnOxxoCrm" href="#" target="_blank" class="btn btn-secondary" style="padding: 5px 12px; font-size: 0.8rem;">Ver en CRM</a>
<a id="btnOxxoCrm" href="#" target="_blank" class="btn btn-uniform" style="padding: 5px 12px; font-size: 0.8rem;">
<img src="?action=image&file=crm.webp" class="icon-crm"> Ver en CRM
</a>
</div>
</div>
@ -1252,6 +1362,8 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<div class="status-dot ${isSuspended ? 'suspended' : 'active'}"></div>
<div class="client-info">
<div class="client-header">
<div class="client-header">
<img src="?action=image&file=client.webp" style="width:24px;height:24px;margin-right:5px;vertical-align:middle;">
<span class="client-name">${highlightMatch(name, query)}</span>
${isSuspended ? '<span class="badge-suspended">SUSPENDIDO</span>' : ''}
</div>
@ -1339,7 +1451,12 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
<td>${new Date(p.createdDate).toLocaleString()}</td>
<td>$${p.amount} ${p.currencyCode}</td>
<td>${p.methodName}</td>
<td><button class="btn btn-whatsapp" onclick="resendPayment(${p.id})">Re-enviar</button></td>
<td>${p.methodName}</td>
<td>
<button class="btn btn-whatsapp" onclick="resendPayment(${p.id})">
<img src="?action=image&file=whatsapp-logo-button.png" class="icon-btn" style="margin-right:5px;filter: brightness(0) invert(1);"> Re-enviar Notificación
</button>
</td>
</tr>
`).join('');
}
@ -1381,6 +1498,8 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
document.getElementById('stripeAmount').value = data.accountOutstanding > 0 ? data.accountOutstanding : '';
document.getElementById('btnVerEnCrm').href = `${store.publicUrl}/client/${data.id}`;
document.getElementById('stripeDetailContainer').style.display = 'block';
if (data.stripeCustomerId) loadStripeHistory(data.stripeCustomerId);
else document.getElementById('stripeHistoryContainer').style.display = 'none';
});
document.getElementById('btnCreateIntent').onclick = async () => {
@ -1476,7 +1595,15 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
// --- INSTALLER MODAL LOGIC ---
function renderTable() {
const tbody = document.querySelector('#installersTable tbody');
tbody.innerHTML = store.installers.map((inst, i) => `<tr><td>#${inst.id}</td><td>${inst.nombre}</td><td>${inst.whatsapp}</td><td><button class="btn btn-secondary" onclick="editInstaller(${i})">✎</button> <button class="btn btn-secondary" style="color:red" onclick="deleteInstaller(${i})">🗑</button></td></tr>`).join('');
tbody.innerHTML = store.installers.map((inst, i) => `<tr>
<td>#${inst.id}</td>
<td>${inst.nombre}</td>
<td>${inst.whatsapp}</td>
<td>
<img src="?action=image&file=edit.webp" class="icon-action" onclick="editInstaller(${i})" title="Editar">
<img src="?action=image&file=delete.webp" class="icon-action" style="margin-left:10px" onclick="deleteInstaller(${i})" title="Borrar">
</td>
</tr>`).join('');
}
renderTable();
@ -1519,6 +1646,93 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
renderTable();
document.getElementById('modalOverlay').style.display = 'none';
};
async function loadStripeHistory(customerId) {
const container = document.getElementById('stripeHistoryContainer');
const tbody = document.querySelector('#stripeHistoryTable tbody');
const balanceBadge = document.getElementById('stripeCashBalanceBadge');
container.style.display = 'block';
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; padding: 20px; color: var(--text-muted);">Cargando historial...</td></tr>';
balanceBadge.style.display = 'none';
try {
const response = await fetch(`?action=get_stripe_history&stripeCustomerId=${customerId}`);
const dataRaw = await response.json();
// Compatibility handling (array vs object)
const data = Array.isArray(dataRaw) ? dataRaw : (dataRaw.history || []);
const cashBalance = (dataRaw.cashBalance !== undefined && dataRaw.cashBalance !== null) ? parseFloat(dataRaw.cashBalance) : null;
// Display Cash Balance
if (cashBalance !== null) {
balanceBadge.style.display = 'inline-flex';
const balanceText = document.getElementById('stripeCashBalanceText');
if (balanceText) {
balanceText.textContent = 'Saldo Stripe: $' + cashBalance.toFixed(2) + ' MXN';
}
if (cashBalance > 0) {
balanceBadge.style.backgroundColor = '#dcfce7'; // Green
balanceBadge.style.color = '#15803d';
balanceBadge.style.border = '1px solid #bbf7d0';
} else {
balanceBadge.style.backgroundColor = '#f1f5f9'; // Gray
balanceBadge.style.color = 'var(--text-main)';
balanceBadge.style.border = '1px solid transparent';
}
}
if (dataRaw.error) {
tbody.innerHTML = `<tr><td colspan="5" style="color:var(--danger); text-align:center; padding: 10px;">Error: ${dataRaw.error}</td></tr>`;
return;
}
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; padding: 20px; color: var(--text-muted);">No hay pagos recientes.</td></tr>';
return;
}
tbody.innerHTML = data.map(payment => {
const date = new Date(payment.created * 1000).toLocaleString();
const statusBadge = getStatusBadge(payment.status);
return `
<tr style="border-bottom: 1px solid var(--border);">
<td style="padding: 10px;">${statusBadge}</td>
<td style="padding: 10px; font-weight: 500;">$${payment.amount.toFixed(2)} ${payment.currency}</td>
<td style="padding: 10px; color: var(--text-muted);">${payment.description}</td>
<td style="padding: 10px; color: var(--text-muted); font-size: 0.9em;">${date}</td>
<td style="padding: 10px;"><small style="color: var(--text-muted); font-family: monospace;">${payment.id}</small></td>
</tr>
`;
}).join('');
} catch (e) {
tbody.innerHTML = `<tr><td colspan="5" style="color:var(--danger); text-align:center; padding: 10px;">Error de conexión</td></tr>`;
}
}
function getStatusBadge(status) {
let color = 'var(--text-muted)';
let bg = '#f1f5f9';
let label = status;
if (status === 'succeeded') {
color = '#15803d';
bg = '#dcfce7';
label = 'Exitoso';
} else if (status === 'requires_payment_method' || status === 'requires_action') {
color = '#b45309';
bg = '#fef3c7';
label = 'Pendiente';
} else if (status === 'canceled') {
color = '#b91c1c';
bg = '#fee2e2';
label = 'Cancelado';
}
return `<span style="background-color: ${bg}; color: ${color}; padding: 4px 10px; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; text-transform: capitalize;">${label}</span>`;
}
</script>
</body>

View File

@ -109,6 +109,9 @@ class Plugin
}
$userInput = file_get_contents('php://input');
// DEBUG TEMPORAL
$this->logger->debug('Payload recibido: ' . $userInput . PHP_EOL);
if (! $userInput) {

View File

@ -141,6 +141,56 @@ class PaymentIntentService
}
}
public function getLastPayments($stripeCustomerId, $limit = 10)
{
try {
$collection = $this->stripeClient->paymentIntents->all([
'customer' => $stripeCustomerId,
'limit' => $limit,
]);
$result = [];
foreach ($collection->data as $payment) {
$description = $payment->description ?? $payment->metadata['description'] ?? 'Pago Stripe';
$result[] = [
'id' => $payment->id,
'amount' => $payment->amount / 100,
'currency' => strtoupper($payment->currency),
'status' => $payment->status,
'created' => $payment->created,
'description' => $description
];
}
return $result;
} catch (\Exception $e) {
$this->log("Error fetching last payments: " . $e->getMessage());
return ['error' => $e->getMessage()];
}
}
public function getCustomerCashBalance($stripeCustomerId)
{
try {
$balanceObj = $this->stripeClient->customers->retrieveCashBalance(
$stripeCustomerId,
[]
);
$amount = $balanceObj->available['mxn'] ?? 0;
return $amount / 100;
} catch (\Exception $e) {
// Fallback or old api version check
try {
$customer = $this->stripeClient->customers->retrieve(
$stripeCustomerId,
['expand' => ['cash_balance']]
);
return ($customer->cash_balance->available['mxn'] ?? 0) / 100;
} catch (\Exception $ex) {
return 0;
}
}
}
private function log($message)
{
if ($this->logger) {