!empty($_POST['subscriptions_enabled']), 'groups.enabled' => !empty($_POST['groups_enabled']), 'mailgun.api_key' => trim((string)($_POST['mailgun_api_key'] ?? '')), 'mailgun.domain' => trim((string)($_POST['mailgun_domain'] ?? '')), 'mailgun.from' => trim((string)($_POST['mailgun_from'] ?? '')), 'mailgun.api_base' => trim((string)($_POST['mailgun_api_base'] ?? 'https://api.mailgun.net/v3')), 'stripe.secret_key' => trim((string)($_POST['stripe_secret_key'] ?? '')), 'stripe.webhook_secret' => trim((string)($_POST['stripe_webhook_secret'] ?? '')), 'stripe.success_url' => trim((string)($_POST['stripe_success_url'] ?? '')), 'stripe.cancel_url' => trim((string)($_POST['stripe_cancel_url'] ?? '')), 'stripe.portal_return_url' => trim((string)($_POST['stripe_portal_return_url'] ?? '')), 'database.mysql.enabled' => !empty($_POST['mysql_enabled']), 'database.mysql.auto_import' => !empty($_POST['mysql_auto_import']), 'database.mysql.dsn' => trim((string)($_POST['mysql_dsn'] ?? '')), 'database.mysql.username' => trim((string)($_POST['mysql_username'] ?? '')), 'database.mysql.password' => (string)($_POST['mysql_password'] ?? ''), 'database.mysql.sync_interval_seconds' => max(60, (int)($_POST['mysql_sync_interval'] ?? 900)), ]); return 'Platform service settings saved.'; } if ($action === 'save_plans') { $db = getDB(); $plans = $_POST['plans'] ?? []; $boolFeatures = [ 'voice_messages', 'recording_status', 'auto_voice_send', 'auto_voice_play', 'username_color', 'text_export', 'voice_archive', 'voice_export', 'email_2fa', ]; $db->beginTransaction(); try { $update = $db->prepare("UPDATE plans SET name=?,description=?,price_cents=?,currency=?, billing_interval=?,stripe_price_id=?,active=?,sort_order=?,updated_at=? WHERE id=?"); $upsertFeature = $db->prepare("INSERT INTO plan_features (plan_id,feature_key,value_json) VALUES (?,?,?) ON CONFLICT(plan_id,feature_key) DO UPDATE SET value_json=excluded.value_json"); foreach ($plans as $id => $plan) { $id = (int)$id; $name = trim((string)($plan['name'] ?? '')); if ($id < 1 || $name === '') continue; $update->execute([ $name, trim((string)($plan['description'] ?? '')), max(0, (int)($plan['price_cents'] ?? 0)), strtolower(substr(trim((string)($plan['currency'] ?? 'usd')), 0, 3)), in_array($plan['billing_interval'] ?? '', ['day', 'week', 'month', 'year'], true) ? $plan['billing_interval'] : 'month', trim((string)($plan['stripe_price_id'] ?? '')) ?: null, !empty($plan['active']) ? 1 : 0, (int)($plan['sort_order'] ?? 0), time(), $id, ]); foreach ($boolFeatures as $key) { $upsertFeature->execute([$id, $key, json_encode(!empty($plan['features'][$key]))]); } $upsertFeature->execute([$id, 'group_member_limit', json_encode(max(2, min(100, (int)($plan['features']['group_member_limit'] ?? 2))))]); $upsertFeature->execute([$id, 'text_archive_days', json_encode(max(30, min(3650, (int)($plan['features']['text_archive_days'] ?? 30))))]); } $db->commit(); } catch (Throwable $e) { if ($db->inTransaction()) $db->rollBack(); throw $e; } return 'Plans, prices, limits, and feature assignments saved.'; } if ($action === 'create_plan') { $slug = strtolower(trim((string)($_POST['slug'] ?? ''))); $name = trim((string)($_POST['name'] ?? '')); if (!preg_match('/^[a-z0-9_-]{2,30}$/', $slug) || $name === '') { throw new RuntimeException('Plan slug or name is invalid.'); } $db = getDB(); $db->prepare("INSERT INTO plans (slug,name,description,price_cents,currency,billing_interval,active,sort_order,created_at,updated_at) VALUES (?,?,?,0,'usd','month',1,50,?,?)") ->execute([$slug, $name, trim((string)($_POST['description'] ?? '')), time(), time()]); $id = (int)$db->lastInsertId(); $features = [ 'voice_messages' => true, 'recording_status' => false, 'auto_voice_send' => false, 'auto_voice_play' => false, 'group_member_limit' => 2, 'username_color' => false, 'text_archive_days' => 30, 'text_export' => true, 'voice_archive' => false, 'voice_export' => false, 'email_2fa' => false, ]; $insert = $db->prepare("INSERT INTO plan_features (plan_id,feature_key,value_json) VALUES (?,?,?)"); foreach ($features as $key => $value) $insert->execute([$id, $key, json_encode($value)]); return "Plan '$name' created."; } if ($action === 'run_mysql_mirror') { require_once ROOT_DIR . '/lib/mysql_mirror.php'; $result = mirrorAllSQLiteDatabases(); return "MySQL mirror completed: {$result['databases']} database(s), {$result['rows']} row(s)."; } return null; } function adminFeatureLabel(string $key): string { return ucwords(str_replace('_', ' ', $key)); } function renderAdminPlatformTab(): void { $plans = plans(false); $db = getDB(); $subscribers = $db->query("SELECT u.id,u.username,u.email,u.email_verified_at,u.subscription_status, u.subscription_period_end,u.cancel_at_period_end,p.name AS plan_name,u.stripe_customer_id,u.manual_plan_id FROM users u LEFT JOIN plans p ON p.id=COALESCE(u.manual_plan_id,u.plan_id) ORDER BY (u.subscription_status IN ('active','trialing','past_due')) DESC,u.created_at DESC LIMIT 500")->fetchAll(); $stats = [ 'visits_24h' => (int)$db->query("SELECT COUNT(*) FROM visitor_events WHERE event_type='visit' AND created_at>" . (time() - 86400))->fetchColumn(), 'unique_24h' => (int)$db->query("SELECT COUNT(DISTINCT ip_hash) FROM visitor_events WHERE created_at>" . (time() - 86400))->fetchColumn(), 'paid' => (int)$db->query("SELECT COUNT(*) FROM users WHERE subscription_status IN ('active','trialing','past_due')")->fetchColumn(), 'groups' => (int)$db->query("SELECT COUNT(*) FROM chat_groups")->fetchColumn(), ]; $events = $db->query("SELECT event_type,COUNT(*) AS total FROM visitor_events WHERE created_at>" . (time() - 2592000) . " GROUP BY event_type ORDER BY total DESC")->fetchAll(); $featureKeys = [ 'voice_messages', 'recording_status', 'auto_voice_send', 'auto_voice_play', 'group_member_limit', 'username_color', 'text_archive_days', 'text_export', 'voice_archive', 'voice_export', 'email_2fa', ]; ?>
// pricing, entitlements, subscriptions, email, Stripe, database mirroring, and usage
| User | Plan | Status | Period End | Stripe | |
|---|---|---|---|---|---|
| = esc($subscriber['username']) ?> | = esc($subscriber['email'] ?: 'not set') ?> = $subscriber['email_verified_at'] ? 'VERIFIED' : '' ?> | = esc($subscriber['plan_name'] ?: 'Free') ?>= $subscriber['manual_plan_id'] ? ' ADMIN' : '' ?> | = esc(strtoupper($subscriber['subscription_status'])) ?> | = $subscriber['subscription_period_end'] ? esc(fmtTime((int)$subscriber['subscription_period_end'])) : '-' ?>= $subscriber['cancel_at_period_end'] ? ' / CANCELING' : '' ?> | = esc($subscriber['stripe_customer_id'] ?: '-') ?> |