mirror of
https://github.com/myronblair/parkerslingshot
synced 2026-06-30 17:50:22 -05:00
Initial commit — Parker County Slingshot Rentals booking site
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>
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
<?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.']);
|
||||
Reference in New Issue
Block a user