Customers intermittently hit a full-screen raw JSON error when booking:
{"error":"Conflict. Timeslot is taken or does not fit the service."}
booking_process() re-validates the chosen slot at submit time and returned
409/403 raw JSON. Because the public booking form is a full-page POST, that
JSON filled the whole screen.
The trigger is a double submit. After inserting the booking, booking_process()
synchronously runs two Google Calendar createEvent calls, a lunch sync, an ntfy
push and an SMTP confirmation e-mail before redirecting - several seconds - and
the submit button was never disabled. On mobile the guest taps "Send" again; the
second request arrives after the first has committed, so the slot reads as taken.
Evidence: 168 duplicate booking pairs exist in prod (same guest, slot and worker,
consecutive booking ids, including runs of four). All are from 2025, none from
2026 - the 409 guard added around May 2025 converted those silent duplicates
into today's visible error.
Prevent the double submit:
- disable the submit button and relabel it on first submit, ignore later ones
- add a hidden sendBooking field, since disabling a submit button can drop its
name/value from the POST and booking_process() bails to the homepage without it
Handle it gracefully when it still happens:
- new _booking_error() renders a localised page in the right skin instead of raw
JSON, replacing all six JSON responses in booking_process()
- new booking-error views for barber/beauty in no/en/hu, each with a message per
error case and a link back to booking
- new Service_model::getBookingBySlotAndGuest(); if the guest's own booking for
that exact slot already exists the submit is a duplicate rather than a real
conflict, so finish normally instead of erroring. Guarded on a non-empty
e-mail, as admin block bookings are stored with an empty guest_email.
No schema change. Verified on test, dev and prod: friendly page in all three
languages and both skins, double submit redirects to booking-finished without
creating a duplicate row, and no raw JSON in any response.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJso3iGT7TkW5tm4RSBohs
26 lines
1.6 KiB
PHP
Executable File
26 lines
1.6 KiB
PHP
Executable File
<?php
|
|
$bookingErrorMessages = array(
|
|
'slot_taken' => 'Ezt az időpontot sajnos lefoglalták, amíg az űrlapot kitöltötte. Kérjük, válasszon másik időpontot.',
|
|
'worker_unavailable' => 'A kiválasztott kolléga ezen a napon nem dolgozik. Kérjük, válasszon másik napot.',
|
|
'outside_schedule' => 'A kiválasztott időpont a kolléga munkaidején kívül esik. Kérjük, válasszon másik időpontot.',
|
|
'alternate_week' => 'A kiválasztott kolléga csak kéthetente dolgozik. Kérjük, válasszon másik napot.',
|
|
'after_hours' => 'A kezelés nem érne véget zárásig. Kérjük, válasszon korábbi időpontot.',
|
|
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
|
|
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
|
|
);
|
|
|
|
$shownError = isset($bookingErrorCode) && isset($bookingErrorMessages[$bookingErrorCode])
|
|
? $bookingErrorMessages[$bookingErrorCode]
|
|
: $bookingErrorMessages['default'];
|
|
|
|
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
|
|
$backUrl = SITEURL.'hu/booking/'.$backSubpage;
|
|
?>
|
|
<div class="bookingContainer" style="min-height:500px;">
|
|
<div class="bookingThankYouTitle">Ez az időpont már nem foglalható</div>
|
|
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
|
|
<div style="text-align:center; margin-top:30px;">
|
|
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Vissza a foglaláshoz</a>
|
|
</div>
|
|
</div>
|