saldo stripe conseguido
This commit is contained in:
parent
28b4324406
commit
aa42284cc5
3238
data/plugin.log
3238
data/plugin.log
File diff suppressed because it is too large
Load Diff
BIN
img/webp/account-balance.webp
Normal file
BIN
img/webp/account-balance.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
img/webp/delete.webp
Executable file
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
BIN
img/webp/edit.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
244
public.php
244
public.php
@ -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>
|
||||
|
||||
|
||||
@ -109,6 +109,9 @@ class Plugin
|
||||
}
|
||||
|
||||
$userInput = file_get_contents('php://input');
|
||||
|
||||
// DEBUG TEMPORAL
|
||||
|
||||
$this->logger->debug('Payload recibido: ' . $userInput . PHP_EOL);
|
||||
|
||||
if (! $userInput) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user