resolveBookingQr($shortCode); $validated = $request->validate([ 'date' => ['required', 'date', 'after_or_equal:today'], 'service_index' => ['required', 'integer', 'min:0'], ]); return response()->json([ 'slots' => $this->slots->availableSlots( $qrCode, $validated['date'], (int) $validated['service_index'], ), ]); } public function store(Request $request, string $shortCode): JsonResponse { $qrCode = $this->resolveBookingQr($shortCode); $validated = $request->validate([ 'customer_name' => ['required', 'string', 'max:120'], 'customer_email' => ['required', 'email', 'max:200'], 'customer_phone' => ['nullable', 'string', 'max:30'], 'service_index' => ['required', 'integer', 'min:0'], 'starts_at' => ['required', 'date'], ]); try { $result = $this->sales->initiateBooking($qrCode, $validated); } catch (RuntimeException $e) { return response()->json(['error' => $e->getMessage()], 422); } if ($result['checkout_url']) { return response()->json([ 'checkout_url' => $result['checkout_url'], 'callback_url' => $result['callback_url'], ]); } return response()->json([ 'confirmed' => true, 'redirect_url' => route('qr.public.booking.confirmed', [ 'shortCode' => $shortCode, 'booking' => $result['booking']->id, ]), ]); } public function callback(Request $request, string $shortCode): RedirectResponse { $reference = trim((string) $request->query('reference', '')); if ($reference === '') { return redirect('/q/'.$shortCode)->with('error', 'Missing payment reference.'); } try { $booking = $this->sales->completeBooking($reference); } catch (\Throwable) { return redirect('/q/'.$shortCode)->with('order_error', 'Payment could not be verified. Reference: '.$reference); } return redirect()->route('qr.public.booking.confirmed', [ 'shortCode' => $shortCode, 'booking' => $booking->id, ]); } public function confirmed(string $shortCode, int $booking): View { $qrCode = $this->resolveBookingQr($shortCode); $record = $qrCode->bookings()->whereKey($booking)->firstOrFail(); return view('public.qr.booking-confirmed', [ 'qrCode' => $qrCode, 'booking' => $record, ]); } private function resolveBookingQr(string $shortCode): QrCode { $qrCode = QrCode::query() ->where('short_code', $shortCode) ->where('is_active', true) ->firstOrFail(); abort_unless($qrCode->isBookingType(), 404); return $qrCode; } }