mirror of
https://github.com/myronblair/parkerslingshot
synced 2026-06-30 17:50:22 -05:00
3e18d71378
Full booking system with Square card-on-file, 10-step booking flow, pre-departure checklist, and Mailjet email integration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
4.6 KiB
PHP
137 lines
4.6 KiB
PHP
<?php
|
|
/**
|
|
* Parker County Slingshot - Booking API
|
|
*/
|
|
require_once __DIR__ . '/../config.php';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonOut(['success'=>false,'error'=>'Method not allowed'], 405);
|
|
}
|
|
|
|
$name = trim($_POST['name'] ?? '');
|
|
$email = trim($_POST['email'] ?? '');
|
|
$phone = trim($_POST['phone'] ?? '');
|
|
$dob = trim($_POST['dob'] ?? '');
|
|
$method = trim($_POST['payment_method'] ?? 'cash');
|
|
$notes = trim($_POST['notes'] ?? '');
|
|
$startD = trim($_POST['start_date'] ?? '');
|
|
$endD = trim($_POST['end_date'] ?? '');
|
|
|
|
// Validate required fields
|
|
if (!$name || !$email || !$phone || !$dob || !$startD || !$endD) {
|
|
jsonOut(['success'=>false,'error'=>'All fields are required.']);
|
|
}
|
|
|
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
jsonOut(['success'=>false,'error'=>'Invalid email address.']);
|
|
}
|
|
|
|
// Validate dates
|
|
$start = DateTime::createFromFormat('Y-m-d', $startD);
|
|
$end = DateTime::createFromFormat('Y-m-d', $endD);
|
|
if (!$start || !$end || $end <= $start) {
|
|
jsonOut(['success'=>false,'error'=>'Invalid dates selected.']);
|
|
}
|
|
|
|
$days = (int)$start->diff($end)->days;
|
|
$minDays = (int)(getSetting('min_days', 1));
|
|
$maxDays = (int)(getSetting('max_days', 3));
|
|
|
|
if ($days < $minDays || $days > $maxDays) {
|
|
jsonOut(['success'=>false,'error'=>"Rental must be $minDays-$maxDays days."]);
|
|
}
|
|
|
|
// Validate age (25+)
|
|
$dobDt = DateTime::createFromFormat('Y-m-d', $dob);
|
|
if (!$dobDt) jsonOut(['success'=>false,'error'=>'Invalid date of birth.']);
|
|
$age = (new DateTime())->diff($dobDt)->y;
|
|
if ($age < 25) {
|
|
jsonOut(['success'=>false,'error'=>'Must be 25 or older to rent.']);
|
|
}
|
|
|
|
// Check availability
|
|
$conflict = db()->prepare("
|
|
SELECT COUNT(*) FROM pcs_bookings
|
|
WHERE status IN ('approved','active','pending')
|
|
AND NOT (end_date <= :start OR start_date >= :end)
|
|
");
|
|
$conflict->execute(['start'=>$startD,'end'=>$endD]);
|
|
if ($conflict->fetchColumn() > 0) {
|
|
jsonOut(['success'=>false,'error'=>'Those dates are no longer available.']);
|
|
}
|
|
|
|
// Check blocked dates
|
|
$cursor = clone $start;
|
|
while ($cursor <= $end) {
|
|
$blocked = db()->prepare("SELECT id FROM pcs_blocked_dates WHERE blocked_date=?");
|
|
$blocked->execute([$cursor->format('Y-m-d')]);
|
|
if ($blocked->fetch()) {
|
|
jsonOut(['success'=>false,'error'=>'One or more selected dates are unavailable.']);
|
|
}
|
|
$cursor->modify('+1 day');
|
|
}
|
|
|
|
// Handle file uploads
|
|
$licenseFile = null;
|
|
$insuranceFile = null;
|
|
|
|
function saveUpload(string $field, string $dir, string $prefix): ?string {
|
|
if (empty($_FILES[$field]['tmp_name'])) return null;
|
|
$file = $_FILES[$field];
|
|
$allowed = ['image/jpeg','image/png','image/gif','image/webp','application/pdf'];
|
|
if (!in_array($file['type'], $allowed)) return null;
|
|
if ($file['size'] > MAX_UPLOAD_BYTES) return null;
|
|
|
|
if (!is_dir($dir)) mkdir($dir, 0755, true);
|
|
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
|
$name = $prefix.'_'.time().'_'.bin2hex(random_bytes(4)).'.'.$ext;
|
|
$path = $dir.$name;
|
|
move_uploaded_file($file['tmp_name'], $path);
|
|
return $name;
|
|
}
|
|
|
|
$licenseFile = saveUpload('license', LICENSE_DIR, 'lic');
|
|
$insuranceFile = saveUpload('insurance', INSURANCE_DIR, 'ins');
|
|
|
|
if (!$licenseFile) {
|
|
jsonOut(['success'=>false,'error'=>"Driver's license upload failed. Check file type and size."]);
|
|
}
|
|
if (!$insuranceFile) {
|
|
jsonOut(['success'=>false,'error'=>'Insurance upload failed. Check file type and size.']);
|
|
}
|
|
|
|
// Create or find customer
|
|
$customer = db()->prepare("SELECT id FROM pcs_customers WHERE email=?");
|
|
$customer->execute([$email]);
|
|
$cust = $customer->fetch();
|
|
|
|
if ($cust) {
|
|
$custId = $cust['id'];
|
|
db()->prepare("UPDATE pcs_customers SET name=?,phone=?,dob=? WHERE id=?")
|
|
->execute([$name,$phone,$dob,$custId]);
|
|
} else {
|
|
db()->prepare("INSERT INTO pcs_customers (email,name,phone,dob) VALUES (?,?,?,?)")
|
|
->execute([$email,$name,$phone,$dob]);
|
|
$custId = db()->lastInsertId();
|
|
}
|
|
|
|
// Generate booking ref
|
|
$ref = 'PSR-'.strtoupper(bin2hex(random_bytes(3)));
|
|
$pricePerDay = (float)(getSetting('price_per_day', 150));
|
|
$total = $days * $pricePerDay;
|
|
|
|
// Get available fleet
|
|
$fleet = db()->query("SELECT id FROM pcs_fleet WHERE is_available=1 LIMIT 1")->fetch();
|
|
$fleetId = $fleet['id'] ?? null;
|
|
|
|
// Insert booking
|
|
db()->prepare("INSERT INTO pcs_bookings
|
|
(booking_ref,customer_id,fleet_id,start_date,end_date,days,price_per_day,total_amount,
|
|
payment_method,license_file,insurance_file,customer_notes)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)")->execute([
|
|
$ref, $custId, $fleetId, $startD, $endD, $days,
|
|
$pricePerDay, $total, $method, $licenseFile, $insuranceFile, $notes
|
|
]);
|
|
|
|
jsonOut(['success'=>true,'ref'=>$ref,'message'=>'Reservation submitted successfully.']);
|