getConnection(); // ---- Auth check ---- if (!isset($iduserlogin)) { die("Access denied."); } // ---- Admin check (Vanguard usually uses role_id=1 for admin) ---- // Adjust role_id list if needed. $stmt = $pdo->prepare("SELECT role_id, email FROM auth_users WHERE id = ? LIMIT 1"); $stmt->execute([$iduserlogin]); $me = $stmt->fetch(PDO::FETCH_ASSOC); if (!$me || !in_array((int)$me['role_id'], [1])) { die("Access denied: admin only."); } // ---- Handle POST actions ---- $success_message = null; $error = null; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $action = $_POST['action']; // Update school status (local app status) if ($action === 'update_school_status') { $school_id = (int)($_POST['school_id'] ?? 0); $new_status = $_POST['status'] ?? 'active'; $allowed = ['active', 'inactive', 'suspended']; if ($school_id <= 0 || !in_array($new_status, $allowed, true)) { $error = "Invalid request."; } else { $stmt = $pdo->prepare("UPDATE schools SET status = ? WHERE id = ?"); if ($stmt->execute([$new_status, $school_id])) { $success_message = "School status updated."; } else { $error = "Failed updating school status."; } } } // NOTE: Stripe actions (cancel, resume, change plan) should call Stripe API. // Here we provide placeholders so UI is ready. if ($action === 'flag_cancel_at_period_end') { $sub_id = (int)($_POST['subscription_row_id'] ?? 0); $flag = (int)($_POST['flag'] ?? 0); $school_id = (int)($_POST['school_id'] ?? 0); if ($sub_id <= 0 || $school_id <= 0 || !in_array($flag, [0, 1], true)) { $error = "Invalid request."; } else { $stmt = $pdo->prepare(" UPDATE school_subscriptions SET cancel_at_period_end = ? WHERE id = ? AND school_id = ? "); $ok = $stmt->execute([$flag, $sub_id, $school_id]); if ($ok) { $success_message = $flag ? "Marked cancel at period end (LOCAL)." : "Unmarked cancel at period end (LOCAL)."; } else { $error = "Failed updating subscription flag."; } } } } // ---- Filters (GET) ---- $q = trim($_GET['q'] ?? ''); $plan_id = (int)($_GET['plan_id'] ?? 0); $sub_status = trim($_GET['sub_status'] ?? ''); // e.g. active, trialing, past_due, canceled, incomplete, unpaid $school_status = trim($_GET['school_status'] ?? ''); // active, inactive, suspended $has_sub = $_GET['has_sub'] ?? ''; // '1' or '0' or '' $where = []; $params = []; // Search by school name/email/owner email if ($q !== '') { $where[] = "(s.name LIKE ? OR s.email LIKE ? OR ou.email LIKE ? OR CONCAT(ou.first_name,' ',ou.last_name) LIKE ?)"; $like = '%' . $q . '%'; $params[] = $like; $params[] = $like; $params[] = $like; $params[] = $like; } if ($plan_id > 0) { $where[] = "ss.plan_id = ?"; $params[] = $plan_id; } if ($sub_status !== '') { $where[] = "ss.status = ?"; $params[] = $sub_status; } if ($school_status !== '') { $where[] = "s.status = ?"; $params[] = $school_status; } if ($has_sub === '1') { $where[] = "ss.id IS NOT NULL"; } elseif ($has_sub === '0') { $where[] = "ss.id IS NULL"; } $sqlWhere = ''; if (!empty($where)) { $sqlWhere = "WHERE " . implode(" AND ", $where); } // ---- Load plans for filter dropdown ---- $stmt = $pdo->prepare("SELECT id, code, name, currency, unit_amount, `interval`, interval_count, is_active FROM billing_plans ORDER BY is_active DESC, unit_amount ASC, name ASC"); $stmt->execute(); $plans = $stmt->fetchAll(PDO::FETCH_ASSOC); // ---- Main query: schools + subscription + owner + plan ---- $stmt = $pdo->prepare(" SELECT s.id AS school_id, s.name AS school_name, s.email AS school_email, s.status AS school_status, s.created_at AS school_created_at, ou.id AS owner_id, ou.first_name AS owner_first_name, ou.last_name AS owner_last_name, ou.email AS owner_email, ss.id AS subscription_row_id, ss.stripe_customer_id, ss.stripe_subscription_id, COALESCE(ss.status, 'none') AS subscription_status, ss.current_period_start, ss.current_period_end, ss.trial_start, ss.trial_end, ss.cancel_at_period_end, ss.updated_at AS subscription_updated_at, bp.id AS plan_id, bp.code AS plan_code, bp.name AS plan_name, bp.currency, bp.unit_amount, bp.`interval`, bp.interval_count FROM schools s JOIN auth_users ou ON ou.id = s.owner_id LEFT JOIN school_subscriptions ss ON ss.school_id = s.id LEFT JOIN billing_plans bp ON bp.id = ss.plan_id $sqlWhere ORDER BY -- Put schools without subscription at the bottom CASE WHEN ss.id IS NULL THEN 1 ELSE 0 END ASC, -- Show problematic subscriptions first FIELD(ss.status, 'past_due','unpaid','incomplete','incomplete_expired','canceled','paused','trialing','active') ASC, s.created_at DESC "); $stmt->execute($params); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // ---- Helpers ---- function h($v) { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); } function moneyFmt($unit_amount, $currency) { if ($unit_amount === null || $currency === null) return '—'; $amount = ((int)$unit_amount) / 100; return number_format($amount, 2, ',', '.') . ' ' . strtoupper($currency); } function dateFmt($dt) { if (!$dt) return '—'; // Accept both timestamp and datetime strings $ts = is_numeric($dt) ? (int)$dt : strtotime($dt); if (!$ts) return '—'; return date('d/m/Y', $ts); } function badgeClassForSub($status) { $map = [ 'active' => 'bg-success', 'trialing' => 'bg-info', 'past_due' => 'bg-warning', 'unpaid' => 'bg-danger', 'incomplete' => 'bg-warning', 'incomplete_expired' => 'bg-danger', 'canceled' => 'bg-secondary', 'paused' => 'bg-secondary', 'none' => 'bg-secondary', ]; return $map[$status] ?? 'bg-dark'; } function badgeClassForSchool($status) { $map = [ 'active' => 'bg-success', 'inactive' => 'bg-secondary', 'suspended' => 'bg-danger', ]; return $map[$status] ?? 'bg-dark'; } ?>
Admin Subscriptions
Schools + Stripe subscription status overview
Reset
Schools
rows
School Owner School status Subscription Plan Period Trial Cancel at period end Stripe IDs Updated Actions
ID: ·
Created:
Qty:
·
Every
Start:
End:
Start:
End:
Yes No
cust:
sub:
No results