query("SELECT * FROM wa_settings LIMIT 1");
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (Exception $e) {
error_log("Gagal membaca konfigurasi WA dari wa_settings: " . $e->getMessage());
return false;
}
// Jika pengaturan tidak ada, atau dinonaktifkan secara global, batalkan pengiriman
if (!$settings || (int)$settings['is_enabled'] !== 1 || empty($settings['api_token'])) {
return false;
}
$endpoint = $settings['api_url'];
$api_key = $settings['api_token'];
$device = $settings['sender_number'] ?? '';
// Susun payload untuk Ruangwa Gateway
$payload = [
'token' => $api_key,
'number' => $phone,
'message' => $message,
];
// Eksekusi cURL
$ch = curl_init();
$curl_opt = [
CURLOPT_URL => $endpoint,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => false
];
curl_setopt_array($ch, $curl_opt);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ($http_code === 200 || $http_code === 201);
}
// ----------------------------------------------------------------------
// LOAD KONFIGURASI SITUS
// ----------------------------------------------------------------------
$site_config_file = 'site_config.json';
$default_site_config = [
"site_logo_url" => "",
"site_title" => "Go Fly Travelia"
];
if (file_exists($site_config_file)) {
$site_config = json_decode(file_get_contents($site_config_file), true);
if (!$site_config) $site_config = $default_site_config;
} else {
$site_config = $default_site_config;
}
// ----------------------------------------------------------------------
// LOAD KONFIGURASI METODE PEMBAYARAN DARI ADMIN DASHBOARD
// ----------------------------------------------------------------------
$payment_config_file = 'payment_config.json';
$default_payment_config = [
"accounts" => [],
"qris_image_url" => "",
"qris_name" => "Go Fly Travelia"
];
if (file_exists($payment_config_file)) {
$payment_config = json_decode(file_get_contents($payment_config_file), true);
if (!$payment_config) $payment_config = $default_payment_config;
} else {
$payment_config = $default_payment_config;
}
// Menentukan metode pembayaran bawaan/default berdasarkan konfigurasi admin
$first_method = 'transfer';
if (!empty($payment_config['accounts'])) {
$first_method = 'va_' . $payment_config['accounts'][0]['id'];
} elseif (!empty($payment_config['qris_image_url'])) {
$first_method = 'qris';
}
// ----------------------------------------------------------------------
// AMBIL SEMUA DATA FLIGHT & VILLA
// ----------------------------------------------------------------------
$all_flights = [];
$all_villas = [];
try {
$stmt_all_f = $pdo->query("SELECT * FROM flights ORDER BY id DESC");
$all_flights = $stmt_all_f->fetchAll();
$stmt_all_v = $pdo->query("SELECT * FROM villas ORDER BY id DESC");
$all_villas = $stmt_all_v->fetchAll();
} catch (PDOException $e) {
error_log($e->getMessage());
}
// ----------------------------------------------------------------------
// VALIDASI PARAMETER
// ----------------------------------------------------------------------
$type = isset($_GET['type']) ? $_GET['type'] : 'flight'; // flight | villa
$class = isset($_GET['class']) ? $_GET['class'] : 'economy'; // economy | business | first
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
if ($type === 'flight' && !empty($all_flights)) {
$id = $all_flights[0]['id'];
} elseif ($type === 'villa' && !empty($all_villas)) {
$id = $all_villas[0]['id'];
}
}
// ----------------------------------------------------------------------
// AMBIL DETAIL ITEM TERPILIH DARI DATABASE
// ----------------------------------------------------------------------
$item = null;
$error_msg = '';
try {
if ($type === 'flight') {
$stmt = $pdo->prepare("SELECT * FROM flights WHERE id = :id LIMIT 1");
$stmt->execute(['id' => $id]);
$item = $stmt->fetch();
if (!$item && !empty($all_flights)) {
$item = $all_flights[0];
$id = $item['id'];
}
if ($item) {
$base_price = $item['price'];
$multiplier = 1;
$class_label = 'Ekonomi';
if ($class === 'business') {
$multiplier = 2.5;
$class_label = 'Bisnis';
} elseif ($class === 'first') {
$multiplier = 6.5;
$class_label = 'First Class';
}
$item['display_price'] = round($base_price * $multiplier);
$item['class'] = $class;
$item['class_label'] = $class_label;
$item['base_price'] = $base_price;
preg_match('/\(([A-Z]{3})\)/', $item['origin'], $m_o);
$item['origin_code'] = isset($m_o[1]) ? $m_o[1] : '???';
$item['origin_city'] = trim(explode('(', $item['origin'])[0]);
preg_match('/\(([A-Z]{3})\)/', $item['destination'], $m_d);
$item['dest_code'] = isset($m_d[1]) ? $m_d[1] : '???';
$item['dest_city'] = trim(explode('(', $item['destination'])[0]);
}
} elseif ($type === 'villa') {
$stmt = $pdo->prepare("SELECT * FROM villas WHERE id = :id LIMIT 1");
$stmt->execute(['id' => $id]);
$item = $stmt->fetch();
if (!$item && !empty($all_villas)) {
$item = $all_villas[0];
$id = $item['id'];
}
if ($item) {
$item['display_price'] = $item['price_per_night'];
$item['base_price'] = $item['price_per_night'];
}
}
} catch (PDOException $e) {
error_log($e->getMessage());
$error_msg = 'Gagal memuat data pemesanan. Silakan coba lagi.';
}
if (!$item && !$error_msg) {
$error_msg = 'Item yang Anda cari tidak ditemukan atau database data kosong.';
}
// Tentukan step dasar
$step = isset($_GET['step']) ? (int)$_GET['step'] : 1;
$checkout_complete = false;
$booking_code = '';
$form_errors = [];
// Base form data
if ($type === 'flight') {
$form_data = [
'fullname' => '',
'address' => '',
'phone' => '',
'email' => '',
'service_type' => 'domestic',
'departure_date' => '',
'return_date' => '',
'notes' => '',
'payment_method' => $first_method,
'doc1_path' => '',
'doc2_path' => '',
'doc3_path' => '',
'doc4_path' => '',
'doc5_path' => '',
'doc6_path' => '',
'doc7_path' => '',
'doc8_path' => '',
];
} else {
$form_data = [
'title' => '',
'fullname' => '',
'email' => '',
'phone' => '',
'passengers' => 1,
'check_in_date' => date('Y-m-d'),
'check_out_date' => date('Y-m-d', strtotime('+1 day')),
'payment_method' => $first_method,
'notes' => ''
];
}
if (isset($_SESSION['checkout_form'])) {
$form_data = array_merge($form_data, $_SESSION['checkout_form']);
}
// ----------------------------------------------------------------------
// POST DARI STEP 1: FORM DATA DIRI (KETIKA "LANJUT KE PEMBAYARAN" DIKLIK)
// ----------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'submit_details') {
if ($type === 'flight') {
$form_data['fullname'] = trim($_POST['fullname'] ?? '');
$form_data['address'] = trim($_POST['address'] ?? '');
$form_data['phone'] = trim($_POST['phone'] ?? '');
$form_data['email'] = trim($_POST['email'] ?? '');
$form_data['service_type'] = $_POST['service_type'] ?? 'domestic';
$form_data['departure_date'] = trim($_POST['departure_date'] ?? '');
$form_data['return_date'] = trim($_POST['return_date'] ?? '');
$form_data['notes'] = trim($_POST['notes'] ?? '');
if (empty($form_data['fullname'])) $form_errors['fullname'] = 'Nama lengkap wajib diisi.';
if (empty($form_data['phone'])) $form_errors['phone'] = 'Nomor WhatsApp wajib diisi.';
if (empty($form_data['email']) || !filter_var($form_data['email'], FILTER_VALIDATE_EMAIL)) $form_errors['email'] = 'Email tidak valid.';
if (empty($form_data['departure_date'])) $form_errors['departure_date'] = 'Tanggal keberangkatan wajib diisi.';
$upload_dir = __DIR__ . '/uploads/passports/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
for ($i = 1; $i <= 8; $i++) {
$input_name = 'doc' . $i;
$form_data[$input_name . '_path'] = $_POST['existing_' . $input_name] ?? '';
if (isset($_FILES[$input_name]) && $_FILES[$input_name]['error'] === UPLOAD_ERR_OK) {
if ($_FILES[$input_name]['size'] > 2 * 1024 * 1024) {
$form_errors[$input_name] = 'Ukuran berkas melebihi 2MB.';
} else {
$ext = strtolower(pathinfo($_FILES[$input_name]['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'pdf'];
if (!in_array($ext, $allowed)) {
$form_errors[$input_name] = 'Format file harus JPG, PNG, atau PDF.';
} else {
$filename = 'doc' . $i . '_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$dest = $upload_dir . $filename;
if (move_uploaded_file($_FILES[$input_name]['tmp_name'], $dest)) {
$form_data[$input_name . '_path'] = 'uploads/passports/' . $filename;
}
}
}
} elseif ($i === 1 && empty($form_data[$input_name . '_path'])) {
if (!isset($_FILES[$input_name]) || $_FILES[$input_name]['error'] === UPLOAD_ERR_NO_FILE) {
$form_errors[$input_name] = 'Dokumen 1 wajib diunggah.';
}
}
}
} else {
$form_data['title'] = trim($_POST['title'] ?? '');
$form_data['fullname'] = trim($_POST['fullname'] ?? '');
$form_data['email'] = trim($_POST['email'] ?? '');
$form_data['phone'] = trim($_POST['phone'] ?? '');
$form_data['passengers'] = (int)($_POST['passengers'] ?? 1);
$form_data['check_in_date'] = trim($_POST['check_in_date'] ?? '');
$form_data['check_out_date'] = trim($_POST['check_out_date'] ?? '');
$form_data['notes'] = trim($_POST['notes'] ?? '');
if (empty($form_data['fullname'])) $form_errors['fullname'] = 'Nama lengkap wajib diisi.';
if (empty($form_data['email']) || !filter_var($form_data['email'], FILTER_VALIDATE_EMAIL)) $form_errors['email'] = 'Email tidak valid.';
if (empty($form_data['phone'])) $form_errors['phone'] = 'Nomor telepon wajib diisi.';
if ($form_data['passengers'] < 1 || $form_data['passengers'] > 10) $form_errors['passengers'] = 'Jumlah tamu harus 1-10.';
if (empty($form_data['check_in_date'])) {
$form_errors['check_in_date'] = 'Tanggal check-in wajib diisi.';
}
if (empty($form_data['check_out_date'])) {
$form_errors['check_out_date'] = 'Tanggal check-out wajib diisi.';
} elseif (!empty($form_data['check_in_date']) && strtotime($form_data['check_out_date']) <= strtotime($form_data['check_in_date'])) {
$form_errors['check_out_date'] = 'Tanggal check-out harus setelah tanggal check-in.';
}
}
$_SESSION['checkout_form'] = $form_data;
// Jika Tidak Ada Error -> Jalankan Trigger Pengiriman WhatsApp Bersamaan
if (empty($form_errors)) {
$step = 2;
$pax_count = ($type === 'flight') ? 1 : max(1, (int)($form_data['passengers'] ?? 1));
// Kalkulasi malam untuk tagihan sementara secara aman
$nights_temp = 1;
if ($type === 'villa') {
try {
$check_in = !empty($form_data['check_in_date']) ? $form_data['check_in_date'] : date('Y-m-d');
$check_out = !empty($form_data['check_out_date']) ? $form_data['check_out_date'] : date('Y-m-d', strtotime('+1 day'));
$datetime1 = new DateTime($check_in);
$datetime2 = new DateTime($check_out);
$interval = $datetime1->diff($datetime2);
$nights_temp = max(1, (int)$interval->days);
} catch (Exception $e) {
$nights_temp = 1;
}
}
$base_price = $item ? $item['display_price'] : 0;
$multiplier_temp = ($type === 'villa') ? $nights_temp : 1; // menggunakan jumlah malam, bukan jumlah tamu
$subtotal_temp = $base_price * $multiplier_temp;
$tax_temp = round($subtotal_temp * 0.11);
$admin_fee_temp = ($type === 'flight') ? 25000 : 50000;
$grand_total_temp = $subtotal_temp + $tax_temp + $admin_fee_temp;
$product_title = ($type === 'flight') ? ($item['airline'] ?? '') : ($item['name'] ?? '');
$customer_name = $form_data['fullname'];
// HUBUNGKAN DENGAN WA FORMATTER UNTUK CHECKOUT STEP 1
if (function_exists('generate_wa_invoice_template')) {
$mock_booking = [
'id' => 'DRAFT',
'type' => $type,
'product_title' => $product_title,
'customer_name' => $customer_name,
'customer_phone' => $form_data['phone'],
'customer_email' => $form_data['email'],
'guest_qty' => $pax_count,
'price' => $grand_total_temp,
'from_route' => ($type === 'flight' && isset($item['origin'])) ? $item['origin'] : null,
'dest_route' => ($type === 'flight' && isset($item['destination'])) ? $item['destination'] : null,
'departure_date' => ($type === 'flight') ? $form_data['departure_date'] : null,
'return_date' => ($type === 'flight' && !empty($form_data['return_date'])) ? $form_data['return_date'] : null,
'cabin_class' => ($type === 'flight') ? $class : null,
'check_in_date' => ($type === 'villa') ? $form_data['check_in_date'] : null,
'check_out_date' => ($type === 'villa') ? $form_data['check_out_date'] : null,
];
// LOGIKA PEMILIHAN TRIGGER STEP 1: PISAH ANTARA PESAWAT & VILLA
$trigger_step_1 = ($type === 'villa') ? 'checkout_villa_step_1' : 'checkout_step_1';
$wa_invoice_msg = generate_wa_invoice_template($mock_booking, $trigger_step_1);
if ($wa_invoice_msg) {
// 1. Kirim Notifikasi ke Nomor HP Pelanggan
kirim_notifikasi_wa($pdo, $form_data['phone'], $wa_invoice_msg);
// 2. Kirim Notifikasi ke Nomor HP Admin secara Bersamaan
$no_wa_admin = '6281234567890'; // <--- GANTI dengan nomor WhatsApp Admin Anda
$pesan_admin_step1 = "🔔 *DRAFT BOOKING BARU (STEP 1)*\n\n" .
"Pelanggan *" . $customer_name . "* baru saja melengkapi data diri dan beralih ke halaman pembayaran.\n\n" .
"▪ Layanan: *" . ($type === 'flight' ? 'Tiket Penerbangan' : 'Sewa Villa') . "*\n" .
"▪ Detail: *" . $product_title . "*\n" .
"▪ Total Tagihan: *Rp " . number_format($grand_total_temp, 0, ',', '.') . "*\n" .
"▪ No. WA Pelanggan: " . $form_data['phone'] . "\n\n" .
"Sistem sedang menunggu pelanggan memilih metode pembayaran di Step 2.";
kirim_notifikasi_wa($pdo, $no_wa_admin, $pesan_admin_step1);
}
}
} else {
$step = 1;
}
}
// ----------------------------------------------------------------------
// POST DARI STEP 2: KONFIRMASI PEMBAYARAN
// ----------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'confirm_payment') {
$form_data = $_SESSION['checkout_form'] ?? $form_data;
$form_data['payment_method'] = $_POST['payment_method'] ?? $first_method;
$pax_count = ($type === 'flight') ? 1 : max(1, (int)($form_data['passengers'] ?? 1));
// Hitung jumlah malam untuk kalkulasi di step database secara aman
$nights_temp = 1;
if ($type === 'villa') {
try {
$check_in = !empty($form_data['check_in_date']) ? $form_data['check_in_date'] : date('Y-m-d');
$check_out = !empty($form_data['check_out_date']) ? $form_data['check_out_date'] : date('Y-m-d', strtotime('+1 day'));
$datetime1 = new DateTime($check_in);
$datetime2 = new DateTime($check_out);
$interval = $datetime1->diff($datetime2);
$nights_temp = max(1, (int)$interval->days);
} catch (Exception $e) {
$nights_temp = 1;
}
}
$multiplier_temp = ($type === 'villa') ? $nights_temp : 1; // menggunakan jumlah malam, bukan jumlah tamu
$total_price = ($item ? $item['display_price'] : 0) * $multiplier_temp;
$tax = round($total_price * 0.11);
$admin_fee = ($type === 'flight') ? 25000 : 50000;
$grand_total = $total_price + $tax + $admin_fee;
$uploaded_docs_array = [];
for ($i = 1; $i <= 8; $i++) {
if (!empty($form_data['doc' . $i . '_path'])) {
$uploaded_docs_array[] = $form_data['doc' . $i . '_path'];
}
}
$uploaded_docs_json = json_encode($uploaded_docs_array);
try {
$stmtInsert = $pdo->prepare("
INSERT INTO bookings (
type, product_title, customer_name, customer_address, customer_phone, customer_email,
guest_qty, special_note, from_route, dest_route, departure_date, return_date,
cabin_class, check_in_date, check_out_date, uploaded_docs, status, payment_method, price
) VALUES (
:type, :product_title, :customer_name, :customer_address, :customer_phone, :customer_email,
:guest_qty, :special_note, :from_route, :dest_route, :departure_date, :return_date,
:cabin_class, :check_in_date, :check_out_date, :uploaded_docs, 'pending', :payment_method, :price
)
");
$stmtInsert->execute([
'type' => $type,
'product_title' => ($type === 'flight') ? ($item['airline'] ?? '') : ($item['name'] ?? ''),
'customer_name' => $form_data['fullname'],
'customer_address' => $form_data['address'] ?? '',
'customer_phone' => $form_data['phone'],
'customer_email' => $form_data['email'],
'guest_qty' => $pax_count,
'special_note' => $form_data['notes'] ?? '',
'from_route' => ($type === 'flight') ? ($item['origin'] ?? null) : null,
'dest_route' => ($type === 'flight') ? ($item['destination'] ?? null) : null,
'departure_date' => ($type === 'flight') ? $form_data['departure_date'] : null,
'return_date' => ($type === 'flight' && !empty($form_data['return_date'])) ? $form_data['return_date'] : null,
'cabin_class' => ($type === 'flight') ? $class : null,
'check_in_date' => ($type === 'villa') ? $form_data['check_in_date'] : null,
'check_out_date' => ($type === 'villa') ? $form_data['check_out_date'] : null,
'uploaded_docs' => $uploaded_docs_json,
'payment_method' => $form_data['payment_method'],
'price' => $grand_total
]);
$last_booking_id = $pdo->lastInsertId();
$booking_code = 'GFT-' . str_pad($last_booking_id, 5, '0', STR_PAD_LEFT);
$_SESSION['last_booking_id'] = $last_booking_id;
$_SESSION['booking_code'] = $booking_code;
$_SESSION['booking_time'] = date('d M Y, H:i') . ' WIB';
// Mengambil kembali baris data booking real dari database untuk Formatter
$stmt_fetch = $pdo->prepare("SELECT * FROM bookings WHERE id = ?");
$stmt_fetch->execute([$last_booking_id]);
$booking_row = $stmt_fetch->fetch(PDO::FETCH_ASSOC);
// Hapus draf form lama setelah checkout berhasil disubmit ke DB agar pemesanan baru bersih
unset($_SESSION['checkout_form']);
// HUBUNGKAN DENGAN WA FORMATUK CHECKOUT STEP 2
if (function_exists('generate_wa_invoice_template') && $booking_row) {
// LOGIKA PEMILIHAN TRIGGER STEP 2: PISAH ANTARA PESAWAT & VILLA
$trigger_step_2 = ($type === 'villa') ? 'checkout_villa_step_2' : 'checkout_step_2';
$wa_billing_msg = generate_wa_invoice_template($booking_row, $trigger_step_2);
if ($wa_billing_msg) {
// --- SINKRONISASI INFORMASI REKENING & QRIS SECARA DINAMIS KE WA ---
$rekening_info = "";
$metode_bayar = $form_data['payment_method'];
if ($metode_bayar === 'qris') {
// Membuat URL domain absolut secara dinamis agar link gambar QRIS dapat di-klik di WA
$domain = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'];
$folder_project = trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
$base_url = $domain . ($folder_project ? '/' . $folder_project : '');
$qris_url = !empty($payment_config['qris_image_url']) ? $base_url . '/' . $payment_config['qris_image_url'] : '';
if (!empty($qris_url)) {
$rekening_info = "*QRIS (" . $payment_config['qris_name'] . ")*\nSilakan unduh / scan kode QR melalui tautan di bawah ini:\n👉 " . $qris_url;
} else {
$rekening_info = "*QRIS (" . $payment_config['qris_name'] . ")*\n(Penting: Gambar QRIS belum diunggah oleh admin di dashboard pembayaran)";
}
} elseif (strpos($metode_bayar, 'va_') === 0) {
$va_id = substr($metode_bayar, 3);
$found_acc = false;
foreach ($payment_config['accounts'] as $acc) {
if ($acc['id'] === $va_id) {
$rekening_info = "*Virtual Account " . $acc['bank_name'] . "*\n🏦 No. Rekening / VA: *" . $acc['va_number'] . "*\n👤 Atas Nama: *" . $acc['owner_name'] . "*";
$found_acc = true;
break;
}
}
if (!$found_acc) {
$rekening_info = "*Transfer Bank* (Akun Virtual Account tidak ditemukan)";
}
} else {
$rekening_info = "*Transfer Bank* (Silakan hubungi bantuan admin untuk nomor rekening tujuan)";
}
// Mengganti placeholder {{rekening}} mentah secara dinamis dengan metode pembayaran spesifik
$wa_billing_msg = str_replace('{{rekening}}', $rekening_info, $wa_billing_msg);
kirim_notifikasi_wa($pdo, $form_data['phone'], $wa_billing_msg);
}
}
} catch (PDOException $e) {
error_log('Bookings table save failed: ' . $e->getMessage());
}
$checkout_complete = true;
$step = 3;
}
// ----------------------------------------------------------------------
// PROSES UNGGAH BUKTI TRANSFER OLEH PELANGGAN (CLIENT-SIDE)
// ----------------------------------------------------------------------
$proof_success_msg = '';
$proof_error_msg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload_proof_client') {
$b_id = (int)($_POST['booking_id'] ?? 0);
if ($b_id <= 0) {
$proof_error_msg = "Sesi transaksi tidak valid atau telah kedaluwarsa. Harap lakukan pemesanan ulang.";
} elseif (!isset($_FILES['proof_file'])) {
$proof_error_msg = "Berkas bukti transfer tidak terdeteksi dalam request.";
} elseif ($_FILES['proof_file']['error'] !== UPLOAD_ERR_OK) {
$error_code = $_FILES['proof_file']['error'];
switch ($error_code) {
case UPLOAD_ERR_INI_SIZE:
$proof_error_msg = "File terlalu besar. Melebihi batas maksimal konfigurasi server."; break;
case UPLOAD_ERR_FORM_SIZE:
$proof_error_msg = "File terlalu besar. Melebihi batas HTML."; break;
case UPLOAD_ERR_PARTIAL:
$proof_error_msg = "File hanya berhasil diunggah sebagian. Harap ulangi proses."; break;
case UPLOAD_ERR_NO_FILE:
$proof_error_msg = "Tidak ada file bukti transfer yang dipilih."; break;
default:
$proof_error_msg = "Kesalahan pengunggahan tidak dikenal (Kode Error: $error_code)."; break;
}
} else {
$file_tmp = $_FILES['proof_file']['tmp_name'];
$file_name = $_FILES['proof_file']['name'];
$file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
$allowed_exts = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'];
if (in_array($file_ext, $allowed_exts)) {
$upload_dir = 'uploads/payments/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$new_file_name = 'proof_client_' . $b_id . '_' . uniqid() . '.' . $file_ext;
$dest_path = $upload_dir . $new_file_name;
if (move_uploaded_file($file_tmp, $dest_path)) {
try {
$stmt = $pdo->prepare("UPDATE bookings SET transfer_proof_url = :url WHERE id = :id");
$stmt->execute(['url' => $dest_path, 'id' => $b_id]);
$proof_success_msg = "Bukti transfer berhasil diunggah! Pembayaran Anda akan segera diverifikasi oleh admin.";
} catch (PDOException $e) {
$proof_error_msg = "Gagal memperbarui bukti pembayaran di database.";
}
} else {
$proof_error_msg = "Gagal menyimpan berkas ke server.";
}
} else {
$proof_error_msg = "Format berkas tidak didukung. Harap gunakan gambar atau PDF.";
}
}
$step = 3;
}
// ----------------------------------------------------------------------
// SINKRONISASI BILA SUDAH SELESAI TRANSAKSI (HANYA AKTIF DI STEP 3)
// ----------------------------------------------------------------------
$client_proof_uploaded = false;
$saved_booking = null;
// Proteksi Session Lock: Hanya load dan kunci type invoice jika berada di step 3 (selesai)
$stored_booking_id = ($step === 3 || $checkout_complete) ? ($_SESSION['last_booking_id'] ?? 0) : 0;
if ($stored_booking_id > 0) {
try {
$stmt_check = $pdo->prepare("SELECT * FROM bookings WHERE id = :id LIMIT 1");
$stmt_check->execute(['id' => $stored_booking_id]);
$saved_booking = $stmt_check->fetch();
if ($saved_booking) {
if (!empty($saved_booking['transfer_proof_url'])) {
$client_proof_uploaded = true;
}
// Override data lokal agar sesuai data yang masuk ke database
$type = $saved_booking['type'];
$passenger_count = $saved_booking['guest_qty'];
if ($type === 'villa') {
$form_data['check_in_date'] = $saved_booking['check_in_date'];
$form_data['check_out_date'] = $saved_booking['check_out_date'];
$form_data['passengers'] = $saved_booking['guest_qty'];
$form_data['fullname'] = $saved_booking['customer_name'];
$form_data['phone'] = $saved_booking['customer_phone'];
}
}
} catch (PDOException $e) {}
}
// ----------------------------------------------------------------------
// HITUNG TOTAL HARGA (GLOBAL)
// ----------------------------------------------------------------------
$passenger_count = ($type === 'flight') ? 1 : max(1, (int)($form_data['passengers'] ?? 1));
$price_per_unit = $item ? $item['display_price'] : 0;
// Kalkulasi jumlah malam untuk villa secara aman dari crash Exception
$nights = 1;
if ($type === 'villa') {
try {
$check_in = !empty($form_data['check_in_date']) ? $form_data['check_in_date'] : date('Y-m-d');
$check_out = !empty($form_data['check_out_date']) ? $form_data['check_out_date'] : date('Y-m-d', strtotime('+1 day'));
$datetime1 = new DateTime($check_in);
$datetime2 = new DateTime($check_out);
$interval = $datetime1->diff($datetime2);
$nights = max(1, (int)$interval->days);
} catch (Exception $e) {
$nights = 1;
}
}
// Subtotal berdasarkan malam (untuk villa) atau unit (untuk penerbangan)
$multiplier = ($type === 'villa') ? $nights : 1;
$subtotal = $price_per_unit * $multiplier;
$tax = round($subtotal * 0.11);
$admin_fee = ($type === 'flight') ? 25000 : 50000;
$grand_total = $subtotal + $tax + $admin_fee;
// Jika transaksi sudah selesai, gunakan grand_total asli dari database
if ($saved_booking) {
$grand_total = $saved_booking['price'];
$subtotal = $grand_total - $tax - $admin_fee;
}
$dep_date_val = !empty($form_data['departure_date']) ? date('Y-m-d', strtotime($form_data['departure_date'])) : '';
$ret_date_val = !empty($form_data['return_date']) ? date('Y-m-d', strtotime($form_data['return_date'])) : '';
// ----------------------------------------------------------------------
// PRE-PROSES DETAIL METODE PEMBAYARAN TERPILIH (STEP 3)
// ----------------------------------------------------------------------
$selected_method_label = 'Transfer Bank';
$selected_acc = null;
$is_qris = false;
if (isset($form_data['payment_method'])) {
if ($form_data['payment_method'] === 'qris') {
$selected_method_label = 'QRIS';
$is_qris = true;
} elseif (strpos($form_data['payment_method'], 'va_') === 0) {
$va_id = substr($form_data['payment_method'], 3);
foreach ($payment_config['accounts'] as $acc) {
if ($acc['id'] === $va_id) {
$selected_acc = $acc;
$selected_method_label = 'Virtual Account — ' . $acc['bank_name'];
break;
}
}
}
}
?>
Checkout —
['icon' => 'fa-user', 'label' => 'Data Diri'],
2 => ['icon' => 'fa-credit-card', 'label' => 'Pembayaran'],
3 => ['icon' => 'fa-circle-check', 'label' => 'Selesai'],
];
foreach ($steps as $num => $s):
$is_active = ($num === $step || ($checkout_complete && $num === 3));
$is_done = $num < $step || $checkout_complete;
?>
Pilih Layanan Pemesanan
Tentukan jenis layanan yang ingin Anda pesan sebelum melengkapi formulir.
Layanan Belum Tersedia
Kembali ke Beranda
Data Pemesan Tiket
Lengkapi data diri dan unggah berkas paspor untuk penerbitan tiket Anda.
Data Tamu Villa
Isi data diri Anda untuk kelancaran proses check-in villa mewah.
Pilih Metode Pembayaran
Semua transaksi dienkripsi dan diproses secara aman sesuai pengaturan sistem.
🎉 Pemesanan Berhasil!
Terima kasih, ! Pemesanan Anda telah kami terima dan sedang diproses.
Detail pemesanan telah dikirim ke WhatsApp
Instruksi Pembayaran Anda
Silakan pindai kode QR di bawah ini
Gunakan aplikasi m-Banking atau dompet digital (GoPay, OVO, Dana, LinkAja, dll.) Anda untuk menyelesaikan pembayaran.
Metode Pembayaran
Nomor Rekening / VA
Nama Pemilik Rekening (A/N)
Kredensial Belum Siap
Hubungi layanan bantuan kami untuk detail nomor rekening atau unggahan kode QRIS.
Detail Pesanan
Layanan:
Kelas:
Berangkat:
Kembali:
Metode Bayar:
Tipe:
Tamu: Tamu
Check-In:
Check-Out:
Durasi: Malam
Metode Bayar:
Total Dibayar
Rp
Langkah Selanjutnya
- Silakan lakukan transfer sesuai rincian di atas.
- Setelah transfer, gunakan tombol Unggah Bukti Transfer di bawah ini untuk mengirim tanda terima transaksi Anda.
- Setelah pembayaran terkonfirmasi oleh admin, e-tiket/voucher akan otomatis diterbitkan.
Formulir Kirim Bukti Transfer