<?php
// ==========================================================
// PROCESS.PHP — PATCH v3.2
// Changelog dari v3.1:
//   [VOUCHER-1] Tambah action 'validate_voucher'
//   [VOUCHER-2] Tambah action 'apply_voucher'
//   [VOUCHER-3] Tambah action 'validate_voucher_menit'
//   [VOUCHER-4] Tambah action ke whitelist ajax
//   [VOUCHER-5] Tambah kolom diskon & voucher_id ke auto_migrate
//   [VOUCHER-6] Proses voucher menit saat action 'start' & 'start_bebas'
//   [VOUCHER-7] Catat diskon voucher saat action 'stop'
// ==========================================================
ob_start();

if (!empty($_REQUEST['ajax']) || in_array($_REQUEST['action'] ?? '', [
    'get_summary','get_member_from_tv','order_fnb','topup_saldo','stop',
    'update_payment_method',
    // [VOUCHER-4]
    'validate_voucher','apply_voucher','validate_voucher_menit',
    'apply_voucher_to_completed', // ← tambahkan di sini
])) {
    error_reporting(0);
    @ini_set('display_errors', '0');
}

include 'config.php';
checkLogin();

$action   = $_REQUEST['action'] ?? '';
$tv_id    = isset($_REQUEST['tv_id']) ? (int)$_REQUEST['tv_id'] : null;
$redirect = "Location: index.php";

$ip = ""; $trx_id = null; $nama_tv = "";
if ($tv_id) {
    $tv_data_global = getTVData($conn, $tv_id);
    if ($tv_data_global) {
        $ip      = $tv_data_global['ip_address'];
        $trx_id  = $tv_data_global['current_trx_id'];
        $nama_tv = $tv_data_global['nama_tv'];
    }
}

$rental_name = get_setting($conn, 'app_name');
$ov          = getOverlaySettings($conn);

// ─────────────────────────────────────────────────────────────────
// HELPER: Sanitasi input stik
// ─────────────────────────────────────────────────────────────────
function sanitize_stik_input(array $raw): string {
    $clean = array_map(function($v) {
        return preg_replace('/[^a-zA-Z0-9 \-_\.]/u', '', trim((string)$v));
    }, $raw);
    $clean = array_filter($clean, fn($v) => $v !== '');
    return count($clean) ? implode(', ', array_values($clean)) : 'Tanpa Stik';
}

// ─────────────────────────────────────────────────────────────────
// HELPER: json_exit
// ─────────────────────────────────────────────────────────────────
function json_exit(array $data): void {
    if (ob_get_length()) ob_clean();
    header('Content-Type: application/json');
    echo json_encode($data);
    exit;
}

// ─────────────────────────────────────────────────────────────────
// HELPER: prepare() dengan error handling terpusat
// ─────────────────────────────────────────────────────────────────
function safe_prepare(mysqli $conn, string $sql): ?mysqli_stmt {
    $stmt = $conn->prepare($sql);
    if ($stmt === false) {
        $err = "PREPARE GAGAL: " . $conn->error . " | SQL: " . $sql;
        error_log($err);
        @file_put_contents(__DIR__ . "/db_error.log",
            date('Y-m-d H:i:s') . " | " . $err . "\n", FILE_APPEND);
        return null;
    }
    return $stmt;
}

// ─────────────────────────────────────────────────────────────────
// HELPER: Cek apakah kolom ada di tabel
// ─────────────────────────────────────────────────────────────────
function column_exists(mysqli $conn, string $table, string $column): bool {
    $res = $conn->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
    return ($res && $res->num_rows > 0);
}

// ─────────────────────────────────────────────────────────────────
// [FIX-4] Auto-migrate: pakai flag session supaya tidak jalan tiap request
// ─────────────────────────────────────────────────────────────────
function auto_migrate_columns(mysqli $conn): void {
    if (!empty($_SESSION['_db_migrated_v32'])) return;

    $migrations = [
        'transactions' => [
            'stik_info'         => "ALTER TABLE transactions ADD COLUMN stik_info VARCHAR(255) NOT NULL DEFAULT 'Tanpa Stik'",
            'remaining_seconds' => "ALTER TABLE transactions ADD COLUMN remaining_seconds INT NOT NULL DEFAULT 0",
            'rate_per_jam'      => "ALTER TABLE transactions ADD COLUMN rate_per_jam INT NOT NULL DEFAULT 0",
            'end_time'          => "ALTER TABLE transactions ADD COLUMN end_time DATETIME NULL DEFAULT NULL",
            'target_selesai'    => "ALTER TABLE transactions ADD COLUMN target_selesai DATETIME NULL DEFAULT NULL",
            'jenis_paket'       => "ALTER TABLE transactions ADD COLUMN jenis_paket ENUM('fixed','open') NOT NULL DEFAULT 'fixed'",
            'payment_method'    => "ALTER TABLE transactions ADD COLUMN payment_method VARCHAR(100) NOT NULL DEFAULT 'Cash'",
            'payment_status'    => "ALTER TABLE transactions ADD COLUMN payment_status ENUM('paid','pending') NOT NULL DEFAULT 'paid'",
            // [VOUCHER-5] Kolom untuk menyimpan info diskon voucher
            'diskon'            => "ALTER TABLE transactions ADD COLUMN diskon INT UNSIGNED NOT NULL DEFAULT 0",
            'voucher_id'        => "ALTER TABLE transactions ADD COLUMN voucher_id INT UNSIGNED NULL DEFAULT NULL",
        ],
    ];

    foreach ($migrations as $table => $cols) {
        foreach ($cols as $col => $ddl) {
            if (!column_exists($conn, $table, $col)) {
                $conn->query($ddl);
                error_log("AUTO_MIGRATE v3.2: Kolom `{$col}` ditambahkan ke `{$table}`");
            }
        }
    }

    $_SESSION['_db_migrated_v32'] = true;
}

auto_migrate_columns($conn);

// ─────────────────────────────────────────────────────────────────
// TUYA SMART PLUG
// ─────────────────────────────────────────────────────────────────
function tuya_auto_control(mysqli $conn, int $tv_id, string $state): bool {
    $check = $conn->query("SHOW TABLES LIKE 'tuya_devices'");
    if (!$check || $check->num_rows === 0) return false;

    $s = safe_prepare($conn, "SELECT * FROM tuya_devices WHERE tv_id = ?");
    if (!$s) return false;
    $s->bind_param("i", $tv_id);
    $s->execute();
    $plugs = $s->get_result();
    $s->close();

    // Baca python path dari DB, fallback ke deteksi OS
    $py_stmt = $conn->prepare("SELECT setting_value FROM system_settings WHERE setting_name = 'python_path' LIMIT 1");
    $python = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? 'python' : 'python3';
    if ($py_stmt) {
        $py_stmt->execute();
        $py_row = $py_stmt->get_result()->fetch_assoc();
        $py_stmt->close();
        if (!empty(trim($py_row['setting_value'] ?? ''))) $python = trim($py_row['setting_value']);
    }
    $script = __DIR__ . DIRECTORY_SEPARATOR . 'tuya_bridge.py';
    if (!file_exists($script)) {
        @file_put_contents(__DIR__ . "/adb_debug.log",
            date('Y-m-d H:i:s') . " | TUYA_AUTO | ERROR: tuya_bridge.py tidak ada\n", FILE_APPEND);
        return false;
    }

    $plug_rows = [];
    while ($plug = $plugs->fetch_assoc()) $plug_rows[] = $plug;

    foreach ($plug_rows as $plug) {
        if (!preg_match('/^[\d\.]+$/', trim($plug['ip_address']))) continue;

        $device_id = escapeshellarg($plug['device_id']);
        $plug_ip   = escapeshellarg($plug['ip_address']);
        $key       = escapeshellarg($plug['local_key']);
        $version   = escapeshellarg($plug['version']);
        $act       = escapeshellarg($state);

        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            $cmd = "start /B $python " . escapeshellarg($script)
                 . " $device_id $plug_ip $key $version $act > NUL 2>&1";
            pclose(popen($cmd, 'r'));
        } else {
            $cmd = "$python " . escapeshellarg($script)
                 . " $device_id $plug_ip $key $version $act > /dev/null 2>&1 &";
            exec($cmd);
        }

        @file_put_contents(__DIR__ . "/adb_debug.log",
            date('Y-m-d H:i:s') . " | TUYA_ASYNC | TV#{$tv_id}"
            . " | Plug: {$plug['nama']} | Action: " . strtoupper($state) . " | Fired async\n",
            FILE_APPEND);

        write_log($conn, 'TUYA', "Async " . strtoupper($state) . " '{$plug['nama']}' TV#{$tv_id}");
    }

    return true;
}

function getCustomerName($conn, $trx_id) {
    if (!$trx_id) return 'Tamu';
    $s = safe_prepare($conn,
        "SELECT m.nama_member FROM transactions t LEFT JOIN members m ON t.member_id = m.id WHERE t.id = ? LIMIT 1");
    if (!$s) return 'Tamu';
    $s->bind_param("i", $trx_id); $s->execute();
    $r    = $s->get_result()->fetch_assoc(); $s->close();
    $nama = $r['nama_member'] ?? '';
    return (!empty($nama)) ? $nama : 'Tamu';
}

// ─────────────────────────────────────────────────────────────────
// Helper sanitasi payment_method dari input POST
// ─────────────────────────────────────────────────────────────────
function sanitize_payment_method(string $raw): string {
    $clean = trim(preg_replace('/[^a-zA-Z0-9 _\-\+]/u', '', $raw));
    return $clean !== '' ? $clean : 'Cash';
}

// ─────────────────────────────────────────────────────────────────
// [VOUCHER] HELPER: Ambil data voucher aktif berdasarkan kode
// ─────────────────────────────────────────────────────────────────
function get_active_voucher(mysqli $conn, string $kode): ?array {
    $stmt = $conn->prepare("SELECT * FROM vouchers WHERE kode = ? LIMIT 1");
    if (!$stmt) return null;
    $stmt->bind_param("s", $kode);
    $stmt->execute();
    $v = $stmt->get_result()->fetch_assoc();
    $stmt->close();
    if (!$v) return null;
    // Auto-expire jika sudah lewat waktu
    if ($v['status'] === 'aktif' && !empty($v['expired_at']) && strtotime($v['expired_at']) < time()) {
        $conn->query("UPDATE vouchers SET status='expired' WHERE id={$v['id']}");
        $v['status'] = 'expired';
    }
    return $v;
}

switch ($action) {

    // =============================================================
    // [VOUCHER-1] VALIDATE VOUCHER — cek diskon nominal/persen
    // Dipanggil AJAX dari modal Stop di index.php
    // Input POST: kode, total, trx_id (opsional)
    // =============================================================
    case 'validate_voucher':
        try {
            $kode  = strtoupper(trim($_POST['kode'] ?? $_GET['kode'] ?? ''));
            $total = (int)($_POST['total'] ?? $_GET['total'] ?? 0);

            if ($kode === '') {
                json_exit(['status' => 'error', 'msg' => 'Kode voucher kosong']);
            }

            $v = get_active_voucher($conn, $kode);

            if (!$v) {
                json_exit(['status' => 'error', 'msg' => 'Kode voucher tidak ditemukan']);
            }

            $status_label = match($v['status']) {
                'used'     => 'sudah pernah dipakai',
                'expired'  => 'sudah kadaluarsa',
                'nonaktif' => 'tidak aktif',
                default    => 'tidak valid',
            };
            if ($v['status'] !== 'aktif') {
                json_exit(['status' => 'error', 'msg' => "Voucher {$status_label}"]);
            }

            // Voucher menit tidak bisa dipakai di billing
            if ($v['jenis'] === 'menit') {
                json_exit([
                    'status' => 'error',
                    'msg'    => 'Voucher menit hanya bisa dipakai saat mulai sesi (Start Session)',
                ]);
            }

            // Hitung diskon
            $diskon = 0;
            $diskon_label = '';
            if ($v['jenis'] === 'nominal') {
                $diskon       = min((int)$v['nilai'], $total);
                $diskon_label = 'Potongan Rp ' . number_format($diskon, 0, ',', '.');
            } elseif ($v['jenis'] === 'persen') {
                $diskon       = (int)round($total * $v['nilai'] / 100);
                $diskon       = min($diskon, $total);
                $diskon_label = 'Potongan ' . $v['nilai'] . '% = Rp ' . number_format($diskon, 0, ',', '.');
            }

            $total_setelah = max(0, $total - $diskon);

            json_exit([
                'status'        => 'success',
                'voucher_id'    => (int)$v['id'],
                'kode'          => $v['kode'],
                'nama'          => $v['nama'],
                'jenis'         => $v['jenis'],
                'nilai'         => (int)$v['nilai'],
                'diskon'        => $diskon,
                'diskon_label'  => $diskon_label,
                'total_awal'    => $total,
                'total_setelah' => $total_setelah,
                'keterangan'    => $v['keterangan'],
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // =============================================================
    // [VOUCHER-2] APPLY VOUCHER — tandai voucher sebagai "used"
    // Dipanggil AJAX setelah stop berhasil dari index.php
    // Input POST: voucher_id, trx_id
    // =============================================================
    case 'apply_voucher':
        try {
            $voucher_id = (int)($_POST['voucher_id'] ?? 0);
            $req_trx_id = (int)($_POST['trx_id']    ?? 0);

            if (!$voucher_id) {
                json_exit(['status' => 'error', 'msg' => 'voucher_id tidak valid']);
            }

            // Double-check status masih aktif (race condition protection)
            $chk = safe_prepare($conn, "SELECT id, status FROM vouchers WHERE id = ?");
            if (!$chk) json_exit(['status' => 'error', 'msg' => 'Database error']);
            $chk->bind_param("i", $voucher_id);
            $chk->execute();
            $row = $chk->get_result()->fetch_assoc();
            $chk->close();

            if (!$row || $row['status'] !== 'aktif') {
                json_exit(['status' => 'error', 'msg' => 'Voucher sudah tidak aktif atau tidak ditemukan']);
            }

            $now = date('Y-m-d H:i:s');
            $upd = safe_prepare($conn,
                "UPDATE vouchers SET status='used', used_by_trx=?, used_at=? WHERE id=?");
            if (!$upd) json_exit(['status' => 'error', 'msg' => 'Database error']);
            $upd->bind_param("isi", $req_trx_id, $now, $voucher_id);
            $upd->execute();
            $upd->close();

            write_log($conn, 'VOUCHER_USED',
                "Voucher ID={$voucher_id} dipakai pada trx_id={$req_trx_id}");

            json_exit(['status' => 'success', 'voucher_id' => $voucher_id, 'trx_id' => $req_trx_id]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }






        // ─────────────────────────────────────────────────────────────────
// APPLY DISKON KE TRANSAKSI YANG SUDAH COMPLETED
// Dipanggil saat kasir input voucher setelah auto-stop
// ─────────────────────────────────────────────────────────────────
case 'apply_voucher_to_completed':
    try {
        $voucher_id_avc  = (int)($_POST['voucher_id']      ?? 0);
        $voucher_diskon  = (int)($_POST['voucher_diskon']  ?? 0);
        $req_trx_id_avc  = (int)($_POST['trx_id']         ?? 0);

        if (!$voucher_id_avc || !$req_trx_id_avc || $voucher_diskon <= 0) {
            json_exit(['status' => 'error', 'msg' => 'Data tidak lengkap']);
        }

        // Ambil total_bayar saat ini
        $chk = safe_prepare($conn,
            "SELECT total_bayar, status FROM transactions WHERE id = ?");
        if (!$chk) json_exit(['status' => 'error', 'msg' => 'DB error']);
        $chk->bind_param("i", $req_trx_id_avc);
        $chk->execute();
        $trx_row = $chk->get_result()->fetch_assoc();
        $chk->close();

        if (!$trx_row) {
            json_exit(['status' => 'error', 'msg' => 'Transaksi tidak ditemukan']);
        }
        if ($trx_row['status'] !== 'completed') {
            json_exit(['status' => 'error', 'msg' => 'Transaksi belum completed']);
        }

        $total_lama   = (int)$trx_row['total_bayar'];
        $diskon_final = min($voucher_diskon, $total_lama);
        $total_baru   = max(0, $total_lama - $diskon_final);

        // Update diskon & total_bayar
        $upd = safe_prepare($conn,
            "UPDATE transactions 
             SET diskon = ?, voucher_id = ?, total_bayar = ?
             WHERE id = ? AND status = 'completed'");
        if (!$upd) json_exit(['status' => 'error', 'msg' => 'DB error']);
        $upd->bind_param("iiii",
            $diskon_final, $voucher_id_avc, $total_baru, $req_trx_id_avc);
        $upd->execute();
        $upd->close();
        // Kembalikan selisih diskon ke saldo member jika transaksi ini pakai Member Saldo
        $chk_member = safe_prepare($conn,
            "SELECT member_id, payment_method FROM transactions WHERE id = ?");
        if ($chk_member) {
            $chk_member->bind_param("i", $req_trx_id_avc);
            $chk_member->execute();
            $trx_member_row = $chk_member->get_result()->fetch_assoc();
            $chk_member->close();

            $trx_member_id  = $trx_member_row['member_id']     ?? null;
            $trx_pay_method = strtolower($trx_member_row['payment_method'] ?? '');

            // Kembalikan saldo hanya jika pembayaran via saldo member
            if ($trx_member_id && (
                str_contains($trx_pay_method, 'saldo') ||
                str_contains($trx_pay_method, 'member') ||
                str_contains($trx_pay_method, 'partial')
            )) {
                $stmt_refund = safe_prepare($conn,
                    "UPDATE members SET saldo = saldo + ? WHERE id = ?");
                if ($stmt_refund) {
                    $stmt_refund->bind_param("ii", $diskon_final, $trx_member_id);
                    $stmt_refund->execute();
                    $stmt_refund->close();
                    write_log($conn, 'VOUCHER_REFUND_SALDO',
                        "Member #{$trx_member_id} dikembalikan Rp " . number_format($diskon_final)
                        . " karena voucher post-stop trx_id={$req_trx_id_avc}");
                }
            }
        }

        // Tandai voucher sebagai used
        $now_avc = date('Y-m-d H:i:s');
        $vupd = safe_prepare($conn,
            "UPDATE vouchers SET status='used', used_by_trx=?, used_at=? WHERE id=?");
        if ($vupd) {
            $vupd->bind_param("isi", $req_trx_id_avc, $now_avc, $voucher_id_avc);
            $vupd->execute();
            $vupd->close();
        }

        write_log($conn, 'VOUCHER_POST_STOP',
            "Voucher ID={$voucher_id_avc} diaplikasikan ke completed trx_id={$req_trx_id_avc}"
            . " | Diskon Rp " . number_format($diskon_final)
            . " | Total baru Rp " . number_format($total_baru));

        json_exit([
            'status'     => 'success',
            'total_baru' => $total_baru,
            'diskon'     => $diskon_final,
        ]);
    } catch (Exception $e) {
        json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
    }

    // =============================================================
    // [VOUCHER-3] VALIDATE VOUCHER MENIT — khusus modal Start Session
    // Input POST: kode, tv_id
    // =============================================================
    case 'validate_voucher_menit':
        try {
            $kode = strtoupper(trim($_POST['kode'] ?? ''));

            if ($kode === '') {
                json_exit(['status' => 'error', 'msg' => 'Kode kosong']);
            }

            $v = get_active_voucher($conn, $kode);

            if (!$v || $v['status'] !== 'aktif') {
                json_exit(['status' => 'error', 'msg' => 'Voucher tidak valid atau tidak aktif']);
            }
            if ($v['jenis'] !== 'menit') {
                json_exit([
                    'status' => 'error',
                    'msg'    => 'Voucher ini bukan jenis menit — gunakan di halaman billing untuk diskon harga',
                ]);
            }

            json_exit([
                'status'       => 'success',
                'voucher_id'   => (int)$v['id'],
                'kode'         => $v['kode'],
                'nama'         => $v['nama'],
                'tambah_menit' => (int)$v['nilai'],
                'keterangan'   => $v['keterangan'],
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // GET SUMMARY — juga return daftar metode pembayaran aktif
    // ─────────────────────────────────────────────────────────────────
    case 'get_summary':
        try {
            $trx_id = isset($_REQUEST['trx_id']) ? (int)$_REQUEST['trx_id'] : null;
            if (!$trx_id) {
                $stmt_tv = safe_prepare($conn, "SELECT current_trx_id FROM tvs WHERE id=?");
                if (!$stmt_tv) throw new Exception("Database error: " . $conn->error);
                $stmt_tv->bind_param("i", $tv_id); $stmt_tv->execute();
                $tv_row  = $stmt_tv->get_result()->fetch_assoc(); $stmt_tv->close();
                $trx_id  = $tv_row['current_trx_id'] ?? null;
            }
            if (!$trx_id) throw new Exception("Tidak ada transaksi aktif");

            $stmt = safe_prepare($conn,
                "SELECT t.*, p.nama_paket, m.nama_member
                 FROM transactions t
                 LEFT JOIN packages p ON t.paket_id = p.id
                 LEFT JOIN members m ON t.member_id = m.id
                 WHERE t.id = ?");
            if (!$stmt) throw new Exception("Database error: " . $conn->error);
            $stmt->bind_param("i", $trx_id); $stmt->execute();
            $trx = $stmt->get_result()->fetch_assoc(); $stmt->close();
            if (!$trx) throw new Exception("Data transaksi tidak ditemukan");

            if ($trx['status'] === 'completed') {
                $end_ts = !empty($trx['end_time']) ? strtotime($trx['end_time']) : time();
                $durasi = max(0, $end_ts - strtotime($trx['start_time']));
} else {
    // [FIX] Open bill yang sedang paused: pakai remaining_seconds bukan selisih waktu
    if ($trx['status'] === 'paused' && $trx['jenis_paket'] === 'open') {
        $durasi = max(0, (int)$trx['remaining_seconds']);
    } else {
        $durasi = max(0, time() - strtotime($trx['start_time']));
    }
}

            $biaya     = (int)$trx['total_bayar'];
            $calc_info = null;
            if ($trx['jenis_paket'] == 'open') {
                $r            = (int)$trx['rate_per_jam'];
                $min          = (int)get_setting($conn, 'min_open_bill');
                $ro           = (int)get_setting($conn, 'round_open_bill') ?: 100;
                $biaya_mentah = ($durasi / 3600) * $r;
                $biaya        = max($min, ceil($biaya_mentah / $ro) * $ro);
                $calc_info    = [
                    'rate_per_jam' => $r,
                    'biaya_mentah' => (int)$biaya_mentah,
                    'min_charge'   => $min,
                    'round_to'     => $ro,
                ];
            }

            $stmtf = safe_prepare($conn,
                "SELECT nama_barang, qty, total_harga FROM orders_fnb WHERE trx_id = ? ORDER BY created_at ASC");
            if (!$stmtf) throw new Exception("Database error: " . $conn->error);
            $stmtf->bind_param("i", $trx_id); $stmtf->execute();
            $fnb_res  = $stmtf->get_result();
            $fnb_list = []; $fnb_total = 0;
            while ($fnb_row = $fnb_res->fetch_assoc()) {
                $fnb_list[] = [
                    'nama_barang' => $fnb_row['nama_barang'],
                    'qty'         => (int)$fnb_row['qty'],
                    'total_harga' => (int)$fnb_row['total_harga'],
                ];
                $fnb_total += (int)$fnb_row['total_harga'];
            }
            $stmtf->close();

            $d_jam      = floor($durasi / 3600);
            $d_menit    = floor(($durasi % 3600) / 60);
            $d_detik    = $durasi % 60;
            $durasi_str = $d_jam > 0 ? "{$d_jam}j {$d_menit}m {$d_detik}d" : "{$d_menit}m {$d_detik}d";

            $member_saldo_val = 0;
            if (!empty($trx['member_id'])) {
                $stmt_ms = safe_prepare($conn, "SELECT saldo FROM members WHERE id = ?");
                if ($stmt_ms) {
                    $stmt_ms->bind_param("i", $trx['member_id']); $stmt_ms->execute();
                    $ms_row           = $stmt_ms->get_result()->fetch_assoc(); $stmt_ms->close();
                    $member_saldo_val = (int)($ms_row['saldo'] ?? 0);
                }
            }

            // Ambil metode pembayaran aktif dari settings
            $metode_raw   = json_decode(get_setting($conn, 'payment_methods') ?: '[]', true);
            $metode_aktif = [];
            if (!empty($metode_raw)) {
                foreach ($metode_raw as $m) {
                    if (!empty($m['aktif'])) {
                        $metode_aktif[] = ['nama' => $m['nama'], 'icon' => $m['icon'] ?? 'bx-credit-card'];
                    }
                }
            }
            if (empty($metode_aktif)) {
                $metode_aktif = [
                    ['nama' => 'Cash',         'icon' => 'bx-money'],
                    ['nama' => 'Transfer Bank', 'icon' => 'bx-transfer'],
                    ['nama' => 'QRIS',          'icon' => 'bx-qr'],
                ];
            }

            json_exit([
                'status'         => 'success',
                'player'         => $trx['nama_member'] ?? 'Tamu',
                'paket'          => $trx['jenis_paket'],
                'jenis_paket'    => $trx['jenis_paket'],
                'biaya_sesi'     => $biaya,
                'fnb_total'      => $fnb_total,
                'fnb_list'       => $fnb_list,
                'grand_total'    => $biaya + $fnb_total,
                'durasi'         => $durasi_str,
                'calc_info'      => $calc_info,
                'member_id'      => $trx['member_id'] ?? null,
                'member_saldo'   => $member_saldo_val,
                'payment_method' => $trx['payment_method'] ?? 'Cash',
                'payment_status' => $trx['payment_status'] ?? 'paid',
                'metode_aktif'   => $metode_aktif,
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // [FIX-1] UPDATE PAYMENT METHOD
    // ─────────────────────────────────────────────────────────────────
    case 'update_payment_method':
        try {
            // tv_id opsional jika trx_id sudah diketahui
if (!$tv_id && !isset($_POST['trx_id'])) throw new Exception("tv_id tidak valid");

            $payment_method = sanitize_payment_method($_POST['payment_method'] ?? 'Cash');
            $payment_status = in_array($_POST['payment_status'] ?? '', ['paid','pending'])
                              ? $_POST['payment_status'] : 'paid';

            $req_trx_id = isset($_POST['trx_id']) ? (int)$_POST['trx_id'] : null;

            if (!$req_trx_id) {
                $stmt_lt = safe_prepare($conn,
                    "SELECT id FROM transactions
                     WHERE tv_id = ? AND status = 'completed'
                     ORDER BY end_time DESC LIMIT 1");
                if (!$stmt_lt) throw new Exception("Database error: " . $conn->error);
                $stmt_lt->bind_param("i", $tv_id); $stmt_lt->execute();
                $lt = $stmt_lt->get_result()->fetch_assoc(); $stmt_lt->close();
                $req_trx_id = $lt['id'] ?? null;
            }

            if (!$req_trx_id) throw new Exception("Transaksi tidak ditemukan untuk TV ini");

            $has_ps_col = column_exists($conn, 'transactions', 'payment_status');
            if ($has_ps_col) {
                $stmt_upd = safe_prepare($conn,
                    "UPDATE transactions SET payment_method=?, payment_status=? WHERE id=?");
                if (!$stmt_upd) throw new Exception("Database error: " . $conn->error);
                $stmt_upd->bind_param("ssi", $payment_method, $payment_status, $req_trx_id);
            } else {
                $stmt_upd = safe_prepare($conn,
                    "UPDATE transactions SET payment_method=? WHERE id=?");
                if (!$stmt_upd) throw new Exception("Database error: " . $conn->error);
                $stmt_upd->bind_param("si", $payment_method, $req_trx_id);
            }

            $stmt_upd->execute();
            $affected = $stmt_upd->affected_rows;
            $stmt_upd->close();

            write_log($conn, 'UPDATE_PAYMENT',
                "TV#{$tv_id} | Trx #{$req_trx_id}"
                . " | Metode: {$payment_method} | Status: {$payment_status}");

            json_exit([
                'status'         => 'success',
                'trx_id'         => $req_trx_id,
                'payment_method' => $payment_method,
                'payment_status' => $payment_status,
                'affected_rows'  => $affected,
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // ORDER FNB
    // ─────────────────────────────────────────────────────────────────
    case 'order_fnb':
        if (!$tv_id) { header("Location: index.php?status=error"); exit; }
        $snack_id     = (int)$_POST['snack_id'];
        $qty          = (int)$_POST['qty'];
        $bypass_saldo = !empty($_POST['bypass_saldo']);

        if ($qty < 1) { header("Location: index.php?status=invalid_qty"); exit; }

        $stmt_tv = safe_prepare($conn, "SELECT current_trx_id FROM tvs WHERE id=?");
        if (!$stmt_tv) { header("Location: index.php?status=db_error"); exit; }
        $stmt_tv->bind_param("i", $tv_id); $stmt_tv->execute();
        $tv_row_fnb = $stmt_tv->get_result()->fetch_assoc(); $stmt_tv->close();
        $trx_id     = $tv_row_fnb['current_trx_id'] ?? null;
        if (!$trx_id) { header("Location: index.php?status=no_active_session"); exit; }

        $stmt_s = safe_prepare($conn, "SELECT id, nama_barang, harga_jual FROM snacks WHERE id=?");
        if (!$stmt_s) { header("Location: index.php?status=db_error"); exit; }
        $stmt_s->bind_param("i", $snack_id); $stmt_s->execute();
        $snack = $stmt_s->get_result()->fetch_assoc(); $stmt_s->close();
        if (!$snack) { header("Location: index.php?status=snack_not_found"); exit; }

        $nama_barang = $snack['nama_barang'];
        $harga_jual  = (int)$snack['harga_jual'];
        $total_harga = $harga_jual * $qty;

        if (!$bypass_saldo) {
            $stmt_trx_m = safe_prepare($conn,
                "SELECT t.member_id, m.saldo, m.nama_member
                 FROM transactions t LEFT JOIN members m ON t.member_id=m.id
                 WHERE t.id = ? LIMIT 1");
            if ($stmt_trx_m) {
                $stmt_trx_m->bind_param("i", $trx_id); $stmt_trx_m->execute();
                $trx_m_row = $stmt_trx_m->get_result()->fetch_assoc(); $stmt_trx_m->close();
                $trx_member_id = $trx_m_row['member_id'] ?? null;
                if ($trx_member_id) {
                    $member_saldo_fnb = (int)($trx_m_row['saldo'] ?? 0);
                    if ($member_saldo_fnb < $total_harga) {
                        $kekurangan_fnb = $total_harga - $member_saldo_fnb;
                        header("Location: index.php?status=saldo_kurang_fnb&kekurangan={$kekurangan_fnb}&tv_id={$tv_id}&snack_id={$snack_id}&qty={$qty}&member_saldo={$member_saldo_fnb}&total_fnb={$total_harga}");
                        exit;
                    }
                }
            }
        }

        $stmt_stok = safe_prepare($conn, "UPDATE snacks SET stok = stok - ? WHERE id = ? AND stok >= ?");
        if (!$stmt_stok) { header("Location: index.php?status=db_error"); exit; }
        $stmt_stok->bind_param("iii", $qty, $snack_id, $qty);
        $stmt_stok->execute();
        if ($stmt_stok->affected_rows === 0) {
            $stmt_stok->close();
            header("Location: index.php?status=stok_tidak_cukup"); exit;
        }
        $stmt_stok->close();

        $stmt_o = safe_prepare($conn,
            "INSERT INTO orders_fnb (trx_id, tv_id, nama_barang, qty, harga_jual, total_harga, created_at)
             VALUES (?, ?, ?, ?, ?, ?, NOW())");
        if (!$stmt_o) { header("Location: index.php?status=db_error"); exit; }
        $stmt_o->bind_param("iisiii", $trx_id, $tv_id, $nama_barang, $qty, $harga_jual, $total_harga);
        if ($stmt_o->execute()) {
            write_log($conn, 'ORDER_FNB',
                "TV $tv_id order {$nama_barang} x$qty = Rp " . number_format($total_harga)
                . ($bypass_saldo ? " [CASH]" : ""));
        }
        $stmt_o->close();
        break;

    // ─────────────────────────────────────────────────────────────────
    // TAMBAH WAKTU
    // ─────────────────────────────────────────────────────────────────
    case 'add_time':
        try {
            if (!$tv_id || !$trx_id) throw new Exception("Tidak ada sesi aktif");
            $paket_id = (int)($_POST['paket_id'] ?? 0);
            if (!$paket_id) throw new Exception("Paket tidak valid");

            $stmt_p = safe_prepare($conn, "SELECT * FROM packages WHERE id = ?");
            if (!$stmt_p) throw new Exception("Database error: " . $conn->error);
            $stmt_p->bind_param("i", $paket_id); $stmt_p->execute();
            $pkt = $stmt_p->get_result()->fetch_assoc(); $stmt_p->close();
            if (!$pkt) throw new Exception("Paket tidak ditemukan");

            $stmt_t = safe_prepare($conn,
                "SELECT * FROM transactions WHERE id = ? AND jenis_paket = 'fixed'");
            if (!$stmt_t) throw new Exception("Database error: " . $conn->error);
            $stmt_t->bind_param("i", $trx_id); $stmt_t->execute();
            $trx = $stmt_t->get_result()->fetch_assoc(); $stmt_t->close();
            if (!$trx) throw new Exception("Transaksi fixed tidak ditemukan");

            $tambah_menit = (int)$pkt['durasi_menit'];
            $tambah_harga = (int)$pkt['harga'];

            if ($trx['status'] === 'paused') {
                $base_sisa  = max(0, (int)$trx['remaining_seconds']);
                $new_target = date('Y-m-d H:i:s', time() + $base_sisa + ($tambah_menit * 60));
            } else {
                $target_ts  = strtotime($trx['target_selesai']);
                $now        = time();
                $new_target = date('Y-m-d H:i:s', ($target_ts < $now ? $now : $target_ts) + ($tambah_menit * 60));
            }

            $new_bayar = (int)$trx['total_bayar'] + $tambah_harga;
            $stmt_u    = safe_prepare($conn,
                "UPDATE transactions SET target_selesai = ?, total_bayar = ? WHERE id = ?");
            if (!$stmt_u) throw new Exception("Database error: " . $conn->error);
            $stmt_u->bind_param("sii", $new_target, $new_bayar, $trx_id);
            $stmt_u->execute(); $stmt_u->close();

            $stmt_tgt = safe_prepare($conn,
                "SELECT UNIX_TIMESTAMP(target_selesai) as target_unix FROM transactions WHERE id = ?");
            if (!$stmt_tgt) throw new Exception("Database error: " . $conn->error);
            $stmt_tgt->bind_param("i", $trx_id); $stmt_tgt->execute();
            $tgt_row        = $stmt_tgt->get_result()->fetch_assoc(); $stmt_tgt->close();
            $new_sisa_detik = max(1, (int)$tgt_row['target_unix'] - time());

            if ($trx['status'] === 'paused') {
                $stmt_rem = safe_prepare($conn,
                    "UPDATE transactions SET remaining_seconds = ? WHERE id = ?");
                if ($stmt_rem) {
                    $stmt_rem->bind_param("ii", $new_sisa_detik, $trx_id);
                    $stmt_rem->execute(); $stmt_rem->close();
                }
            } else {
                $stmt_tv_active = safe_prepare($conn, "UPDATE tvs SET status='active' WHERE id=?");
                if ($stmt_tv_active) {
                    $stmt_tv_active->bind_param("i", $tv_id);
                    $stmt_tv_active->execute(); $stmt_tv_active->close();
                }

                $customer_name  = getCustomerName($conn, $trx_id);
                $target_ts_send = (int)$tgt_row['target_unix'];
                controlTV($ip, 'start_overlay', array_merge([
                    'action'           => 'TIMER',
                    'seconds'          => $new_sisa_detik,
                    'target_timestamp' => $target_ts_send,
                    'minutes'          => 0,
                    'customer'         => $customer_name,
                    'rental'           => $rental_name,
                    'tv_no'            => $nama_tv,
                ], $ov));
            }

            write_log($conn, 'ADD_TIME',
                "TV $tv_id tambah: {$pkt['nama_paket']} (+{$tambah_menit}m) | Status: {$trx['status']}");

            json_exit([
                'status'          => 'success',
                'msg'             => "Berhasil tambah {$tambah_menit} menit",
                'new_sisa_detik'  => $new_sisa_detik,
                'new_total_bayar' => $new_bayar,
                'tambah_menit'    => $tambah_menit,
                'tambah_harga'    => $tambah_harga,
                'nama_paket'      => $pkt['nama_paket'],
                'is_paused'       => ($trx['status'] === 'paused'),
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // [FIX-3] START SESSION FIXED — simpan payment_method & payment_status
    // ─────────────────────────────────────────────────────────────────
    case 'start':
        $paket_id       = (int)$_POST['paket_id'];
        $member_id      = !empty($_POST['member_id']) ? (int)$_POST['member_id'] : null;
        $bypass_saldo   = !empty($_POST['bypass_saldo']);
        $stik           = sanitize_stik_input((array)($_POST['stik'] ?? []));
        $pay_method     = sanitize_payment_method($_POST['payment_method'] ?? 'Cash');
        $pay_status     = in_array($_POST['payment_status'] ?? '', ['paid','pending'])
                          ? $_POST['payment_status'] : 'paid';

        $stmt_p = safe_prepare($conn, "SELECT * FROM packages WHERE id = ?");
        if (!$stmt_p) {
            if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Database error: '.$conn->error]);
            header("Location: index.php?status=db_error"); exit;
        }
        $stmt_p->bind_param("i", $paket_id); $stmt_p->execute();
        $pkt = $stmt_p->get_result()->fetch_assoc(); $stmt_p->close();

        if (!$pkt) {
            if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Paket tidak ditemukan']);
            header("Location: index.php?status=paket_not_found"); exit;
        }

        $customer_name = 'Tamu';
        if ($member_id) {
            $stmt_m = safe_prepare($conn, "SELECT nama_member, saldo FROM members WHERE id = ? LIMIT 1");
            if ($stmt_m) {
                $stmt_m->bind_param("i", $member_id); $stmt_m->execute();
                $mr = $stmt_m->get_result()->fetch_assoc(); $stmt_m->close();
                if ($mr && !empty($mr['nama_member'])) $customer_name = $mr['nama_member'];

                if (!$bypass_saldo) {
                    $member_saldo = (int)($mr['saldo'] ?? 0);
                    $harga_paket  = (int)$pkt['harga'];
                    if ($member_saldo < $harga_paket) {
                        $kekurangan = $harga_paket - $member_saldo;
                        if (isset($_REQUEST['ajax'])) {
                            json_exit([
                                'status'       => 'saldo_kurang',
                                'msg'          => 'Saldo tidak mencukupi',
                                'harga_paket'  => $harga_paket,
                                'member_saldo' => $member_saldo,
                                'kekurangan'   => $kekurangan,
                                'member_name'  => $customer_name,
                                'member_id'    => $member_id,
                                'nama_paket'   => $pkt['nama_paket'],
                            ]);
                        }
                        header("Location: index.php?status=saldo_kurang&kekurangan={$kekurangan}&tv_id={$tv_id}&member_id={$member_id}&paket_id={$paket_id}");
                        exit;
                    }
                }
            }
        }

        $durasi_detik = (int)$pkt['durasi_menit'] * 60;
        controlTV($ip, 'stop');
        controlTV($ip, 'wake');

        $has_stik_col = column_exists($conn, 'transactions', 'stik_info');
        $has_pm_col   = column_exists($conn, 'transactions', 'payment_method');
        $has_ps_col   = column_exists($conn, 'transactions', 'payment_status');

        if ($has_stik_col && $has_pm_col && $has_ps_col) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions
                    (tv_id, member_id, paket_id, start_time, target_selesai,
                     total_bayar, status, jenis_paket, stik_info, payment_method, payment_status)
                 VALUES (?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND), ?, 'ongoing', 'fixed', ?, ?, ?)");
            if (!$stmt) {
                if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Database error: '.$conn->error]);
                header("Location: index.php?status=db_error"); exit;
            }
            $stmt->bind_param("iiiiisss", $tv_id, $member_id, $paket_id, $durasi_detik, $pkt['harga'], $stik, $pay_method, $pay_status);
        } elseif ($has_stik_col && $has_pm_col) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions
                    (tv_id, member_id, paket_id, start_time, target_selesai,
                     total_bayar, status, jenis_paket, stik_info, payment_method)
                 VALUES (?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND), ?, 'ongoing', 'fixed', ?, ?)");
            if (!$stmt) {
                if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Database error: '.$conn->error]);
                header("Location: index.php?status=db_error"); exit;
            }
            $stmt->bind_param("iiiiiss", $tv_id, $member_id, $paket_id, $durasi_detik, $pkt['harga'], $stik, $pay_method);
        } elseif ($has_stik_col) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions
                    (tv_id, member_id, paket_id, start_time, target_selesai,
                     total_bayar, status, jenis_paket, stik_info)
                 VALUES (?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND), ?, 'ongoing', 'fixed', ?)");
            if (!$stmt) {
                if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Database error: '.$conn->error]);
                header("Location: index.php?status=db_error"); exit;
            }
            $stmt->bind_param("iiiiis", $tv_id, $member_id, $paket_id, $durasi_detik, $pkt['harga'], $stik);
        } else {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions
                    (tv_id, member_id, paket_id, start_time, target_selesai,
                     total_bayar, status, jenis_paket)
                 VALUES (?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? SECOND), ?, 'ongoing', 'fixed')");
            if (!$stmt) {
                if (isset($_REQUEST['ajax'])) json_exit(['status'=>'error','msg'=>'Database error: '.$conn->error]);
                header("Location: index.php?status=db_error"); exit;
            }
            $stmt->bind_param("iiiii", $tv_id, $member_id, $paket_id, $durasi_detik, $pkt['harga']);
        }

        if ($stmt->execute()) {
            $new_trx_id = $stmt->insert_id;

            // [VOUCHER-6] Proses voucher menit jika diisi
            $voucher_menit_kode = strtoupper(trim($_POST['voucher_menit_kode'] ?? ''));
            $voucher_menit_id   = (int)($_POST['voucher_menit_id'] ?? 0);
            if ($voucher_menit_kode !== '' && $voucher_menit_id > 0) {
                $sv = get_active_voucher($conn, $voucher_menit_kode);
                if ($sv && $sv['id'] == $voucher_menit_id && $sv['status'] === 'aktif' && $sv['jenis'] === 'menit') {
                    $tambah_detik_v = (int)$sv['nilai'] * 60;
                    // Tambahkan waktu ke transaksi yang baru dibuat
                    $conn->query("UPDATE transactions SET
                        target_selesai   = DATE_ADD(target_selesai, INTERVAL {$tambah_detik_v} SECOND)
                        WHERE id = {$new_trx_id}");
                    // Tandai voucher sebagai used
                    $now_v = date('Y-m-d H:i:s');
                    $upd_v = safe_prepare($conn,
                        "UPDATE vouchers SET status='used', used_by_trx=?, used_at=? WHERE id=?");
                    if ($upd_v) {
                        $upd_v->bind_param("isi", $new_trx_id, $now_v, $sv['id']);
                        $upd_v->execute(); $upd_v->close();
                    }
                    $durasi_detik += $tambah_detik_v; // Update local var untuk overlay
                    write_log($conn, 'VOUCHER_MENIT',
                        "Voucher {$sv['kode']} +{$sv['nilai']}m dipakai trx_id={$new_trx_id}");
                }
            }

            $stmt_tv_start = safe_prepare($conn, "UPDATE tvs SET status='active', current_trx_id=? WHERE id=?");
            if ($stmt_tv_start) {
                $stmt_tv_start->bind_param("ii", $new_trx_id, $tv_id);
                $stmt_tv_start->execute(); $stmt_tv_start->close();
            }

            $stmt_tgt = safe_prepare($conn,
                "SELECT UNIX_TIMESTAMP(target_selesai) as target_unix FROM transactions WHERE id = ?");
            if ($stmt_tgt) {
                $stmt_tgt->bind_param("i", $new_trx_id); $stmt_tgt->execute();
                $tgt_row        = $stmt_tgt->get_result()->fetch_assoc(); $stmt_tgt->close();
                $sisa_detik     = max(1, (int)$tgt_row['target_unix'] - time());
                $target_ts_send = (int)$tgt_row['target_unix'];

                controlTV($ip, 'start_overlay', array_merge([
                    'action'           => 'TIMER',
                    'seconds'          => $sisa_detik,
                    'target_timestamp' => $target_ts_send,
                    'minutes'          => 0,
                    'customer'         => $customer_name,
                    'rental'           => $rental_name,
                    'tv_no'            => $nama_tv,
                ], $ov));
            }

            write_log($conn, 'START_SESSION',
                "$nama_tv | Paket: {$pkt['nama_paket']} ({$pkt['durasi_menit']}m)"
                . " | Harga: Rp " . number_format($pkt['harga'])
                . " | Stik: $stik | Customer: $customer_name"
                . " | Bayar: {$pay_method} [{$pay_status}]"
                . ($bypass_saldo ? " [BYPASS_SALDO]" : "")
                . ($voucher_menit_kode ? " | Voucher: {$voucher_menit_kode}" : ""));

            if ($tv_id) tuya_auto_control($conn, $tv_id, 'on');
        } else {
            error_log("START execute() gagal: " . $stmt->error);
        }
        $stmt->close();
        break;

    // ─────────────────────────────────────────────────────────────────
    // [FIX-3] START OPEN BILL — simpan payment_method & payment_status
    // ─────────────────────────────────────────────────────────────────
    case 'start_bebas':
        $rate         = (int)$_POST['harga_per_jam'];
        $member_id    = !empty($_POST['member_id']) ? (int)$_POST['member_id'] : null;
        $bypass_saldo = !empty($_POST['bypass_saldo']);
        $stik         = sanitize_stik_input((array)($_POST['stik'] ?? []));
        $pay_method   = sanitize_payment_method($_POST['payment_method'] ?? 'Cash');
        $pay_status   = in_array($_POST['payment_status'] ?? '', ['paid','pending'])
                        ? $_POST['payment_status'] : 'paid';

        $customer_name = 'Tamu';
        if ($member_id) {
            $stmt_m = safe_prepare($conn, "SELECT nama_member, saldo FROM members WHERE id = ? LIMIT 1");
            if ($stmt_m) {
                $stmt_m->bind_param("i", $member_id); $stmt_m->execute();
                $mr = $stmt_m->get_result()->fetch_assoc(); $stmt_m->close();
                if ($mr && !empty($mr['nama_member'])) $customer_name = $mr['nama_member'];

                if (!$bypass_saldo) {
                    $member_saldo = (int)($mr['saldo'] ?? 0);
                    $min_charge   = (int)(get_setting($conn, 'min_open_bill') ?: 3000);
                    if ($member_saldo < $min_charge) {
                        $kekurangan = $min_charge - $member_saldo;
                        if (isset($_REQUEST['ajax'])) {
                            json_exit([
                                'status'       => 'saldo_kurang',
                                'msg'          => 'Saldo tidak mencukupi untuk minimum charge open bill',
                                'min_charge'   => $min_charge,
                                'harga_paket'  => $min_charge,
                                'member_saldo' => $member_saldo,
                                'kekurangan'   => $kekurangan,
                                'member_name'  => $customer_name,
                                'member_id'    => $member_id,
                                'nama_paket'   => 'Open Bill (min. charge)',
                            ]);
                        }
                        header("Location: index.php?status=saldo_kurang&kekurangan={$kekurangan}&tv_id={$tv_id}&member_id={$member_id}");
                        exit;
                    }
                }
            }
        }

        controlTV($ip, 'stop');
        controlTV($ip, 'wake');

        $has_stik_col_open = column_exists($conn, 'transactions', 'stik_info');
        $has_rate_col      = column_exists($conn, 'transactions', 'rate_per_jam');
        $has_pm_col_open   = column_exists($conn, 'transactions', 'payment_method');
        $has_ps_col_open   = column_exists($conn, 'transactions', 'payment_status');

        if ($has_stik_col_open && $has_rate_col && $has_pm_col_open && $has_ps_col_open) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions (tv_id, member_id, start_time, total_bayar, status, jenis_paket, rate_per_jam, stik_info, payment_method, payment_status)
                 VALUES (?, ?, NOW(), 0, 'ongoing', 'open', ?, ?, ?, ?)");
            if (!$stmt) { header("Location: index.php?status=db_error"); exit; }
            $stmt->bind_param("iiisss", $tv_id, $member_id, $rate, $stik, $pay_method, $pay_status);
        } elseif ($has_stik_col_open && $has_rate_col && $has_pm_col_open) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions (tv_id, member_id, start_time, total_bayar, status, jenis_paket, rate_per_jam, stik_info, payment_method)
                 VALUES (?, ?, NOW(), 0, 'ongoing', 'open', ?, ?, ?)");
            if (!$stmt) { header("Location: index.php?status=db_error"); exit; }
            $stmt->bind_param("iiiss", $tv_id, $member_id, $rate, $stik, $pay_method);
        } elseif ($has_stik_col_open && $has_rate_col) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions (tv_id, member_id, start_time, total_bayar, status, jenis_paket, rate_per_jam, stik_info)
                 VALUES (?, ?, NOW(), 0, 'ongoing', 'open', ?, ?)");
            if (!$stmt) { header("Location: index.php?status=db_error"); exit; }
            $stmt->bind_param("iiis", $tv_id, $member_id, $rate, $stik);
        } elseif ($has_rate_col) {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions (tv_id, member_id, start_time, total_bayar, status, jenis_paket, rate_per_jam)
                 VALUES (?, ?, NOW(), 0, 'ongoing', 'open', ?)");
            if (!$stmt) { header("Location: index.php?status=db_error"); exit; }
            $stmt->bind_param("iii", $tv_id, $member_id, $rate);
        } else {
            $stmt = safe_prepare($conn,
                "INSERT INTO transactions (tv_id, member_id, start_time, total_bayar, status, jenis_paket)
                 VALUES (?, ?, NOW(), 0, 'ongoing', 'open')");
            if (!$stmt) { header("Location: index.php?status=db_error"); exit; }
            $stmt->bind_param("ii", $tv_id, $member_id);
        }

        if ($stmt->execute()) {
            $new_trx_id = $stmt->insert_id;

            // [VOUCHER-6] Voucher menit juga berlaku untuk Open Bill
            $voucher_menit_kode_ob = strtoupper(trim($_POST['voucher_menit_kode'] ?? ''));
            $voucher_menit_id_ob   = (int)($_POST['voucher_menit_id'] ?? 0);
            if ($voucher_menit_kode_ob !== '' && $voucher_menit_id_ob > 0) {
                $sv_ob = get_active_voucher($conn, $voucher_menit_kode_ob);
                if ($sv_ob && $sv_ob['id'] == $voucher_menit_id_ob
                    && $sv_ob['status'] === 'aktif' && $sv_ob['jenis'] === 'menit') {
                    // Untuk open bill: kurangi start_time agar seperti sudah dapat waktu gratis
                    $tambah_detik_ob = (int)$sv_ob['nilai'] * 60;
                    $conn->query("UPDATE transactions SET
                        start_time = DATE_SUB(start_time, INTERVAL {$tambah_detik_ob} SECOND)
                        WHERE id = {$new_trx_id}");
                    $now_ob = date('Y-m-d H:i:s');
                    $upd_ob = safe_prepare($conn,
                        "UPDATE vouchers SET status='used', used_by_trx=?, used_at=? WHERE id=?");
                    if ($upd_ob) {
                        $upd_ob->bind_param("isi", $new_trx_id, $now_ob, $sv_ob['id']);
                        $upd_ob->execute(); $upd_ob->close();
                    }
                    write_log($conn, 'VOUCHER_MENIT',
                        "Voucher {$sv_ob['kode']} +{$sv_ob['nilai']}m (open) dipakai trx_id={$new_trx_id}");
                }
            }

            $stmt_tv_start = safe_prepare($conn, "UPDATE tvs SET status='active', current_trx_id=? WHERE id=?");
            if ($stmt_tv_start) {
                $stmt_tv_start->bind_param("ii", $new_trx_id, $tv_id);
                $stmt_tv_start->execute(); $stmt_tv_start->close();
            }

            $stmt_st = safe_prepare($conn,
                "SELECT UNIX_TIMESTAMP(start_time) as start_unix FROM transactions WHERE id = ?");
            if ($stmt_st) {
                $stmt_st->bind_param("i", $new_trx_id); $stmt_st->execute();
                $st_row     = $stmt_st->get_result()->fetch_assoc(); $stmt_st->close();
                $start_unix = (int)$st_row['start_unix'];

                controlTV($ip, 'start_overlay', array_merge([
                    'action'          => 'TIMER',
                    'seconds'         => 0,
                    'minutes'         => 0,
                    'start_timestamp' => $start_unix,
                    'customer'        => $customer_name,
                    'rental'          => $rental_name,
                    'tv_no'           => $nama_tv,
                ], $ov));
            }

            write_log($conn, 'START_OPEN',
                "$nama_tv | Open Bill | Rate: Rp " . number_format($rate) . "/jam"
                . " | Customer: $customer_name"
                . " | Bayar: {$pay_method} [{$pay_status}]"
                . ($bypass_saldo ? " [BYPASS_SALDO]" : "")
                . ($voucher_menit_kode_ob ? " | Voucher: {$voucher_menit_kode_ob}" : ""));

            if ($tv_id) tuya_auto_control($conn, $tv_id, 'on');
        } else {
            error_log("START_BEBAS execute() gagal: " . $stmt->error);
        }
        $stmt->close();
        break;

    // ─────────────────────────────────────────────────────────────────
    // PAUSE
    // ─────────────────────────────────────────────────────────────────
    case 'pause':
        if ($trx_id) {
            $stmt_pg = safe_prepare($conn,
                "SELECT id, status, jenis_paket, start_time, target_selesai, remaining_seconds
                 FROM transactions WHERE id = ?");
            if ($stmt_pg) {
                $stmt_pg->bind_param("i", $trx_id); $stmt_pg->execute();
                $pause_trx = $stmt_pg->get_result()->fetch_assoc(); $stmt_pg->close();

                if ($pause_trx && $pause_trx['status'] === 'ongoing') {
                    if ($pause_trx['jenis_paket'] == 'fixed') {
                        $remaining = max(1, strtotime($pause_trx['target_selesai']) - time());
                    } else {
                        $remaining = max(1, time() - strtotime($pause_trx['start_time']));
                    }

                    $stmt_pu = safe_prepare($conn,
                        "UPDATE transactions SET status = 'paused', remaining_seconds = ? WHERE id = ?");
                    if ($stmt_pu) {
                        $stmt_pu->bind_param("ii", $remaining, $trx_id);
                        $stmt_pu->execute(); $stmt_pu->close();
                    }

                    $stmt_tv = safe_prepare($conn, "UPDATE tvs SET status = 'paused' WHERE id = ?");
                    if ($stmt_tv) {
                        $stmt_tv->bind_param("i", $tv_id);
                        $stmt_tv->execute(); $stmt_tv->close();
                    }

                    controlTV($ip, 'pause_overlay');
                    write_log($conn, 'PAUSE',
                        "$nama_tv | Trx #$trx_id | Sisa: {$remaining}d | Tipe: {$pause_trx['jenis_paket']}");
                }
            }
        }
        if (isset($_REQUEST['ajax'])) json_exit(['status' => 'success']);
        break;

    // ─────────────────────────────────────────────────────────────────
    // RESUME
    // ─────────────────────────────────────────────────────────────────
    case 'resume':
        if ($trx_id) {
            $stmt_rg = safe_prepare($conn,
                "SELECT id, status, jenis_paket, start_time, target_selesai, remaining_seconds
                 FROM transactions WHERE id = ?");
            if ($stmt_rg) {
                $stmt_rg->bind_param("i", $trx_id); $stmt_rg->execute();
                $resume_trx = $stmt_rg->get_result()->fetch_assoc(); $stmt_rg->close();

                if ($resume_trx && $resume_trx['status'] === 'paused') {
                    $remaining     = max(1, (int)$resume_trx['remaining_seconds']);
                    $customer_name = getCustomerName($conn, $trx_id);

                    if ($resume_trx['jenis_paket'] == 'fixed') {
                        $stmt_ru = safe_prepare($conn,
                            "UPDATE transactions SET status = 'ongoing',
                             target_selesai = DATE_ADD(NOW(), INTERVAL ? SECOND), remaining_seconds = 0
                             WHERE id = ?");
                        if ($stmt_ru) {
                            $stmt_ru->bind_param("ii", $remaining, $trx_id);
                            $stmt_ru->execute(); $stmt_ru->close();
                        }

                        $stmt_tgt = safe_prepare($conn,
                            "SELECT UNIX_TIMESTAMP(target_selesai) as target_unix FROM transactions WHERE id = ?");
                        if ($stmt_tgt) {
                            $stmt_tgt->bind_param("i", $trx_id); $stmt_tgt->execute();
                            $tgt_row        = $stmt_tgt->get_result()->fetch_assoc(); $stmt_tgt->close();
                            $sisa_detik     = max(1, (int)$tgt_row['target_unix'] - time());
                            $target_ts_send = (int)$tgt_row['target_unix'];

                            $stmt_tv_a = safe_prepare($conn, "UPDATE tvs SET status = 'active' WHERE id = ?");
                            if ($stmt_tv_a) { $stmt_tv_a->bind_param("i", $tv_id); $stmt_tv_a->execute(); $stmt_tv_a->close(); }

                            controlTV($ip, 'wake');
                            controlTV($ip, 'start_overlay', array_merge([
                                'action'           => 'TIMER',
                                'seconds'          => $sisa_detik,
                                'target_timestamp' => $target_ts_send,
                                'minutes'          => 0,
                                'customer'         => $customer_name,
                                'rental'           => $rental_name,
                                'tv_no'            => $nama_tv,
                            ], $ov));
                        }
                    } else {
                        $stmt_ru = safe_prepare($conn,
                            "UPDATE transactions SET status = 'ongoing',
                             start_time = DATE_SUB(NOW(), INTERVAL ? SECOND), remaining_seconds = 0
                             WHERE id = ?");
                        if ($stmt_ru) {
                            $stmt_ru->bind_param("ii", $remaining, $trx_id);
                            $stmt_ru->execute(); $stmt_ru->close();
                        }

                        $stmt_st = safe_prepare($conn,
                            "SELECT UNIX_TIMESTAMP(start_time) as start_unix FROM transactions WHERE id = ?");
                        if ($stmt_st) {
                            $stmt_st->bind_param("i", $trx_id); $stmt_st->execute();
                            $st_row     = $stmt_st->get_result()->fetch_assoc(); $stmt_st->close();
                            $start_unix = (int)$st_row['start_unix'];

                            $stmt_tv_a = safe_prepare($conn, "UPDATE tvs SET status = 'active' WHERE id = ?");
                            if ($stmt_tv_a) { $stmt_tv_a->bind_param("i", $tv_id); $stmt_tv_a->execute(); $stmt_tv_a->close(); }

                            controlTV($ip, 'wake');
                            controlTV($ip, 'start_overlay', array_merge([
                                'action'          => 'TIMER',
                                'seconds'         => 0,
                                'minutes'         => 0,
                                'start_timestamp' => $start_unix,
                                'customer'        => $customer_name,
                                'rental'          => $rental_name,
                                'tv_no'           => $nama_tv,
                            ], $ov));
                        }
                    }

                    write_log($conn, 'RESUME',
                        "$nama_tv | Trx #$trx_id | Sisa: {$remaining}d | Tipe: {$resume_trx['jenis_paket']}");
                }
            }
        }
if (isset($_REQUEST['ajax'])) {
    json_exit([
        'status'         => 'success',
        'new_sisa_detik' => $sisa_detik ?? ($remaining ?? 1),
        'jenis_paket'    => $resume_trx['jenis_paket'] ?? 'fixed',
    ]);
}
break;

    // ─────────────────────────────────────────────────────────────────
    // PINDAH TV
    // ─────────────────────────────────────────────────────────────────
    case 'move':
        $from_tv_id = (int)($_POST['from_tv_id'] ?? $_REQUEST['from_tv_id'] ?? 0);
        $to_tv_id   = (int)($_POST['to_tv_id']   ?? $_REQUEST['to_tv_id']   ?? 0);

        if ($from_tv_id && $to_tv_id && $from_tv_id !== $to_tv_id) {
            $from_tv     = getTVData($conn, $from_tv_id);
            $from_trx_id = $from_tv['current_trx_id'] ?? null;

            if ($from_trx_id) {
                $to_tv = getTVData($conn, $to_tv_id);
                if ($to_tv && $to_tv['status'] === 'available') {
                    $from_nama   = $from_tv['nama_tv'];
                    $to_nama     = $to_tv['nama_tv'];
                    $from_status = $from_tv['status'];

                    $stmt_mv = safe_prepare($conn, "UPDATE transactions SET tv_id = ? WHERE id = ?");
                    if ($stmt_mv) {
                        $stmt_mv->bind_param("ii", $to_tv_id, $from_trx_id);
                        $stmt_mv->execute(); $stmt_mv->close();
                    }

                    $stmt_from = safe_prepare($conn,
                        "UPDATE tvs SET status = 'available', current_trx_id = NULL WHERE id = ?");
                    if ($stmt_from) {
                        $stmt_from->bind_param("i", $from_tv_id);
                        $stmt_from->execute(); $stmt_from->close();
                    }

                    $stmt_to = safe_prepare($conn,
                        "UPDATE tvs SET status = ?, current_trx_id = ? WHERE id = ?");
                    if ($stmt_to) {
                        $stmt_to->bind_param("sii", $from_status, $from_trx_id, $to_tv_id);
                        $stmt_to->execute(); $stmt_to->close();
                    }

                    controlTV($from_tv['ip_address'], 'stop');
                    controlTV($from_tv['ip_address'], 'sleep');

                    if ($from_status === 'active') {
                        $customer_name = getCustomerName($conn, $from_trx_id);
                        $stmt_tgt      = safe_prepare($conn,
                            "SELECT jenis_paket,
                                    UNIX_TIMESTAMP(target_selesai) as target_unix,
                                    UNIX_TIMESTAMP(start_time)    as start_unix
                             FROM transactions WHERE id = ?");
                        if ($stmt_tgt) {
                            $stmt_tgt->bind_param("i", $from_trx_id); $stmt_tgt->execute();
                            $trx_data = $stmt_tgt->get_result()->fetch_assoc(); $stmt_tgt->close();

                            controlTV($to_tv['ip_address'], 'wake');

                            if ($trx_data['jenis_paket'] == 'fixed') {
                                $sisa_detik     = max(1, (int)$trx_data['target_unix'] - time());
                                $target_ts_send = (int)$trx_data['target_unix'];
                                controlTV($to_tv['ip_address'], 'start_overlay', array_merge([
                                    'action'           => 'TIMER',
                                    'seconds'          => $sisa_detik,
                                    'target_timestamp' => $target_ts_send,
                                    'minutes'          => 0,
                                    'customer'         => $customer_name,
                                    'rental'           => $rental_name,
                                    'tv_no'            => $to_nama,
                                ], $ov));
                            } else {
                                $start_unix = (int)$trx_data['start_unix'];
                                controlTV($to_tv['ip_address'], 'start_overlay', array_merge([
                                    'action'          => 'TIMER',
                                    'seconds'         => 0,
                                    'minutes'         => 0,
                                    'start_timestamp' => $start_unix,
                                    'customer'        => $customer_name,
                                    'rental'          => $rental_name,
                                    'tv_no'           => $to_nama,
                                ], $ov));
                            }
                        }
                    }

                    tuya_auto_control($conn, $from_tv_id, 'off');
                    if ($from_status === 'active') {
                        tuya_auto_control($conn, $to_tv_id, 'on');
                    }

                    write_log($conn, 'MOVE',
                        "$from_nama → $to_nama | Trx #$from_trx_id | Tuya: {$from_nama}→OFF, {$to_nama}→ON");
                }
            }
        }
        break;

    // ─────────────────────────────────────────────────────────────────
    // STOP SESSION
    // ─────────────────────────────────────────────────────────────────
    case 'stop':
        file_put_contents(__DIR__ . '/stop_debug.log',
            date('Y-m-d H:i:s') .
            " | POST: " . json_encode($_POST) .
            " | GET: " . json_encode($_GET) .
            "\n",
            FILE_APPEND
        );

        $req_trx_id = isset($_REQUEST['trx_id']) ? (int)$_REQUEST['trx_id'] : null;
        $use_trx_id = $req_trx_id ?: $trx_id;

        if ($use_trx_id) {
            $saved_trx_id = $use_trx_id;

            $stmt_stop = safe_prepare($conn,
                "SELECT t.*, p.nama_paket, m.nama_member, m.saldo as member_saldo
                 FROM transactions t
                 LEFT JOIN packages p ON t.paket_id=p.id
                 LEFT JOIN members m ON t.member_id=m.id
                 WHERE t.id=?");
            if (!$stmt_stop) {
                if (isset($_REQUEST['ajax']))
                    json_exit(['status' => 'error', 'msg' => 'Database error: ' . $conn->error]);
                break;
            }
            $stmt_stop->bind_param("i", $use_trx_id); $stmt_stop->execute();
            $stop_trx = $stmt_stop->get_result()->fetch_assoc(); $stmt_stop->close();

            if (!$tv_id && $stop_trx) {
                $tv_id = (int)$stop_trx['tv_id'];
                $stmt_tvr = safe_prepare($conn, "SELECT ip_address, nama_tv FROM tvs WHERE id=?");
                if ($stmt_tvr) {
                    $stmt_tvr->bind_param("i", $tv_id); $stmt_tvr->execute();
                    $tvr = $stmt_tvr->get_result()->fetch_assoc(); $stmt_tvr->close();
                    if ($tvr) { $ip = $tvr['ip_address']; $nama_tv = $tvr['nama_tv']; }
                }
            }

            if (!$stop_trx || !in_array($stop_trx['status'], ['ongoing', 'paused'])) {
                if (isset($_REQUEST['ajax']))
                    json_exit(['status' => 'success', 'trx_id' => $saved_trx_id, 'payment_method' => 'already_stopped']);
                break;
            }

            // [FIX] Hitung durasi yang benar — jika paused, pakai remaining_seconds (elapsed saat pause)
if ($stop_trx['status'] === 'paused' && $stop_trx['jenis_paket'] === 'open') {
    $stop_durasi = max(0, (int)$stop_trx['remaining_seconds']);
} else {
    $stop_durasi = max(0, time() - strtotime($stop_trx['start_time']));
}
            $stop_h       = floor($stop_durasi / 3600);
            $stop_m       = floor(($stop_durasi % 3600) / 60);
            $stop_s       = $stop_durasi % 60;
            $stop_dur_str = ($stop_h > 0 ? "{$stop_h}j " : "") . "{$stop_m}m {$stop_s}d";
            $biaya_sesi   = (int)$stop_trx['total_bayar'];

            if ($stop_trx['jenis_paket'] == 'open') {
                $r            = (int)$stop_trx['rate_per_jam'];
                $min_charge   = (int)get_setting($conn, 'min_open_bill');
                $round_val    = (int)get_setting($conn, 'round_open_bill') ?: 100;
                $biaya_mentah = ($stop_durasi / 3600) * $r;
                $biaya_sesi   = max($min_charge, ceil($biaya_mentah / $round_val) * $round_val);
                $stmt_upd_bayar = safe_prepare($conn,
                    "UPDATE transactions SET total_bayar=? WHERE id=?");
                if ($stmt_upd_bayar) {
                    $stmt_upd_bayar->bind_param("ii", $biaya_sesi, $use_trx_id);
                    $stmt_upd_bayar->execute(); $stmt_upd_bayar->close();
                }
            }

            $stmt_fnb_total = safe_prepare($conn,
                "SELECT COALESCE(SUM(total_harga), 0) as total FROM orders_fnb WHERE trx_id=?");
            $fnb_total = 0;
            if ($stmt_fnb_total) {
                $stmt_fnb_total->bind_param("i", $use_trx_id); $stmt_fnb_total->execute();
                $fnb_total = (int)($stmt_fnb_total->get_result()->fetch_assoc()['total'] ?? 0);
                $stmt_fnb_total->close();
            }










// STEP 1: Hitung grand_total awal
            $grand_total = $biaya_sesi + $fnb_total;

            // STEP 2: Terapkan voucher SEBELUM potong saldo
            $voucher_id_stop     = (int)($_POST['voucher_id']      ?? 0);
            $voucher_diskon_stop = (int)($_POST['voucher_diskon']  ?? 0);

            if ($voucher_id_stop > 0 && $voucher_diskon_stop > 0) {
                $voucher_diskon_stop = min($voucher_diskon_stop, $grand_total);
                $grand_total         = max(0, $grand_total - $voucher_diskon_stop);

                if (column_exists($conn, 'transactions', 'diskon')
                    && column_exists($conn, 'transactions', 'voucher_id')) {
                    $stmt_dsk = safe_prepare($conn,
                        "UPDATE transactions SET diskon=?, voucher_id=?, total_bayar=? WHERE id=?");
                    if ($stmt_dsk) {
                        $stmt_dsk->bind_param("iiii",
                            $voucher_diskon_stop, $voucher_id_stop, $grand_total, $use_trx_id);
                        $stmt_dsk->execute();
                        $stmt_dsk->close();
                    }
                }

                write_log($conn, 'VOUCHER_DISKON',
                    "Trx #{$use_trx_id} | Diskon Rp " . number_format($voucher_diskon_stop)
                    . " | Total bayar updated: Rp " . number_format($grand_total));
            }

            // STEP 3: Ambil manual payment method
            $member_id = $stop_trx['member_id'] ?? null;

            $manual_method = '';
            if (!empty($_POST['payment_method'])) {
                $manual_method = sanitize_payment_method($_POST['payment_method']);
            }

            // STEP 4: Re-fetch saldo terbaru, lalu potong pakai grand_total sudah didiskon
            if ($member_id) {
                $member_saldo = 0;
                $stmt_fresh = safe_prepare($conn, "SELECT saldo FROM members WHERE id = ?");
                if ($stmt_fresh) {
                    $stmt_fresh->bind_param("i", $member_id);
                    $stmt_fresh->execute();
                    $fresh = $stmt_fresh->get_result()->fetch_assoc();
                    $stmt_fresh->close();
                    $member_saldo = (int)($fresh['saldo'] ?? 0);
                }

                if ($member_saldo >= $grand_total) {
                    $stmt_deduct = safe_prepare($conn, "UPDATE members SET saldo = saldo - ? WHERE id = ?");
                    if ($stmt_deduct) {
                        $stmt_deduct->bind_param("ii", $grand_total, $member_id);
                        $stmt_deduct->execute(); $stmt_deduct->close();
                    }
                    $payment_method = !empty($manual_method) ? $manual_method : 'Member Saldo';
                } elseif ($member_saldo > 0) {
                    $stmt_deduct = safe_prepare($conn, "UPDATE members SET saldo = 0 WHERE id = ?");
                    if ($stmt_deduct) {
                        $stmt_deduct->bind_param("i", $member_id);
                        $stmt_deduct->execute(); $stmt_deduct->close();
                    }
                    $payment_method = !empty($manual_method) ? $manual_method : 'Partial Saldo+Cash';
                } else {
                    $payment_method = !empty($manual_method) ? $manual_method : 'Cash';
                }
            } else {
                $payment_method = !empty($manual_method) ? $manual_method : 'Cash';
            }




// ── lanjut ke bagian $has_pm_col = column_exists(...) seperti biasa ──











            $has_pm_col = column_exists($conn, 'transactions', 'payment_method');
            $has_ps_col = column_exists($conn, 'transactions', 'payment_status');

            if ($has_pm_col && $has_ps_col) {
                $pay_status_stop = 'paid';
                $stmt_complete = safe_prepare($conn,
                    "UPDATE transactions SET end_time=NOW(), status='completed',
                     payment_method=?, payment_status=?
                     WHERE id=? AND status IN ('ongoing','paused')");
                if (!$stmt_complete) {
                    if (isset($_REQUEST['ajax']))
                        json_exit(['status' => 'error', 'msg' => 'Database error: ' . $conn->error]);
                    break;
                }
                $stmt_complete->bind_param("ssi", $payment_method, $pay_status_stop, $use_trx_id);
            } elseif ($has_pm_col) {
                $stmt_complete = safe_prepare($conn,
                    "UPDATE transactions SET end_time=NOW(), status='completed', payment_method=?
                     WHERE id=? AND status IN ('ongoing','paused')");
                if (!$stmt_complete) {
                    if (isset($_REQUEST['ajax']))
                        json_exit(['status' => 'error', 'msg' => 'Database error: ' . $conn->error]);
                    break;
                }
                $stmt_complete->bind_param("si", $payment_method, $use_trx_id);
            } else {
                $stmt_complete = safe_prepare($conn,
                    "UPDATE transactions SET end_time=NOW(), status='completed'
                     WHERE id=? AND status IN ('ongoing','paused')");
                if (!$stmt_complete) {
                    if (isset($_REQUEST['ajax']))
                        json_exit(['status' => 'error', 'msg' => 'Database error: ' . $conn->error]);
                    break;
                }
                $stmt_complete->bind_param("i", $use_trx_id);
            }

            $stmt_complete->execute();
            if ($stmt_complete->affected_rows === 0) {
                $stmt_complete->close();
                write_log($conn, 'STOP_SKIPPED',
                    "$nama_tv | Trx #$use_trx_id sudah completed (double-stop prevented)");
                if (isset($_REQUEST['ajax']))
                    json_exit(['status' => 'success', 'trx_id' => $saved_trx_id, 'payment_method' => 'already_stopped']);
                break;
            }
            $stmt_complete->close();

            if ($tv_id) {
                $stmt_tv_avail = safe_prepare($conn,
                    "UPDATE tvs SET status='available', current_trx_id=NULL WHERE id=?");
                if ($stmt_tv_avail) {
                    $stmt_tv_avail->bind_param("i", $tv_id);
                    $stmt_tv_avail->execute(); $stmt_tv_avail->close();
                }
            }

            $expired_action    = get_setting($conn, 'expired_action') ?: 'sleep';
            $cur_expired_image = get_setting($conn, 'expired_image_path') ?: '';
            $image_file_exists = (!empty($cur_expired_image) && file_exists(__DIR__ . '/' . $cur_expired_image));
            $is_auto_stop      = isset($_REQUEST['auto_timeout']);

            if ($expired_action === 'show_image' && $image_file_exists && $is_auto_stop) {
                $server_url = rtrim(get_setting($conn, 'server_url'), '/');
                if (empty($server_url)) {
                    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
                    if (in_array($host, ['localhost', '127.0.0.1', '::1'])) {
                        $local_ip = gethostbyname(gethostname());
                        if ($local_ip && $local_ip !== '127.0.0.1') $host = $local_ip;
                    }
                    $scheme     = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
                    $server_url = $scheme . '://' . $host . rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
                }
                $image_url = $server_url . '/' . $cur_expired_image;
                controlTV($ip, 'show_expired_image', ['image_url' => $image_url]);
                write_log($conn, 'STOP_SESSION',
                    "$nama_tv | Trx #$use_trx_id | Durasi: $stop_dur_str"
                    . " | Total: Rp " . number_format($grand_total)
                    . ($voucher_diskon_stop > 0 ? " (diskon Rp " . number_format($voucher_diskon_stop) . ")" : "")
                    . " | Bayar: $payment_method | Aksi: Gambar expired");
            } else {
                controlTV($ip, 'stop');
                controlTV($ip, 'sleep');
                write_log($conn, 'STOP_SESSION',
                    "$nama_tv | Trx #$use_trx_id | Durasi: $stop_dur_str"
                    . " | Total: Rp " . number_format($grand_total)
                    . ($voucher_diskon_stop > 0 ? " (diskon Rp " . number_format($voucher_diskon_stop) . ")" : "")
                    . " | Bayar: $payment_method | Aksi: Sleep");
            }

            if ($tv_id) tuya_auto_control($conn, $tv_id, 'off');

            if (isset($_REQUEST['ajax'])) {
                $response_action = ($expired_action === 'show_image' && $image_file_exists && $is_auto_stop)
                    ? 'show_image' : 'sleep';
                json_exit([
                    'status'         => 'success',
                    'trx_id'         => $saved_trx_id,
                    'payment_method' => $payment_method,
                    'expired_action' => $response_action,
                ]);
            }
        } elseif (isset($_REQUEST['ajax'])) {
            json_exit(['status' => 'error', 'msg' => 'Tidak ada transaksi aktif untuk dihentikan']);
        }
        break;

    // ─────────────────────────────────────────────────────────────────
    // TOP-UP SALDO MEMBER
    // ─────────────────────────────────────────────────────────────────
    case 'topup_saldo':
        try {
            $member_id_tu = (int)($_POST['member_id'] ?? 0);
            $amount_tu    = (int)($_POST['amount']    ?? 0);
            if (!$member_id_tu || $amount_tu < 1) throw new Exception("Data tidak valid");

            $stmt_tu = safe_prepare($conn, "UPDATE members SET saldo = saldo + ? WHERE id = ?");
            if (!$stmt_tu) throw new Exception("Database error: " . $conn->error);
            $stmt_tu->bind_param("ii", $amount_tu, $member_id_tu);
            $stmt_tu->execute();
            if ($stmt_tu->affected_rows === 0) throw new Exception("Member tidak ditemukan");
            $stmt_tu->close();

            $stmt_sg = safe_prepare($conn, "SELECT saldo, nama_member FROM members WHERE id = ?");
            if (!$stmt_sg) throw new Exception("Database error: " . $conn->error);
            $stmt_sg->bind_param("i", $member_id_tu); $stmt_sg->execute();
            $sg = $stmt_sg->get_result()->fetch_assoc(); $stmt_sg->close();

            $new_saldo   = (int)$sg['saldo'];
            $nama_member = $sg['nama_member'];

            $conn->query("CREATE TABLE IF NOT EXISTS `member_topups` (
                `id`         INT UNSIGNED NOT NULL AUTO_INCREMENT,
                `member_id`  INT UNSIGNED NOT NULL,
                `amount`     INT UNSIGNED NOT NULL DEFAULT 0,
                `note`       VARCHAR(255) NOT NULL DEFAULT '',
                `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (`id`),
                KEY `idx_member` (`member_id`),
                KEY `idx_created` (`created_at`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

            $has_note = ($conn->query("SHOW COLUMNS FROM member_topups LIKE 'note'")->num_rows > 0);
            if ($has_note) {
                $note_val = 'Top Up via Dashboard';
                $stmt_ins = safe_prepare($conn,
                    "INSERT INTO member_topups (member_id, amount, note, created_at) VALUES (?, ?, ?, NOW())");
                if ($stmt_ins) {
                    $stmt_ins->bind_param("iis", $member_id_tu, $amount_tu, $note_val);
                    $stmt_ins->execute(); $stmt_ins->close();
                }
            } else {
                $stmt_ins = safe_prepare($conn,
                    "INSERT INTO member_topups (member_id, amount, created_at) VALUES (?, ?, NOW())");
                if ($stmt_ins) {
                    $stmt_ins->bind_param("ii", $member_id_tu, $amount_tu);
                    $stmt_ins->execute(); $stmt_ins->close();
                }
            }

            write_log($conn, 'TOPUP',
                "Member #{$member_id_tu} ({$nama_member}) +Rp " . number_format($amount_tu)
                . " | Saldo baru: Rp " . number_format($new_saldo) . " | Via: Dashboard");

            json_exit([
                'status'      => 'success',
                'new_saldo'   => $new_saldo,
                'member_name' => $nama_member,
                'amount'      => $amount_tu,
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // AMBIL DATA MEMBER DARI TV AKTIF
    // ─────────────────────────────────────────────────────────────────
    case 'get_member_from_tv':
        try {
            if (!$tv_id) throw new Exception("tv_id kosong");
            $stmt_gmf = safe_prepare($conn,
                "SELECT t.member_id, m.nama_member, m.saldo
                 FROM tvs tv
                 LEFT JOIN transactions t ON tv.current_trx_id = t.id
                 LEFT JOIN members m ON t.member_id = m.id
                 WHERE tv.id = ? LIMIT 1");
            if (!$stmt_gmf) throw new Exception("Database error: " . $conn->error);
            $stmt_gmf->bind_param("i", $tv_id); $stmt_gmf->execute();
            $gmf = $stmt_gmf->get_result()->fetch_assoc(); $stmt_gmf->close();
            json_exit([
                'status'      => 'success',
                'member_id'   => $gmf['member_id']  ?? null,
                'member_name' => $gmf['nama_member'] ?? 'Tamu',
                'saldo'       => (int)($gmf['saldo'] ?? 0),
            ]);
        } catch (Exception $e) {
            json_exit(['status' => 'error', 'msg' => $e->getMessage()]);
        }

    // ─────────────────────────────────────────────────────────────────
    // REMOTE CONTROL
    // ─────────────────────────────────────────────────────────────────
    case 'adb_power':    controlTV($ip, 'power');    write_log($conn, 'REMOTE', "$nama_tv | Power"); break;
    case 'adb_vol_up':   controlTV($ip, 'vol_up');   write_log($conn, 'REMOTE', "$nama_tv | Vol Up"); break;
    case 'adb_vol_down': controlTV($ip, 'vol_down'); write_log($conn, 'REMOTE', "$nama_tv | Vol Down"); break;
    case 'adb_home':     controlTV($ip, 'home');     write_log($conn, 'REMOTE', "$nama_tv | Home"); break;
    case 'adb_connect':  controlTV($ip, 'connect');  write_log($conn, 'REMOTE', "$nama_tv | IP: $ip"); break;

    case 'check_adb_status':
        $adb_list = getADBList();
        $tvs_q    = $conn->query("SELECT id, ip_address, nama_tv FROM tvs");
        $res      = [];
        while ($r = $tvs_q->fetch_assoc()) {
            $res[] = [
                'id'     => $r['id'],
                'nama'   => $r['nama_tv'],
                'status' => checkADBStatus($r['ip_address'], $adb_list),
            ];
        }
        json_exit(['status' => 'success', 'tvs' => $res]);
}

if (isset($_REQUEST['ajax'])) { if (ob_get_length()) ob_clean(); echo "success"; exit; }
header($redirect); exit;