yogiboook_new/public/userarea/admin_subscription_plans.php
2026-01-23 08:13:41 +01:00

557 lines
27 KiB
PHP

<?php
// admin_subscription_plans.php
// Show errors (remove in production)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
// DB connection
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Check login
if (!isset($iduserlogin)) {
die("Errore: ID utente non definito.");
}
/**
* Check if the current user is admin.
* IMPORTANT: set your real admin role_id(s) here.
*/
function isAdmin(PDO $pdo, int $userId): bool
{
// TODO: adjust these role ids according to your system
$adminRoleIds = [1]; // e.g. 1 = superadmin
$stmt = $pdo->prepare("SELECT role_id FROM auth_users WHERE id = ?");
$stmt->execute([$userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return false;
return in_array((int)$row['role_id'], $adminRoleIds, true);
}
if (!isAdmin($pdo, (int)$iduserlogin)) {
die("Accesso negato: pagina riservata all'amministratore.");
}
function formatMoneyFromCents(int $cents, string $currency): string
{
$amount = number_format($cents / 100, 2, ',', '.');
return $amount . ' ' . strtoupper($currency);
}
// Handle POST actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Common fields
$code = trim($_POST['code'] ?? '');
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
$stripe_product_id = trim($_POST['stripe_product_id'] ?? '');
$stripe_price_id = trim($_POST['stripe_price_id'] ?? '');
$currency = strtoupper(trim($_POST['currency'] ?? 'EUR'));
$unit_amount = (int)($_POST['unit_amount'] ?? 0); // cents
$interval = in_array(($_POST['interval'] ?? ''), ['day', 'week', 'month', 'year'], true) ? $_POST['interval'] : 'month';
$interval_count = max(1, (int)($_POST['interval_count'] ?? 1));
$trial_days = max(0, (int)($_POST['trial_days'] ?? 0));
$is_active = isset($_POST['is_active']) ? 1 : 0;
// ADD
if ($action === 'add_plan') {
if ($code === '' || $name === '') {
$error = "Code e Nome sono obbligatori.";
} elseif ($stripe_price_id === '') {
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
} elseif (strlen($currency) !== 3) {
$error = "Currency deve essere nel formato ISO (es. EUR).";
} elseif ($unit_amount < 0) {
$error = "Unit amount non può essere negativo.";
} else {
try {
$stmt = $pdo->prepare("
INSERT INTO billing_plans
(code, name, description, stripe_product_id, stripe_price_id, currency, unit_amount, `interval`, interval_count, trial_days, is_active, created_at, updated_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
");
$stmt->execute([
$code,
$name,
$description ?: null,
$stripe_product_id ?: null,
$stripe_price_id,
$currency,
$unit_amount,
$interval,
$interval_count,
$trial_days,
$is_active
]);
$success_message = "Piano creato con successo.";
} catch (PDOException $e) {
$error = "Errore durante la creazione del piano: " . $e->getMessage();
}
}
}
// EDIT
if ($action === 'edit_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} elseif ($code === '' || $name === '') {
$error = "Code e Nome sono obbligatori.";
} elseif ($stripe_price_id === '') {
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
} elseif (strlen($currency) !== 3) {
$error = "Currency deve essere nel formato ISO (es. EUR).";
} elseif ($unit_amount < 0) {
$error = "Unit amount non può essere negativo.";
} else {
try {
$stmt = $pdo->prepare("
UPDATE billing_plans
SET
code = ?,
name = ?,
description = ?,
stripe_product_id = ?,
stripe_price_id = ?,
currency = ?,
unit_amount = ?,
`interval` = ?,
interval_count = ?,
trial_days = ?,
is_active = ?,
updated_at = NOW()
WHERE id = ?
");
$stmt->execute([
$code,
$name,
$description ?: null,
$stripe_product_id ?: null,
$stripe_price_id,
$currency,
$unit_amount,
$interval,
$interval_count,
$trial_days,
$is_active,
$id
]);
$success_message = "Piano aggiornato con successo.";
} catch (PDOException $e) {
$error = "Errore durante l'aggiornamento del piano: " . $e->getMessage();
}
}
}
// DISABLE
if ($action === 'disable_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} else {
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 0, updated_at = NOW() WHERE id = ?");
$stmt->execute([$id]);
$success_message = "Piano disattivato.";
}
}
// ENABLE
if ($action === 'enable_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} else {
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 1, updated_at = NOW() WHERE id = ?");
$stmt->execute([$id]);
$success_message = "Piano riattivato.";
}
}
}
// Fetch plans
$stmt = $pdo->prepare("SELECT * FROM billing_plans ORDER BY is_active DESC, name ASC");
$stmt->execute();
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<?php include('siteinfo.php'); ?>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10 mb-4">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h5 class="mb-1">Subscription Plans (Admin)</h5>
<p class="mb-0 text-muted">Gestione piani abbonamento delle scuole</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPlanModal">
<i class="bx bx-plus"></i> Nuovo Piano
</button>
</div>
</div>
</div>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if (isset($success_message)): ?>
<div class="alert alert-success" role="alert"><?php echo htmlspecialchars($success_message); ?></div>
<?php endif; ?>
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Elenco Piani</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="plansTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Code</th>
<th>Nome</th>
<th>Prezzo</th>
<th>Intervallo</th>
<th>Trial</th>
<th>Stripe Product</th>
<th>Stripe Price *</th>
<th>Stato</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($plans as $p): ?>
<tr>
<td><span class="badge bg-dark"><?php echo htmlspecialchars($p['code']); ?></span></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
<?php if (!empty($p['description'])): ?>
<div class="text-muted small"><?php echo nl2br(htmlspecialchars($p['description'])); ?></div>
<?php endif; ?>
</td>
<td><?php echo formatMoneyFromCents((int)$p['unit_amount'], $p['currency']); ?></td>
<td><?php echo (int)$p['interval_count'] . ' / ' . htmlspecialchars($p['interval']); ?></td>
<td><?php echo (int)$p['trial_days'] > 0 ? ((int)$p['trial_days'] . ' gg') : '—'; ?></td>
<td>
<?php if (!empty($p['stripe_product_id'])): ?>
<span class="badge bg-secondary"><?php echo htmlspecialchars($p['stripe_product_id']); ?></span>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td>
<span class="badge bg-info"><?php echo htmlspecialchars($p['stripe_price_id']); ?></span>
</td>
<td>
<span class="badge <?php echo ((int)$p['is_active'] === 1) ? 'bg-success' : 'bg-danger'; ?>">
<?php echo ((int)$p['is_active'] === 1) ? 'Attivo' : 'Disattivo'; ?>
</span>
</td>
<td class="text-nowrap">
<button class="btn btn-sm btn-warning"
data-bs-toggle="modal"
data-bs-target="#editPlanModal"
onclick='fillEditPlanModal(<?php echo json_encode([
"id" => (int)$p["id"],
"code" => $p["code"],
"name" => $p["name"],
"description" => $p["description"],
"stripe_product_id" => $p["stripe_product_id"],
"stripe_price_id" => $p["stripe_price_id"],
"currency" => $p["currency"],
"unit_amount" => (int)$p["unit_amount"],
"interval" => $p["interval"],
"interval_count" => (int)$p["interval_count"],
"trial_days" => (int)$p["trial_days"],
"is_active" => (int)$p["is_active"],
]); ?>)'>
<i class="bx bx-edit"></i>
</button>
<?php if ((int)$p['is_active'] === 1): ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Disattivare questo piano?');">
<input type="hidden" name="action" value="disable_plan">
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
<button class="btn btn-sm btn-danger" type="submit" title="Disattiva">
<i class="bx bx-power-off"></i>
</button>
</form>
<?php else: ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Riattivare questo piano?');">
<input type="hidden" name="action" value="enable_plan">
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
<button class="btn btn-sm btn-success" type="submit" title="Riattiva">
<i class="bx bx-check-circle"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="mt-3 text-muted small">
<strong>Nota:</strong> `unit_amount` è in centesimi (es. 2990 = 29,90 EUR)
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
$(document).ready(function() {
$('#plansTable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.10.25/i18n/Italian.json"
},
"pageLength": 10,
"lengthMenu": [10, 25, 50, 100],
"order": [
[1, "asc"]
]
});
});
function fillEditPlanModal(p) {
document.getElementById('edit_id').value = p.id;
document.getElementById('edit_code').value = p.code || '';
document.getElementById('edit_name').value = p.name || '';
document.getElementById('edit_description').value = p.description || '';
document.getElementById('edit_stripe_product_id').value = p.stripe_product_id || '';
document.getElementById('edit_stripe_price_id').value = p.stripe_price_id || '';
document.getElementById('edit_currency').value = p.currency || 'EUR';
document.getElementById('edit_unit_amount').value = (p.unit_amount ?? 0);
document.getElementById('edit_interval').value = p.interval || 'month';
document.getElementById('edit_interval_count').value = p.interval_count || 1;
document.getElementById('edit_trial_days').value = p.trial_days || 0;
document.getElementById('edit_is_active').checked = (parseInt(p.is_active, 10) === 1);
}
</script>
<!-- ADD MODAL -->
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nuovo Piano</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="action" value="add_plan">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Code *</label>
<input type="text" name="code" class="form-control" required>
</div>
<div class="col-md-8 mb-3">
<label class="form-label">Nome *</label>
<input type="text" name="name" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descrizione</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Valuta</label>
<input type="text" name="currency" class="form-control" value="EUR" maxlength="3">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Unit amount (centesimi) *</label>
<input type="number" name="unit_amount" class="form-control" min="0" value="0" required>
<small class="text-muted">es: 2990 = 29,90</small>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Trial days</label>
<input type="number" name="trial_days" class="form-control" min="0" value="0">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Interval</label>
<select name="interval" class="form-control">
<option value="day">day</option>
<option value="week">week</option>
<option value="month" selected>month</option>
<option value="year">year</option>
</select>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Interval count</label>
<input type="number" name="interval_count" class="form-control" min="1" value="1">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Stato</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" name="is_active" value="1" checked>
<label class="form-check-label">Attivo</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Stripe product id</label>
<input type="text" name="stripe_product_id" class="form-control" placeholder="prod_... (optional)">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Stripe price id *</label>
<input type="text" name="stripe_price_id" class="form-control" placeholder="price_..." required>
<small class="text-muted">Obbligatorio (NOT NULL)</small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="submit" class="btn btn-primary">Crea Piano</button>
</div>
</form>
</div>
</div>
</div>
<!-- EDIT MODAL -->
<div class="modal fade" id="editPlanModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modifica Piano</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="action" value="edit_plan">
<input type="hidden" name="id" id="edit_id">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Code *</label>
<input type="text" name="code" id="edit_code" class="form-control" required>
</div>
<div class="col-md-8 mb-3">
<label class="form-label">Nome *</label>
<input type="text" name="name" id="edit_name" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descrizione</label>
<textarea name="description" id="edit_description" class="form-control" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Valuta</label>
<input type="text" name="currency" id="edit_currency" class="form-control" maxlength="3">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Unit amount (centesimi) *</label>
<input type="number" name="unit_amount" id="edit_unit_amount" class="form-control" min="0" required>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Trial days</label>
<input type="number" name="trial_days" id="edit_trial_days" class="form-control" min="0">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Interval</label>
<select name="interval" id="edit_interval" class="form-control">
<option value="day">day</option>
<option value="week">week</option>
<option value="month">month</option>
<option value="year">year</option>
</select>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Interval count</label>
<input type="number" name="interval_count" id="edit_interval_count" class="form-control" min="1">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Stato</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active" value="1">
<label class="form-check-label">Attivo</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Stripe product id</label>
<input type="text" name="stripe_product_id" id="edit_stripe_product_id" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Stripe price id *</label>
<input type="text" name="stripe_price_id" id="edit_stripe_price_id" class="form-control" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="submit" class="btn btn-primary">Salva</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>