mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-07-28 09:02:29 -05:00
CRITICAL: block direct access to customer license/insurance uploads (was fully downloadable with no auth); fix double-booking race condition and unstable deposit-hold idempotency key
This commit is contained in:
+39
-22
@@ -39,29 +39,43 @@ $pkg = PACKAGES[$package];
|
||||
$rentalDate = $date;
|
||||
$endDate = date('Y-m-d', strtotime($date . ' +' . $pkg['days'] . ' days'));
|
||||
|
||||
// Check availability
|
||||
$conflict = db()->prepare(
|
||||
"SELECT id FROM bookings
|
||||
WHERE status IN ('pending','confirmed')
|
||||
AND rental_date <= ? AND end_date >= ?"
|
||||
);
|
||||
$conflict->execute([$endDate, $rentalDate]);
|
||||
if ($conflict->fetch()) {
|
||||
echo json_encode(['success'=>false,'error'=>'Sorry, that date is already booked. Please choose another date.']); exit;
|
||||
}
|
||||
$blockedCheck = db()->prepare("SELECT id FROM blocked_dates WHERE block_date BETWEEN ? AND ?");
|
||||
$blockedCheck->execute([$rentalDate, $endDate]);
|
||||
if ($blockedCheck->fetch()) {
|
||||
echo json_encode(['success'=>false,'error'=>'That date is unavailable. Please choose another date.']); exit;
|
||||
// Check availability + create booking atomically. A named advisory lock (scoped to the
|
||||
// requested date range) prevents two concurrent submissions from both passing the
|
||||
// conflict check before either has inserted, which would otherwise double-book the
|
||||
// vehicle for overlapping dates.
|
||||
$lockName = 'psr_booking_' . $rentalDate . '_' . $endDate;
|
||||
$gotLock = (int) db()->query("SELECT GET_LOCK('" . addslashes($lockName) . "', 5)")->fetchColumn();
|
||||
if (!$gotLock) {
|
||||
http_response_code(503);
|
||||
echo json_encode(['success'=>false,'error'=>'Please try again in a moment.']); exit;
|
||||
}
|
||||
|
||||
// Create booking
|
||||
$ref = generateRef();
|
||||
$stmt = db()->prepare(
|
||||
"INSERT INTO bookings (booking_ref, name, email, phone, package, rental_date, end_date, amount, notes)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)"
|
||||
);
|
||||
$stmt->execute([$ref, $name, $email, $phone, $package, $rentalDate, $endDate, $pkg['amount'], $message]);
|
||||
try {
|
||||
$conflict = db()->prepare(
|
||||
"SELECT id FROM bookings
|
||||
WHERE status IN ('pending','confirmed')
|
||||
AND rental_date <= ? AND end_date >= ?"
|
||||
);
|
||||
$conflict->execute([$endDate, $rentalDate]);
|
||||
if ($conflict->fetch()) {
|
||||
echo json_encode(['success'=>false,'error'=>'Sorry, that date is already booked. Please choose another date.']); exit;
|
||||
}
|
||||
$blockedCheck = db()->prepare("SELECT id FROM blocked_dates WHERE block_date BETWEEN ? AND ?");
|
||||
$blockedCheck->execute([$rentalDate, $endDate]);
|
||||
if ($blockedCheck->fetch()) {
|
||||
echo json_encode(['success'=>false,'error'=>'That date is unavailable. Please choose another date.']); exit;
|
||||
}
|
||||
|
||||
// Create booking
|
||||
$ref = generateRef();
|
||||
$stmt = db()->prepare(
|
||||
"INSERT INTO bookings (booking_ref, name, email, phone, package, rental_date, end_date, amount, notes)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)"
|
||||
);
|
||||
$stmt->execute([$ref, $name, $email, $phone, $package, $rentalDate, $endDate, $pkg['amount'], $message]);
|
||||
} finally {
|
||||
db()->query("SELECT RELEASE_LOCK('" . addslashes($lockName) . "')");
|
||||
}
|
||||
|
||||
$dateLabel = date('F j, Y', strtotime($rentalDate));
|
||||
$pkgLabel = $pkg['label'];
|
||||
@@ -127,7 +141,10 @@ $sqId = null;
|
||||
if ($squareToken) {
|
||||
$sqResp = squareApi('POST', '/payments', [
|
||||
'source_id' => $squareToken,
|
||||
'idempotency_key' => $ref . '-dep-' . time(),
|
||||
// Stable per booking ref (not time()-suffixed) so a network retry or
|
||||
// accidental double-submit of this same deposit hold is recognized by
|
||||
// Square as a duplicate instead of placing a second hold on the card.
|
||||
'idempotency_key' => $ref . '-dep',
|
||||
'amount_money' => ['amount' => (int)(DEPOSIT_AMOUNT * 100), 'currency' => 'USD'],
|
||||
'autocomplete' => false, // hold only — capture when confirmed
|
||||
'location_id' => SQUARE_LOCATION_ID,
|
||||
|
||||
Reference in New Issue
Block a user