16300, 'SGD' => 12100, 'EUR' => 17600, 'IDR' => 1];
if (!isset($_SESSION['exchange_rates'])) {
$ratesJson = @file_get_contents("https://open.er-api.com/v6/latest/USD");
if ($ratesJson) {
$ratesData = json_decode($ratesJson, true);
if (isset($ratesData['rates'])) {
$_SESSION['exchange_rates'] = $ratesData['rates'];
}
}
}
$rates = $_SESSION['exchange_rates'] ?? $fallbackRates;
$idrRate = $rates['IDR'] ?? 16300;
if ($fromCurrency === 'USD') return $amount * $idrRate;
$fromRate = $rates[$fromCurrency] ?? null;
if ($fromRate) return ($amount / $fromRate) * $idrRate;
return $amount * ($fallbackRates[$fromCurrency] ?? 1);
}
$total_idr_raw = convertToIdr($total_usd, $currency);
$total_idr = round($total_idr_raw);
$fare_idr = round($total_idr * 0.80);
$taxes_idr = $total_idr - $fare_idr;
// 5. FORM SUBMISSION HANDLING & PAYMENT REDIRECT
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = isset($_POST['email']) ? trim($_POST['email']) : '';
$phone = isset($_POST['phoneNumber']) ? trim($_POST['phoneNumber']) : '';
$submitted_passengers = isset($_POST['passengers']) && is_array($_POST['passengers']) ? $_POST['passengers'] : [];
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Please provide a valid email address.";
}
if (empty($phone)) {
$errors[] = "Phone number is required.";
}
for ($i = 0; $i < $passenger_count; $i++) {
$pax = $submitted_passengers[$i] ?? [];
$givenName = isset($pax['givenName']) ? trim($pax['givenName']) : '';
$familyName = isset($pax['familyName']) ? trim($pax['familyName']) : '';
$dob = isset($pax['dob']) ? trim($pax['dob']) : '';
if (empty($givenName) || empty($familyName)) {
$errors[] = "Full name of passenger #" . ($i + 1) . " is required.";
}
if (empty($dob)) {
$errors[] = "Date of birth of passenger #" . ($i + 1) . " is required.";
}
$file_field = "passenger_doc_" . $i;
if (isset($_FILES[$file_field]) && $_FILES[$file_field]['error'] === UPLOAD_ERR_OK) {
$fileName = $_FILES[$file_field]['name'];
$fileSize = $_FILES[$file_field]['size'];
$fileNameCmps = explode(".", $fileName);
$fileExtension = strtolower(end($fileNameCmps));
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
if (!in_array($fileExtension, $allowedExtensions)) {
$errors[] = "Supported document formats for passenger #" . ($i + 1) . " are limited to JPG, JPEG, PNG, or PDF.";
}
if ($fileSize > 2097152) {
$errors[] = "Document file size for passenger #" . ($i + 1) . " exceeds the maximum limit of 2MB.";
}
} else {
$errors[] = "Identity document (Passport/ID Card) for passenger #" . ($i + 1) . " is required.";
}
}
$uploaded_docs_paths = [];
if (empty($errors)) {
$upload_dir = 'uploads/';
if (!is_dir($upload_dir)) {
@mkdir($upload_dir, 0755, true);
}
for ($i = 0; $i < $passenger_count; $i++) {
$file_field = "passenger_doc_" . $i;
$fileTmpPath = $_FILES[$file_field]['tmp_name'];
$fileName = $_FILES[$file_field]['name'];
$fileNameCmps = explode(".", $fileName);
$fileExtension = strtolower(end($fileNameCmps));
$newFileName = 'pax_' . ($i + 1) . '_' . time() . '_' . bin2hex(random_bytes(8)) . '.' . $fileExtension;
$dest_path = $upload_dir . $newFileName;
if (move_uploaded_file($fileTmpPath, $dest_path)) {
$uploaded_docs_paths[] = [
'passenger_index' => $i + 1,
'original_name' => $fileName,
'file_path' => $dest_path
];
} else {
$errors[] = "Failed to process document upload for passenger #" . ($i + 1);
}
}
}
if (empty($errors)) {
try {
$baggage_cost = 0;
$seat_cost = 0;
$services_summary_lines = [];
$submitted_baggage = isset($_POST['baggage']) && is_array($_POST['baggage']) ? $_POST['baggage'] : [];
for ($i = 0; $i < $passenger_count; $i++) {
$bag_option = $submitted_baggage[$i] ?? 'standard';
$bag_price = 0;
$bag_label = "Standard Included";
switch ($bag_option) {
case '5kg':
$bag_price = 150000;
$bag_label = "+5 KG Upgrade";
break;
case '10kg':
$bag_price = 300000;
$bag_label = "+10 KG Upgrade";
break;
case '20kg':
$bag_price = 550000;
$bag_label = "+20 KG Upgrade";
break;
case '30kg':
$bag_price = 800000;
$bag_label = "+30 KG Upgrade";
break;
}
if ($bag_price > 0) {
$baggage_cost += $bag_price;
$services_summary_lines[] = "Passenger #" . ($i + 1) . " Baggage: " . $bag_label . " (+Rp " . number_format($bag_price, 0, ',', '.') . ")";
}
}
$submitted_seats = isset($_POST['seats']) && is_array($_POST['seats']) ? $_POST['seats'] : [];
for ($i = 0; $i < $passenger_count; $i++) {
$seat_code = strtoupper(trim($submitted_seats[$i] ?? ''));
if (!empty($seat_code)) {
$seat_price = 0;
$row_num = (int)preg_replace('/[^0-9]/', '', $seat_code);
if ($row_num >= 1 && $row_num <= 2) {
$seat_price = 150000;
$seat_type = "Extra Legroom Seat";
} else {
$seat_price = 50000;
$seat_type = "Standard Seat";
}
$seat_cost += $seat_price;
$services_summary_lines[] = "Passenger #" . ($i + 1) . " Seat: " . $seat_code . " (" . $seat_type . " - +Rp " . number_format($seat_price, 0, ',', '.') . ")";
}
}
$total_idr_with_extras = $total_idr + $baggage_cost + $seat_cost;
$pax_details = [];
foreach ($submitted_passengers as $index => $pax) {
$gender_label = (isset($pax['gender']) && $pax['gender'] === 'f') ? 'Female' : 'Male';
$title_label = ucfirst($pax['title'] ?? 'Mr');
$doc_info = "";
foreach ($uploaded_docs_paths as $doc) {
if ($doc['passenger_index'] === ($index + 1)) {
$doc_info = " (Doc: " . $doc['original_name'] . ")";
break;
}
}
$assigned_seat = !empty($submitted_seats[$index]) ? strtoupper($submitted_seats[$index]) : 'None';
$assigned_bag = !empty($submitted_baggage[$index]) && $submitted_baggage[$index] !== 'standard' ? $submitted_baggage[$index] : 'Standard';
$pax_details[] = "Adult " . ($index + 1) . ": " . $title_label . ". " . strtoupper(($pax['givenName'] ?? '') . ' ' . ($pax['familyName'] ?? '')) . " (" . $gender_label . ", DOB: " . ($pax['dob'] ?? '') . ", Seat: " . $assigned_seat . ", Baggage: " . $assigned_bag . ")" . $doc_info;
}
$special_note = implode("\n", $pax_details);
$meta_key = isset($_POST['meta_key']) ? trim($_POST['meta_key']) : '';
$meta_val = isset($_POST['meta_val']) ? trim($_POST['meta_val']) : '';
if (!empty($meta_key) && !empty($meta_val)) {
$special_note .= "\n\nMetadata: " . $meta_key . " = " . $meta_val;
}
if (!empty($services_summary_lines)) {
$special_note .= "\n\n=== EXTRA SERVICES DETAILS ===\n" . implode("\n", $services_summary_lines);
}
$primary_passenger = strtoupper($submitted_passengers[0]['givenName'] . ' ' . $submitted_passengers[0]['familyName']);
$product_title = $carrier_name . " (" . $origin . " - " . $destination . ")";
// Prepare PDO insertion
$stmtInsert = $pdo->prepare("INSERT INTO bookings (
customer_name,
customer_phone,
customer_email,
guest_qty,
price,
status,
special_note,
type,
product_title,
from_route,
dest_route,
cabin_class,
departure_date,
return_date,
payment_method,
uploaded_docs
) VALUES (
:customer_name,
:customer_phone,
:customer_email,
:guest_qty,
:price,
'pending',
:special_note,
'flight',
:product_title,
:from_route,
:dest_route,
:cabin_class,
:departure_date,
:return_date,
'Bank Transfer',
:uploaded_docs
)");
$stmtInsert->execute([
'customer_name' => $primary_passenger,
'customer_phone' => $phone,
'customer_email' => $email,
'guest_qty' => $passenger_count,
'price' => $total_idr_with_extras,
'special_note' => $special_note,
'product_title' => $product_title,
'from_route' => $origin,
'dest_route' => $destination,
'cabin_class' => $cabinClass,
'departure_date' => $dep_date,
'return_date' => $isReturn ? $retDepDate : null,
'uploaded_docs' => json_encode($uploaded_docs_paths)
]);
$booking_id = $pdo->lastInsertId();
// ========================================================
// AMBIL PENGATURAN MASTER DEFAULT AGAR SINKRON
// ========================================================
$master_logo = 'https://plain-apac-prod-public.komododecks.com/202606/20/zBiz1pT76LPgsDgGiz3k/image.png';
$master_company = 'PT Hilton Tours Travel';
$master_address = "Ruko Keprimall No.46, Sukajadi, Kec. Batam Kota, Kota Batam,\nKepulauan Riau 29444, Indonesia";
$master_pnr = 'Payment Not Verified';
$master_ticket_no = 'Payment Not Verified';
$master_airline_code = strtoupper(substr($carrier_name, 0, 2)) . "-" . rand(100, 999);
try {
$stmtMCheck = $pdo->query("SELECT * FROM invoice_master_settings WHERE id = 1");
$mRow = $stmtMCheck->fetch();
if ($mRow) {
$master_logo = $mRow['invoice_logo_url'] ?: $master_logo;
$master_company = $mRow['company_name'] ?: $master_company;
$master_address = $mRow['company_address'] ?: $master_address;
$master_pnr = $mRow['ticket_pnr'] ?: $master_pnr;
$master_ticket_no = $mRow['ticket_number'] ?: $master_ticket_no;
$master_airline_code = $mRow['airline_code'] ?: $master_airline_code;
}
} catch (Exception $e) {
// Fallback silent
}
// OTOMATIS GENERATE DATA INVOICE BARU BERDASARKAN BOOKING ID
try {
$airports_map = [
'CGK' => ['Jakarta (CGK)', 'Soekarno Hatta Intl. Airport Terminal 3'],
'SIN' => ['Singapore (SIN)', 'Changi International Airport Terminal 4'],
'DPS' => ['Denpasar (DPS)', 'Ngurah Rai Intl. Airport Terminal Domestic'],
'SUB' => ['Surabaya (SUB)', 'Juanda International Airport Terminal 1'],
'KUL' => ['Kuala Lumpur (KUL)', 'Kuala Lumpur International Airport T2'],
];
$f1_from_name = $airports_map[$origin][0] ?? "Airport " . $origin;
$f1_from_detail = $airports_map[$origin][1] ?? "Departures Terminal";
$f1_to_name = $airports_map[$destination][0] ?? "Airport " . $destination;
$f1_to_detail = $airports_map[$destination][1] ?? "Arrivals Terminal";
$f2_carrier = $isReturn ? $retCarrierName : "";
$f2_code = $isReturn ? strtoupper(substr($retCarrierName, 0, 2)) . "-" . rand(100, 999) : "";
$f2_class = $isReturn ? ucfirst($cabinClass) . " (Promo)" : "";
$f2_from_code = $isReturn ? $retOrigin : "";
$f2_from_name = $isReturn ? ($airports_map[$retOrigin][0] ?? "Airport " . $retOrigin) : "";
$f2_from_detail = $isReturn ? ($airports_map[$retOrigin][1] ?? "Departures Terminal") : "";
$f2_to_code = $isReturn ? $retDestination : "";
$f2_to_name = $isReturn ? ($airports_map[$retDestination][0] ?? "Airport " . $retDestination) : "";
$f2_to_detail = $isReturn ? ($airports_map[$retDestination][1] ?? "Arrivals Terminal") : "";
$f2_date = $isReturn ? $retDepDate : "";
$f2_time = $isReturn ? ($retDepTime . " - " . $retArrTime) : "";
$f2_duration = $isReturn ? "Duration " . $retDuration : "";
$f2_baggage = $isReturn ? "Baggage 23 kg" : "";
$inv_price_fee = 15000;
$inv_price_tax = round($total_idr_with_extras * 0.10);
$inv_price_other = 0;
$inv_price_basic = $total_idr_with_extras - $inv_price_fee - $inv_price_tax;
$invoice_num_id = "INV/" . date('Ymd') . "/HT/" . str_pad($booking_id, 6, '0', STR_PAD_LEFT);
$stmtInv = $pdo->prepare("INSERT INTO invoice_settings (
id, invoice_logo_url, company_name, company_address,
footer_no_print_title, footer_no_print_desc, footer_support_phone, footer_support_email, footer_support_web, footer_eco_title, footer_eco_sub,
invoice_id, booking_id, booking_date, issue_date, payment_method, payment_status,
contact_name, contact_email, contact_phone,
passenger_name, passenger_type, passenger_identity,
f1_carrier, f1_code, f1_class, f1_from_code, f1_from_name, f1_from_detail, f1_to_code, f1_to_name, f1_to_detail, f1_date, f1_time, f1_duration, f1_baggage,
f2_carrier, f2_code, f2_class, f2_from_code, f2_from_name, f2_from_detail, f2_to_code, f2_to_name, f2_to_detail, f2_date, f2_time, f2_duration, f2_baggage,
ticket_pnr, ticket_number, airline_code,
price_basic, price_tax, price_other, price_fee, price_total
) VALUES (
:id, :invoice_logo_url, :company_name, :company_address,
:footer_no_print_title, :footer_no_print_desc, :footer_support_phone, :footer_support_email, :footer_support_web, :footer_eco_title, :footer_eco_sub,
:invoice_id, :booking_id, :booking_date, :issue_date, :payment_method, :payment_status,
:contact_name, :contact_email, :contact_phone,
:passenger_name, :passenger_type, :passenger_identity,
:f1_carrier, :f1_code, :f1_class, :f1_from_code, :f1_from_name, :f1_from_detail, :f1_to_code, :f1_to_name, :f1_to_detail, :f1_date, :f1_time, :f1_duration, :f1_baggage,
:f2_carrier, :f2_code, :f2_class, :f2_from_code, :f2_from_name, :f2_from_detail, :f2_to_code, :f2_to_name, :f2_to_detail, :f2_date, :f2_time, :f2_duration, :f2_baggage,
:ticket_pnr, :ticket_number, :airline_code,
:price_basic, :price_tax, :price_other, :price_fee, :price_total
)");
$stmtInv->execute([
'id' => $booking_id,
'invoice_logo_url' => $master_logo,
'company_name' => $master_company,
'company_address' => $master_address,
'footer_no_print_title' => 'Tidak perlu cetak!',
'footer_no_print_desc' => 'E-tiket dapat langsung ditunjukkan melalui HP.',
'footer_support_phone' => '0823 7456 4489 (24 jam)',
'footer_support_email' => 'support@hiltontourstravel.online',
'footer_support_web' => 'https://hiltontourstravel.online',
'footer_eco_title' => 'HILTONTOURSTRAVEL.ONLINE',
'footer_eco_sub' => 'Peduli lingkungan',
'invoice_id' => $invoice_num_id,
'booking_id' => (string)$booking_id,
'booking_date' => date('d M Y, H:i'),
'issue_date' => date('d M Y, H:i'),
'payment_method' => 'Bank Transfer',
'payment_status' => 'PENDING',
'contact_name' => $primary_passenger,
'contact_email' => $email,
'contact_phone' => $phone,
'passenger_name' => $primary_passenger,
'passenger_type' => 'Adult',
'passenger_identity' => 'Identity Document Uploaded',
'f1_carrier' => $carrier_name,
'f1_code' => strtoupper(substr($carrier_name, 0, 2)) . "-" . rand(100, 999),
'f1_class' => ucfirst($cabinClass) . " (Promo)",
'f1_from_code' => $origin,
'f1_from_name' => $f1_from_name,
'f1_from_detail' => $f1_from_detail,
'f1_to_code' => $destination,
'f1_to_name' => $f1_to_name,
'f1_to_detail' => $f1_to_detail,
'f1_date' => $dep_date,
'f1_time' => $dep_time . " - " . $arr_time,
'f1_duration' => "Duration " . $duration,
'f1_baggage' => "Baggage 23 kg",
'f2_carrier' => $f2_carrier,
'f2_code' => $f2_code,
'f2_class' => $f2_class,
'f2_from_code' => $f2_from_code,
'f2_from_name' => $f2_from_name,
'f2_from_detail' => $f2_from_detail,
'f2_to_code' => $f2_to_code,
'f2_to_name' => $f2_to_name,
'f2_to_detail' => $f2_to_detail,
'f2_date' => $f2_date,
'f2_time' => $f2_time,
'f2_duration' => $f2_duration,
'f2_baggage' => $f2_baggage,
'ticket_pnr' => $master_pnr,
'ticket_number' => $master_ticket_no,
'airline_code' => $master_airline_code,
'price_basic' => $inv_price_basic,
'price_tax' => $inv_price_tax,
'price_other' => $inv_price_other,
'price_fee' => $inv_price_fee,
'price_total' => $total_idr_with_extras
]);
} catch (Exception $ex) {
// Fail-silent fallback
}
$_SESSION['order_id'] = $booking_id;
$_SESSION['primary_passenger'] = $primary_passenger;
$_SESSION['stored_total_idr'] = $total_idr_with_extras;
header("Location: pembayaran.php");
exit();
} catch (PDOException $e) {
$errors[] = "Failed to register booking in database: " . $e->getMessage();
}
}
}
?>
Checkout – Duffel Flight Booking
Search Flights
Booking Checkout
This fare offer will automatically expire on WIB
Selected Route Details
•
-
• Non-stop
Depart from Airport:
Arrive at Airport:
Reschedule Policy
Flight schedule adjustments are permitted up until the departure date without any penalty charges.
•
-
• Non-stop
Depart from Airport:
Arrive at Airport:
Inbound Flight Reschedule Policy
Adjustments on the returning flight route are allowed prior to the original departure date. An administration charge of Rp 300,000 applies.
Order Rescheduling Policy
Rescheduling is permitted before departure with an administrative charge of Rp 300,000 per passenger per route segment.
Global Refund Policy
Refund claims are eligible prior to flight departure subject to deduction fees of Rp 2,250,000 per ticket.
Payment Details
Base Flight Fare
Rp
Airport Taxes & Fees
Rp
Extra Baggage Upgrade
Rp 0
Seat Selections Fee
Rp 0
Rp